Skip to main content

Stop using Telnet to test ports

Make life simpler by automating network checks with tools like Expect, Bash, Netcat, and Nmap instead.
Image
Laptop keyboard with Ethernet patch cable

Most sysadmins know what Telnet is. Before more robust and secure alternatives like Secure Shell (SSH) appeared, Telnet was the most common way to access remote systems. The Telnet protocol is not encrypted, which is why people no longer use it to provide access to a server. Instead, people use it to check whether a service is listening on a given port, like this: telnet $machine $port. For example:

$ telnet raspberrypi 8086
Trying fd22:4e39:e630:1:dea6:32ff:fef9:4748...
Connected to raspberrypi.
Escape character is '^]'.
  
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad RequestConnection closed by foreign host.

Press Ctrl+D to exit the session or press a key to force the server to close the connection.

This works perfectly fine, assuming you're testing only one service and one port, but what if you need to perform automatic checks on a large combination of hosts and ports? I prefer to let my computer do the boring stuff for me, especially when testing TCP/IP basic connectivity, like open ports.

In this article, I will show you how to perform the following tasks:

  • Improve your usage of Telnet with Expect
  • Do the same with Bash
  • Use Netcat as a replacement for TCP port check
  • Use Nmap to perform more complex checks

The examples I use will focus on TCP connectivity testing rather than UDP, which is out of scope for this article.

Know what to expect

Expect is an extension of the programming language Tcl, which can be used to automate external processes. With Expect, you can read the list of hosts and ports from a file and use Telnet to check whether a TCP port is responding or not.

Say you have the following configuration file:

google.com 80
amazon.com 80
raspberrypi 22,9090,8086,21
dmaf5 22,80

With a bit of Expect magic, you could automate the process using this script:

#!/usr/bin/env -S expect -f
# Poor man TCP port scanner with Telnet and Expect
# Author: Jose Vicente Nunez <@josevnz@fosstodon.org>
if { $argc == 0 } {
        send_user "Please provide the data file with machine port, one per line!"
        exit 100
}
set timeout 5
set f [open [lindex $argv 0]]
foreach host [split [read $f] "\n"] {
    set pairs [split $host " "];
    set machine [lindex $pairs 0]
    set ports [lindex $pairs 1]
    foreach port [split $ports ","] {
        log_user 0
        spawn /bin/telnet -e ! $machine $port
        expect {
            log_user 1
            "Connection refused" { catch {exp_close}; exp_wait; send_user "ERROR: $machine -> $port\n" }
            "Escape character is '!'." { send_user "OK: $machine -> $port\n"; send "!\r" }
        }
    }
}
close $f

Before running the script, ensure you have both Expect and Telnet installed.

$ sudo dnf -y install expect telnet

For example, your output could look like this:

$ ./tcp_port_scan.exp port_scan.csv 
OK: google.com -> 80
OK: amazon.com -> 80
OK: raspberrypi -> 22
OK: raspberrypi -> 9090
OK: raspberrypi -> 8086
ERROR: raspberrypi -> 21
OK: dmaf5 -> 22
ERROR: dmaf5 -> 80

When should you use this type of script? Expect is a good alternative if you need something quick, especially if you already have both Expect and Telnet installed on one of your machines.

This solution is not efficient, however, because you have to fork a telnet session for every port you want to check. You also have to account for all the possible responses from the Telnet command, as well as subtle issues like your timeout being too small (if the port is being filtered, for example). 

[ Network getting out of control? Check out Network automation for everyone, a complimentary book from Red Hat. ]

You can do it in Bash too

Next, you might decide it is OK to write a TCP port check in Bash:

#!/bin/bash -e
# Poor man TCP port scanner with Bash
# Author: Jose Vicente Nunez <@josevnz@fosstodon.org>
if [ -n "$1" ] && [ -f "$1" ]; then
  while read -r line; do
    machine=$(echo "$line"| /bin/cut -d' ' -f1)|| exit 100
    ports=$(echo "$line"| /bin/cut -d' ' -f2)|| exit 101
    OLD_IFS=$OLD_IFS
    IFS=","
    for port in $ports; do
      if  (echo >/dev/tcp/"$machine"/"$port") >/dev/null 2>&1; then
        echo "OK: $machine -> $port"
      else
        echo "ERROR: $machine -> $port"
      fi
    done
    IFS=$OLD_IFS
  done < "$1"
else
  echo "ERROR: Invalid or missing data file!"
  exit 103
fi

The pure Bash script works much the same as the Expect version:

$ ./tcp_port_scan.sh port_scan.csv 
OK: google.com -> 80
OK: amazon.com -> 80
OK: raspberrypi -> 22
OK: raspberrypi -> 9090
OK: raspberrypi -> 8086
ERROR: raspberrypi -> 21
OK: dmaf5 -> 22
ERROR: dmaf5 -> 80

It is faster than the Expect version because it doesn't require Telnet forking, but error handling is complicated. It also doesn't deal well with filtered ports.

Any other options?

For example, what if you want to check connectivity with a host that is behind a firewall?

[ Download a Bash shell scripting cheat sheet. ]

Use Netcat

Netcat is another versatile program that can use proxies to connect to other machines. It also has several implementations.

For the sake of example, assume you want to check whether port 22 is open on host raspberrypi.home:

$ nc -z -v -w 5 raspberrypi 22
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to fd22:4e39:e630:1:dea6:32ff:fef9:4748:22.
Ncat: 0 bytes sent, 0 bytes received in 0.06 seconds.

# Trying a closed port like 222
$ nc -z -v -w 5 raspberrypi 222
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connection to fd22:4e39:e630:1:dea6:32ff:fef9:4748 failed: Connection refused.
Ncat: Trying next address...
Ncat: Connection refused.

With that in mind, you can automate scanning a bunch of hosts using a Netcat wrapper:

# Port check with Netcat
# Author: Jose Vicente Nunez <@josevnz@fosstodon.org>
if [ -n "$1" ] && [ -f "$1" ]; then
  while read -r line; do
    machine=$(echo "$line"| /bin/cut -d' ' -f1)|| exit 100
    ports=$(echo "$line"| /bin/cut -d' ' -f2)|| exit 101
    OLD_IFS=$OLD_IFS
    IFS=","
    for port in $ports; do
      if  /usr/bin/nc -z -v -w 5 "$machine" "$port" > /dev/null 2>&1; then
        echo "OK: $machine -> $port"
      else
        echo "ERROR: $machine -> $port"
      fi
    done
    IFS=$OLD_IFS
  done < "$1"
else
  echo "ERROR: Invalid or missing data file!"
  exit 103
fi

Why would you use nc instead of the previous script written in Bash? There are a couple of reasons. First, you can use a SOCKS (secure socket) proxy to scan servers with -x. For example, start a SOCKS proxy like this on port 2080:

josevnz@raspberrypi:~$ ssh -f -g -D 2080 -C -q -N josevnz@192.168.1.27

Then access the servers behind your firewall through the proxy like this:

$ nc --proxy 192.168.1.27:2080 --proxy-type socks5 -z -v -w 5 redhat.com 80
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to proxy 192.168.1.27:2080
Ncat: No authentication needed.
Ncat: Host redhat.com will be resolved by the proxy.
Ncat: connection succeeded.
Ncat: 0 bytes sent, 0 bytes received in 0.12 seconds.

You can also use Netcat to start a server to help you test basic connectivity if you don't have a server handy.

On the receiving side, start a server:

josevnz@raspberrypi:~$ nc -l 2080

Then on the client, enter:

$ nc --verbose 192.168.1.27 2080
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to 192.168.1.27:2080.
Hello
Hi

Now you can write and receive messages on both sides, like a bidirectional chat. There are other features and use cases for nc; read the documentation to learn more.

Try Nmap, the multitool of network tools

Netcat is handy for TCP connectivity testing, but when it comes to a command-line tool with a powerful array of options, you cannot beat Nmap. Nmap offers a higher grade of automation than Netcat. For example, you can provide a data file in a format that Nmap understands, like this:

google.com
amazon.com
raspberrypi.home
dmaf5.home

Then you can use Nmap to check all these hosts for just ports 80 and 443:

$ nmap -iL port_scan_nmap.csv -p80,443
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-19 20:18 EDT
Nmap scan report for google.com (142.250.72.110)
Host is up (0.014s latency).
Other addresses for google.com (not scanned): 2607:f8b0:4006:81c::200e
rDNS record for 142.250.72.110: lga34s32-in-f14.1e100.net

PORT    STATE SERVICE
80/tcp  open  http
443/tcp open  https

Nmap scan report for amazon.com (54.239.28.85)
Host is up (0.019s latency).
Other addresses for amazon.com (not scanned): 52.94.236.248 205.251.242.103

PORT    STATE SERVICE
80/tcp  open  http
443/tcp open  https

Nmap scan report for raspberrypi.home (192.168.1.27)
Host is up (0.00062s latency).
Other addresses for raspberrypi.home (not scanned): fd22:4e39:e630:1:dea6:32ff:fef9:4748

PORT    STATE  SERVICE
80/tcp  closed http
443/tcp closed https

Nmap scan report for dmaf5.home (192.168.1.30)
Host is up (0.00041s latency).
Other addresses for dmaf5.home (not scanned): fd22:4e39:e630:1:67b8:6c9e:14f0:5d6c fd22:4e39:e630:1:dd80:f446:ff6c:aa4a 192.168.1.31

PORT    STATE  SERVICE
80/tcp  closed http
443/tcp closed https

This gives you more freedom with the IP address or network ranges, but not much choice if you want to use different port combinations. To deal with this, either iterate to each machine and port combination from an external script and then call Nmap, or let Nmap also check those nonexisting ports and then analyze the results:

$ nmap -iL port_scan_nmap.csv -p80,22,9090,8086,21 --open \
    -oG -| /bin/rg -v -e 'Status: Up|^#'
Host: 142.250.72.110 (lga34s32-in-f14.1e100.net)    Ports: 80/open/tcp//http/// Ignored State: filtered (4)
Host: 205.251.242.103 (s3-console-us-standard.console.aws.amazon.com)   Ports: 80/open/tcp//http/// Ignored State: closed (4)
Host: 192.168.1.27 (raspberrypi.home)   Ports: 22/open/tcp//ssh///, 8086/open/tcp//d-s-n///, 9090/open/tcp//zeus-admin///   Ignored State: closed (2)
Host: 192.168.1.30 (dmaf5.home) Ports: 22/open/tcp//ssh///  Ignored State: closed (4)

Nmap can use SOCKS5 proxies, but not in the way you might think. Nmap distribution also comes with its own version of Netcat called ncat. Which one you should use depends on your use case. I usually work with whichever version is installed.

When an open TCP socket test is not enough

Just checking whether a TCP port is open will not indicate whether a service is healthy. The server may be accepting connections, yet there could be more subtle problems. For example, you can check to see if a web server TLS works and if the digital certificates look correct:

$ sudo dnf install -y openssl.x86_64

$ openssl s_client -tls1_2 -connect solomon.stupidzombie.com:443
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = solomon.stupidzombie.com
verify error:num=10:certificate has expired
notAfter=Mar 11 14:38:06 2023 GMT
verify return:1
depth=0 CN = solomon.stupidzombie.com
notAfter=Mar 11 14:38:06 2023 GMT
verify return:1
---
...

In the example above, the socket connection worked, but the SSL certificate has expired.

Here is another way to test the same web server:

$ curl --fail --verbose https://solomon.stupidzombie.com:443
*   Trying 132.145.176.191:443...
* Connected to solomon.stupidzombie.com (132.145.176.191) port 443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS header, Unknown (21):
* TLSv1.3 (OUT), TLS alert, certificate expired (557):
* SSL certificate problem: certificate has expired
* Closing connection 0
curl: (60) SSL certificate problem: certificate has expired
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

This time, curl indicates that the certificate expired. The web server is working as expected, but there is a problem with the digital certificates.

Not every HTTP application be tested the same way, however. Take a look at how Grafana can tell you if it is okay or not:

$ curl --fail --silent \
    http://raspberrypi:3000/api/health && \
    printf "\nLook, I'm OK\n"
{
  "commit": "21c1d14e91",
  "database": "ok",
  "version": "9.3.2"
}
Look, I'm OK

Or an InfluxDB database:

$ curl --fail http://raspberrypi:8086/ping && \
    printf "Look, I'm OK"
Look, I'm OK

Here is a nice surprise for you: Nmap can also call scripts to perform high-level checks on applications like web servers. The example below uses http-fetch to fetch files from servers during the test:

$ nmap -p443 -PS443 --open --script http-fetch \
    --script-args \
    'maxpagecount=1,destination=/tmp/files' 
    solomon.stupidzombie.com
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-29 20:48 EDT
Nmap scan report for solomon.stupidzombie.com (132.145.176.191)
Host is up (0.023s latency).

PORT    STATE SERVICE
443/tcp open  https
|_http-fetch: Successfully Downloaded Everything At: /tmp/files/132.145.176.191/443/

Nmap done: 1 IP address (1 host up) scanned in 0.62 seconds

$ find /tmp/files/132.145.176.191/443/
/tmp/files/132.145.176.191/443/
/tmp/files/132.145.176.191/443/index.html

What about a MySQL database or IMAP server? As you can see, there are many ways to tackle this problem.

What to learn next

  • Expect is an extension of Tcl, so try a tutorial to get familiar with what the language can do.
  • Bash can also be used to do UDP checks. This excellent guide can show you how to do that and much more.
  • Netcat and Nmap are powerful tools that deserve time to be studied. You may be surprised by the number of things they can do for you besides basic TCP port checks.
  • Nmap can be extended with Lua scripts to perform more complex checks.
  • In the case of Nmap, you can even use scripts to test at the protocol level, not just opening the port.
  • A Telnet client may not be installed in your Linux distribution anymore, as the server is considered insecure, so learning other tools is a good idea.

You can also use programming languages to perform connectivity checks, which allows you to address more complex scenarios. In my next article, I'll show you how to use Python and Scapy to perform complex packet manipulations.

[ Cheat sheet: Get a list of Linux utilities and commands for managing servers and networks. ]

Author’s photo

Jose Vicente Nunez

Proud dad and husband, software developer and sysadmin. Recreational runner and geek. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.