Stop using Telnet to test ports

José Vicente Núñez
10 min readApr 18, 2023

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. ]

Originally published at https://www.redhat.com on April 18, 2023.

--

--

José Vicente Núñez

🇻🇪 🇺🇸, proud dad and husband, DevOps and sysadmin, recreational runner and geek.