I just had this quick idea to write a tcp port scanner in bash. Bash supports a special /dev/tcp/host/port file that you can read/write. Writing to this special file makes bash open a tcp connection to host:port . If writing to the port succeeds, the port is open, else the port is closed.

At first I wrote this quick script.

for port in {1..65535}; do echo >/dev/tcp/google.com/$port && echo "port $port is open" || echo "port $port is closed" done

This code loops over ports 1-65535 and tries to open google.com:$port . However, this doesn't work that well because if the port is closed, it takes bash like 2 minutes to realize that.

To solve this I needed something like alarm(2) system call to interrupt bash. Bash doesn't have a built-in alarm function, so I had to write a helper program in Perl to handle SIGALRM.

alarm() { perl -e ' eval { $SIG{ALRM} = sub { die }; alarm shift; system(@ARGV); }; if ($@) { exit 1 } ' "$@"; }

This alarm function takes two args – seconds for the alarm call and the code to execute. If the code doesn't execute in the given time, the function fails.

Once I had this, I could take my earlier code and just call it through alarm:

for port in {1..65535}; do alarm 1 "echo >/dev/tcp/google.com/$port" && echo "port $port is open" || echo "port $port is closed" done

This is working! Now if bash freezes because of a closed port, alarm 1 will kill the probe in 1 second and the script will move to the next port.

I went ahead and turned this into a proper scan function:

scan() { if [[ -z $1 || -z $2 ]]; then echo "Usage: $0 <host> <port, ports, or port-range>" return fi local host=$1 local ports=() case $2 in *-*) IFS=- read start end <<< "$2" for ((port=start; port <= end; port++)); do ports+=($port) done ;; *,*) IFS=, read -ra ports <<< "$2" ;; *) ports+=($2) ;; esac for port in "${ports[@]}"; do alarm 1 "echo >/dev/tcp/$host/$port" && echo "port $port is open" || echo "port $port is closed" done }

You can run the scan function from your shell. It takes two arguments – the host to scan and a list of ports to scan (such as 22,80,443), or a range of ports to scan (such as 1-1024), or an individual port to scan (such as 80).

Here is what happens when I run scan google.com 78-82 .

$ scan google.com 78-82 port 78 is closed port 79 is closed port 80 is open port 81 is closed port 82 is closed

Similarly you can write an udp port scanner. Just replace /dev/tcp/ with /dev/udp/ .

Update

I was just creating a GNU coreutils cheat sheet and discovered that coreutils include a timeout utility that runs a command with a time limit. By using timeout , I rewrote the tcp port scanner without using a Perl helper program.

$ timeout 1 bash -c "echo >/dev/tcp/$host/$port" && echo "port $port is open" || echo "port $port is closed"

Have fun scanning those ports and see you next time!