测试远程TCP端口是否从shell脚本打开

我正在寻找一种快速而简单的方法,用于正确地测试一个给定的TCP端口是否在远程服务器上打开,从Shell脚本中。

我已经设法用telnet命令做到这一点,当端口被打开时,它工作得很好,但当它没有超时时,它似乎并没有超时,只是挂在那里……

下面是一个例子:

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

我要么需要一个更好的方法,或者一种方法强制telnet超时,如果它没有在8秒内连接,例如,并返回一些我可以在Shell中捕获的东西(返回代码,或stdout中的字符串)。

我知道Perl方法,它使用IO::Socket::INET模块,并编写了一个成功的测试端口的脚本,但如果可能的话,宁愿避免使用Perl。

注意:这是我的服务器正在运行(我需要从哪里运行这个)

SunOS 5.10 Generic_139556-08 i86pc i386 i86pc

585465 次浏览

我猜现在给答案已经太迟了,而且这个答案可能不太好,但是你可以……

把它放在一个while循环中,并在上面加一个计时器。我更喜欢Perl而不是Solaris,但是根据你所使用的shell,你应该能够做一些像这样的事情:

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

然后在while循环中添加一个标志,这样如果它在完成之前超时,你可以引用超时作为失败的原因。

我怀疑telnet也有一个超时开关,但就我所知,我认为上面的方法是可行的。

使用netcat,你可以像这样检查端口是否打开:

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

如果TCP端口被打开,nc的返回值将为成功,如果无法建立TCP连接,则返回值将为失败(通常为返回代码1)。

某些版本的nc会挂起,因为它们即使在从/dev/null接收到文件结束后也不会关闭套接字的发送部分。在我自己的Ubuntu笔记本电脑(18.04)上,我安装的netcat-openbsd版本的netcat提供了一个解决方案:-N选项是必须的,以获得立即的结果:

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

正如B. Rhodes所指出的,nc (netcat)将完成这项工作。更紧凑的使用方法:

nc -z <host> <port>

这样nc将只检查端口是否打开,成功时退出0,失败时退出1。

快速交互式检查(5秒超时):

nc -z -v -w5 <host> <port>

目录:

  • 使用bash和timeout
    • 命令
    • 例子
  • <李>使用nc
    • 命令
    • RHEL 6 (nc-1.84)
      • 安装
      • 例子
    • (nmap-ncat-6.40)
      • 安装
      • 例子
  • 讲话

使用bash和timeout:

注意timeout应该随RHEL 6+一起出现,或者在GNU coreutils 8.22中找到。在MacOS上,使用brew install coreutils安装它,并将其用作gtimeout

命令:

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

如果参数化主机和端口,请确保如上所述将它们指定为${HOST}${PORT}。不要仅仅将它们指定为$HOST$PORT,即不带大括号;在这种情况下行不通。

例子:

成功:

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

失败:

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

如果必须保留bash的退出状态,

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

使用nc:

注意,在RHEL 7上安装了一个向后不兼容的nc版本。

命令:

注意,下面的命令是唯一的,因为它对于RHEL 6和7都是相同的。只是安装和输出不同而已。

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

RHEL 6 (nc-1.84):

安装:

$ sudo yum install nc

例子:

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

如果主机名映射到多个ip,上述失败的命令将循环遍历多个或所有ip。例如:

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

安装:

$ sudo yum install nmap-ncat

例子:

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

如果主机名映射到多个ip,上述失败的命令将循环遍历多个或所有ip。例如:

$ 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

备注:

-v (--verbose)参数和echo $?命令当然仅供说明。

如果你正在使用kshbash,它们都支持使用/dev/tcp/IP/PORT构造从套接字到/从套接字的IO重定向。在这个Korn shell示例中,我将从套接字重定向no-op ()标准输入:

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

如果套接字没有打开,shell将打印一个错误:

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

因此,你可以在如果条件中使用它作为测验:

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

no-op是在一个子shell中,所以如果stin重定向失败,我可以扔掉std-err。

我经常使用/dev/tcp来检查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$

这个一行程序打开文件描述符9用于从套接字读取和写入,将HTTP GET打印到套接字,并使用cat从套接字读取。

使用-z-w TIMEOUT选项来实现nc很容易,但并非所有系统都安装了nc。如果你有一个最新版本的bash,这将工作:

# 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

这里发生的事情是timeout将运行子命令,如果它没有在指定的超时内退出(在上面的例子中是1秒),就会杀死它。在这种情况下,bash是子命令,并使用其特殊的bash3来尝试打开到指定的服务器和端口的连接。如果bash可以在超时时间内打开连接,cat将立即关闭连接(因为它是从/dev/null读取的),并以状态代码0退出,该状态代码将通过bash传播,然后是timeout。如果bash在指定的超时之前获得连接失败,则bash将退出,退出码为1,timeout也将返回该退出码。如果bash不能建立连接并且指定的超时过期,则timeout将终止bash并以124的状态退出。

虽然这是一个老问题,但我只是处理了它的一个变体,但这里的解决方案都不适用,所以我找到了另一个,并为子孙后代添加了它。是的,我知道OP说他们知道这个选项,它不适合他们,但对于任何跟随它的人来说,它可能是有用的。

在我的例子中,我想从docker构建中测试本地apt-cacher-ng服务的可用性。这意味着在测试之前绝对不能安装任何东西。没有ncnmapexpecttelnetpython。然而,perl与核心库一起存在,所以我使用了这个:

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

我需要在cron中运行的短脚本,没有输出。我用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

要运行它,你应该安装nmap,因为它不是默认安装包。

在Bash中,对TCP/UDP连接使用伪设备文件是直接的。以下是剧本:

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

测试:

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

下面是一行代码(Bash语法):

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

请注意,有些服务器可以通过防火墙保护免受SYN flood攻击,因此您可能会遇到TCP连接超时(~75秒)。要解决超时问题,请尝试:

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

看:如何减少TCP connect()系统调用超时?

这在幕后使用telnet,在mac/linux上似乎工作得很好。它不使用netcat,因为linux/mac版本之间的差异,这适用于默认的mac安装。

例子:

$ 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

Nmap-ncat用于测试尚未使用的本地端口


availabletobindon() {
port="$1"
nc -w 2 -i 1 localhost "$port" 2>&1 | grep -v -q 'Idle timeout expired'
return "$?"
}

我需要一个更灵活的解决方案来处理多个git存储库,所以我基于12编写了以下sh代码。你可以用你的服务器地址代替gitlab.com,用你的端口代替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

在某些情况下,像curl, telnet, nc和nmap这样的工具是不可用的,你仍然有机会使用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

如果你想使用nc,但没有支持-z的版本,请尝试使用--send-only:

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

和timeout:

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

如果是IP,则不需要DNS查询:

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

它根据是否可以连接返回代码为-z

使用bash检查端口

例子

$ ./test_port_bash.sh 192.168.7.7 22

端口“22”被打开

代码

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

在投票最多的答案的基础上,这里有一个等待两个端口打开的函数,还有一个超时。请注意必须打开的两个端口,8890和1111,以及max_attempts(每秒1次)。

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
}

我的机器不支持nc/dev/tcp/$hostname/$port,但支持timeout,所以我返回telnet如下:

if echo "quit" | timeout 2 telnet $SERVER $PORT 2>&1 | grep -q 'Connected to'; then
echo "Connection to $SERVER on port $PORT succeeded"
exit 0
else
echo "Connection to $SERVER on port $PORT failed"
exit 1
fi