Teste se a porta TCP remota está aberta em um script de shell

297

Estou procurando um método rápido e simples para testar adequadamente se uma determinada porta TCP está aberta em um servidor remoto, de dentro de um script do Shell.

Eu consegui fazer isso com o comando telnet, e funciona bem quando a porta é aberta, mas parece que o tempo limite não está disponível e fica travado ...

Aqui está uma amostra:

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

Eu preciso de uma maneira melhor ou de forçar o tempo limite do telnet se ele não se conectar em menos de 8 segundos, por exemplo, e retornar algo que eu possa pegar no Shell (código de retorno ou string no stdout).

Conheço o método Perl, que usa o módulo IO :: Socket :: INET e escreveu um script bem-sucedido que testa uma porta, mas prefere evitar o uso de Perl, se possível.

Nota: É disso que meu servidor está sendo executado (de onde eu preciso executar isso)

SunOS 5.10 Genérico_139556-08 i86pc i386 i86pc

Yanick Girouard
fonte
E o Netcat ou o Nmap ?
Jeremiah Willcock
A resposta estava no Expect. Escrevemos um script simples que envia um telnet na porta que precisávamos, com um tempo limite de 8 segundos. Há muitos exemplos para escolher também. Nós nossa Baseado fora este post: unix.com/shell-programming-scripting/...
Yanick Girouard
1
check_tcp em github.com/monitoring-plugins/monitoring-plugins pode fazer isso, incluindo a inserção de strings e a verificação de uma resposta esperada.

Respostas:

452

Como apontado por B. Rhodes, ncfará o trabalho. Uma maneira mais compacta de usá-lo:

nc -z <host> <port>

Dessa forma nc, apenas será verificado se a porta está aberta, saindo com 0 em caso de sucesso, 1 em caso de falha.

Para uma verificação interativa rápida (com um intervalo de 5 segundos):

nc -z -v -w5 <host> <port>
Alessio Gaeta
fonte
48
O padrão centos7 usa o nmap netcat e não possui a opção -z.
Jolestar
7
Isso não funciona no RHEL / Centos. Para aqueles distros que você precisa para: nc -vn <host> <port>
Justin Dennahower
2
FWIW, reformulei completamente minha resposta com um exemplo , aplicável separadamente ao RHEL 6 e ao RHEL 7. #
Acumenus
4
no Mac, pelo menos, pode ser necessário adicionar -G#para definir um tempo limite de conexão separado / além do -w#tempo limite, que basicamente funciona como um tempo limite de leitura.
Doktor J
1
@jolestar Você pode atualizar manualmente o Ncat no Centos 7 para obter a -zopção. Você pode considerar: unix.stackexchange.com/questions/393762/…
Damien Ó Ceallaigh
150

É fácil o suficiente para fazer com os -ze -w TIMEOUTopções para nc, mas nem todos os sistemas tenham ncinstalado. Se você possui uma versão recente o suficiente do bash, isso funcionará:

# 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

O que está acontecendo aqui é que timeoutexecutará o subcomando e o matará se ele não sair dentro do tempo limite especificado (1 segundo no exemplo acima). Nesse caso, bashé o subcomando e usa seu tratamento especial / dev / tcp para tentar abrir uma conexão com o servidor e a porta especificados. Se bashconseguir abrir a conexão dentro do tempo limite, catapenas a feche imediatamente (já que está lendo a partir de /dev/null) e sairá com um código de status 0que será propagado através bashe depois timeout. Se houver bashuma falha na conexão antes do tempo limite especificado, bashserá encerrado com um código de saída 1, que timeouttambém retornará. E se o bash não conseguir estabelecer uma conexão e o tempo limite especificado expirar,timeoutmatará bashe sairá com um status de 124.

onlynone
fonte
1
Está /dev/tcpdisponível em sistemas diferentes do Linux? E os Macs em particular?
Peter Peter
8
A coisa / dev / tcp é um recurso do bash, então sim. No entanto, parece que os macs não têm tempo limite ...
onlynone
1
@onlynone - Parece que timeouttambém não existe no FreeBSD, e na minha antiga caixa do Ubuntu é um pacote instalável, mas não existe por padrão. Seria ótimo se houvesse uma maneira de fazer isso apenas no bash, sem ferramentas de terceiros, como timeout ou netcat.
Graham
3
Só queria dizer que timeoutparece ser parte de coreutils GNU, e pode ser instalado em Macs com homebrew: brew install coreutils. Ele estará disponível como gtimeout.
onlynone
1
@Wildcard Um log de alterações do bash sugere bash-2.04-devel, embora o suporte a nomes de host possa ter sido adicionado bash-2.05-alpha1.
Acumenus
109

TOC:

  • Usando bash e timeout
    • Comando
    • Exemplos
  • Usando nc
    • Comando
    • RHEL 6 (nc-1,84)
      • Instalação
      • Exemplos
    • RHEL 7 (nmap-ncat-6.40)
      • Instalação
      • Exemplos
  • Observações

Usando bash e timeout:

Observe que ele timeoutdeve estar presente no RHEL 6+ ou é encontrado alternativamente no GNU coreutils 8.22. No MacOS, instale-o usando brew install coreutilse use-o como gtimeout.

Comando:

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

Se você estiver parametrizando o host e a porta, especifique-os como ${HOST}e ${PORT}acima. Não os especifique meramente como $HOSTe $PORT, ou seja, sem os aparelhos; não vai funcionar neste caso.

Exemplo:

Sucesso:

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

Falha:

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

Se você deve preservar o status de saída de bash,

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

Usando nc:

Observe que uma versão incompatível com versões anteriores ncé instalada no RHEL 7.

Comando:

Observe que o comando abaixo é único, pois é idêntico para o RHEL 6 e 7. É apenas a instalação e a saída que são diferentes.

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

RHEL 6 (nc-1,84):

Instalação:

$ sudo yum install nc

Exemplos:

Sucesso:
$ nc -w 2 -v canyouseeme.org 80 </dev/null; echo $?
Connection to canyouseeme.org 80 port [tcp/http] succeeded!
0
Falha:
$ 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

Se o nome do host for mapeado para vários IPs, o comando com falha acima percorrerá muitos ou todos eles. Por exemplo:

$ 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):

Instalação:

$ sudo yum install nmap-ncat

Exemplos:

Sucesso:
$ 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
Falha:
$ nc -w 2 -v canyouseeme.org 81 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connection timed out.
1

Se o nome do host for mapeado para vários IPs, o comando com falha acima percorrerá muitos ou todos eles. Por exemplo:

$ 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

Observações:

O argumento -v( --verbose) e o echo $?comando são obviamente apenas para ilustração.

Acumenus
fonte
3
timeout 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?é um ótimo ponto!
Ipeacocks
adicione 2>&1a não echo estado result_test=$(nc -w 2 $ip_addreess 80 </dev/null 2>&1 ; echo $?)
Sanya Snex
55

Com netcatvocê pode verificar se uma porta está aberta assim:

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

O valor de retorno ncserá bem-sucedido se a porta TCP for aberta e falhará (normalmente o código de retorno 1) se não puder estabelecer a conexão TCP.

Algumas versões ncserão interrompidas quando você tentar isso, porque elas não fecham a metade do soquete de envio mesmo depois de receber o final do arquivo /dev/null. No meu próprio laptop Ubuntu (18.04), a netcat-openbsdversão do netcat que eu instalei oferece uma solução alternativa: a -Nopção é necessária para obter um resultado imediato:

nc -N my.example.com 80 < /dev/null
Brandon Rhodes
fonte
1
Funciona muito bem para sabores ncque não suportam a -wbandeira.
David Dossot
5
obrigado - também funciona para versões do nc sem -zsuporte - funciona no RHEL / CentOS 7, por exemplo #
23220 Andrew Andrew
1
Embora essa abordagem seja boa, -wé necessária, caso contrário, o comando quando usado em um script pode travar por mais de dez segundos. Eu escrevi uma resposta que inclui -w.
Acumenus
2
+1 isso é ótimo porque o nc é padrão com o linux alpino e o ubuntu ambos. provavelmente outros. não é necessário instalar mais quando você estiver conectado e não puder. obrigado por isso! Devo acrescentar,nc host port -w 2 && echo it works
std''OrgnlDave
2
Eu prefiro nc -vz localhost 3306. Dá uma saída mais detalhada. Tentei apenas no mac.
perfil completo de EFreak
29

No Bash, usar arquivos de pseudo-dispositivo para conexões TCP / UDP é direto. Aqui está o 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

Teste:

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

Aqui está uma linha (sintaxe do Bash):

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

Observe que alguns servidores podem ser protegidos por firewall contra ataques de inundação SYN; portanto, você pode experimentar um tempo limite de conexão TCP (~ 75seg). Para solucionar o problema de tempo limite, tente:

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

Consulte: Como diminuir o tempo limite da chamada do sistema TCP connect ()?

kenorb
fonte
1
@ABB Isso está relacionado à configuração de firewall do servidor, que não fornece nenhuma resposta para impedir ataques de inundação SYN. A execução telnet canyouseeme.org 81também trava. Isso é controlado pelos seus limites de tempo limite, que provavelmente são codificados no bash. Consulte: diminuir o tempo limite da chamada do sistema TCP connect () .
Kenorb # 18/16
Para parametrização, agora parece ser necessário especificar $SERVERe $PORTcom chaves, pelo menos como ${SERVER}e ${PORT}.
Acumenus
13

Eu precisava de uma solução mais flexível para trabalhar em vários repositórios git, então escrevi o seguinte código sh com base em 1 e 2 . Você pode usar o endereço do servidor em vez do gitlab.com e sua porta em substituição a 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
fonte
1
Obrigado! Faz minha vida mais fácil para /etc/rc.local
Shahin Khaled
10

Se você estiver usando o ksh ou o bash , ambos suportam o redirecionamento de E / S para / de um soquete usando a construção / dev / tcp / IP / PORT . Neste shell Korn exemplo, eu estou redirecionando não-op de ( : ) std-in de um soquete:

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

O shell imprime um erro se o soquete não estiver aberto:

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

Portanto, você pode usar isso como teste em uma condição if :

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

O no-op está em um subshell para que eu possa jogar fora o std-err se o redirecionamento do std-in falhar.

Costumo usar / dev / tcp para verificar a disponibilidade de um recurso por 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$ 

Essa linha única abre o descritor de arquivo 9 para leitura e gravação no soquete, imprime o HTTP GET no soquete e usa catpara ler a partir do soquete.

merebagatelle
fonte
1
@ABB Acho que você quer dizer que trava por um longo tempo se a porta não estiver respondendo, como por exemplo quando filtrada ou se não houver nada no IP. Uma porta fechada não causa um atraso se a porta recusar ativamente a conexão. Para muitos propósitos, isso é bastante aceitável.
Mc0e 19/0318
Essa técnica já foi publicada antes desta resposta em uma resposta anterior da kenorb . De qualquer forma, o ponto mais importante é que, no entanto, ele fica travado por um longo tempo se a porta não estiver respondendo. Por exemplo, tente </dev/tcp/canyouseeme.org/80e depois </dev/tcp/canyouseeme.org/81.
Acumenus 19/0318
9

Embora seja uma pergunta antiga, acabei de lidar com uma variante, mas nenhuma das soluções aqui era aplicável, então encontrei outra e a adicionei para a posteridade. Sim, eu sei que o OP disse que eles estavam cientes dessa opção e que não era adequada a eles, mas para qualquer um que a seguir poderá ser útil.

No meu caso, quero testar a disponibilidade de um apt-cacher-ngserviço local a partir de uma dockercompilação. Isso significa que absolutamente nada pode ser instalado antes do teste. Não nc, nmap, expect, telnetou python. perlno entanto, está presente, junto com as bibliotecas principais, então usei isso:

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

Em alguns casos em que ferramentas como curl, telnet, nc ou nmap estão indisponíveis, você ainda tem uma chance com o 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
fonte
6

verifique as portas usando o bash

Exemplo

$ ./test_port_bash.sh 192.168.7.7 22

a porta 22 está aberta

Código

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
fonte
4

Se você deseja usar, ncmas não possui uma versão compatível -z, tente usar --send-only:

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

e com tempo limite:

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

e sem pesquisa de DNS, se for um IP:

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

Ele retorna os códigos com -zbase em se ele pode se conectar ou não.

Chris Mendez
fonte
1

Suponho que seja tarde demais para uma resposta, e isso pode não ser bom, mas aqui está ...

Que tal colocá-lo dentro de um loop while com um cronômetro de algum tipo. Sou mais cara do Perl do que o Solaris, mas dependendo do shell que você estiver usando, você poderá fazer algo como:

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

Depois, basta adicionar um sinalizador no loop while, para que, se o tempo limite exceder o tempo limite, você pode citar o tempo limite como motivo da falha.

Suspeito que o telnet também tenha um interruptor de tempo limite, mas no topo da minha cabeça, acho que o exposto acima funcionará.

Thomas Vermelho
fonte
1

Eu precisava de um script curto, que foi executado no cron e não tem saída. Resolvo meu problema usando o 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

Para executá-lo Você deve instalar o nmap porque não é o pacote instalado padrão.

Krystian Kulasza
fonte
nmapA saída grepable de também pode ser útil -oG -para enviá-la ao stdout. (o grep iria precisar de um bit de alteração)
van den Berg Gert
0

Isso usa o telnet nos bastidores e parece funcionar bem no mac / linux. Ele não usa o netcat devido às diferenças entre as versões no linux / mac, e isso funciona com uma instalação padrão do mac.

Exemplo:

$ 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
fonte
0

Com base na resposta mais votada, aqui está uma função para aguardar a abertura de duas portas, com um tempo limite também. Observe as duas portas que devem estar abertas, 8890 e 1111, bem como as max_attempts (1 por segundo).

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

nmap-ncat para testar a porta local que ainda não está em uso


availabletobindon() {
  port="$1"
  nc -w 2 -i 1 localhost "$port" 2>&1 | grep -v -q 'Idle timeout expired'
  return "$?"
}
isleofgray
fonte
Não me diz que minha porta 80 está aberta.
21716