Follow me on Twitter for my latest adventures!
I just had this quick idea to write a tcp port scanner in bash. Bash supports the 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.
So 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 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) to interrupt bash. Bash doesn't have a built-in alarm function, so I had to write my own using Perl:
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, the 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
It turns out GNU's coreutils include timeout utility that runs a command with a time limit. Using timeout we can rewrite the tcp proxy without using Perl for SIGALRM:
$ timeout 1 bash -c "echo >/dev/tcp/$host/$port" &&
echo "port $port is open" ||
echo "port $port is closed"


Facebook
Plurk
more
GitHub
LinkedIn
FriendFeed
Google Plus
Amazon wish list
Comments
beautiful idea as well as realization Peteris, as always
1 liner:
for i in {1..100}; do nc -v -z www.google.com $i; done
you depend on nc....
Debian doesn't support /dev/tcp, but has netcat installed by default.
vs... depending on perl?
OP's script relies on perl
Works but I get a ton of 'connection refused' that I am unable to grep/filter out. I tried:
scan localhost 1-100
This is how to write alarm in bash:
function alarm() { timeout=$1; shift; bash -c "$@" & pid=$! { sleep $timeout kill $pid 2> /dev/null } & wait $pid 2> /dev/null return $? } alarm 1 "echo >/dev/tcp/google.com/230" && echo "Y" || echo "N" //this prints 'N' in ~1s alarm 60 "echo >/dev/tcp/google.com/80" && echo "Y" || echo "N" //this still returns almost immediately and prints "Y"backgrounding a job to sleep/kill a pid isn't necessarily a great idea imho. here, if the port in question is open then then you will pass the wait relatively quickly, but you still have a timer set to kill the pid. A short timer probably won't cause an issue, but the longer you wait the more likely it becomes that the kernel will assign that pid to a new process that you probably don't want killed. especially if you are launching two new pids for each possible port.
Thanks for posting the pure Bash version, Coder.C!
Here's a pure bash version I came up with a while ago, abusing the timeout in read to wait less than a second while still only using bash builtins :)
Good job!
I'll reformat this for better viewing:
Please explain the use of the '&' outside of the subshell '( )' as opposed to within the subshell in the context of being within a script:
(
bash -c "(>/dev/tcp/$1/$p)" 2> /dev/null && echo open: $p
) &
instead of
(
bash -c "(>/dev/tcp/$1/$p)" 2> /dev/null && echo open: $p
& )
I have seen both on the web but never have seen an explanation of the difference.
Thanks
Just do this in Python instead.
Is it possible to capture a UDP datagram and convert it to TCP?
Ok, so why not just use nmap?
Because I can.
Any idea when /dev/tcp came in? Or what kernel options might be required to make it work?
I'm running 2.6.32 (Ubuntu Lucid), Bash 4.1.5 and the tcp & udp dev files are not present :(
ubuntu's not a real operating system, just a non-functional failed prototype, so that is expected.
There are no /dev files; The trickery is a feature built into BASH.
Leave a new comment