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"

Comments

@kjellski Permalink
August 28, 2012, 13:07

beautiful idea as well as realization Peteris, as always

piyush Permalink
August 28, 2012, 13:08

1 liner:

for i in {1..100}; do nc -v -z www.google.com $i; done

sandra Permalink
August 28, 2012, 15:36

you depend on nc....

meh Permalink
August 28, 2012, 16:37

Debian doesn't support /dev/tcp, but has netcat installed by default.

Martin Permalink
August 28, 2012, 23:35

vs... depending on perl?

pxs Permalink
August 29, 2012, 06:42

OP's script relies on perl

Keith Brown Permalink
August 28, 2012, 15:21

Works but I get a ton of 'connection refused' that I am unable to grep/filter out. I tried:
scan localhost 1-100

Coder.C Permalink
August 28, 2012, 17:28

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"
sean Permalink
August 28, 2012, 21:23

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.

June 12, 2013, 01:14

You can't wait for a PID that isn't a child process, so the PID reuse isn't a problem. There's a lot of other good info on managing and waiting for (multiple) sub-processes here: http://stackoverflow.com/questions/356100/how-to-wait-in-bash-for-several-subprocesses-to-finish-and-return-exit-code-0

Jason Dusek Permalink
August 28, 2012, 19:19

Thanks for posting the pure Bash version, Coder.C!

Simon Stroh Permalink
August 28, 2012, 22:38

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

function portscan() {
  for p in {0..65535};do((bash -c "(>/dev/tcp/$1/$p)" 2> /dev/null && echo open: $p)&read -t0.1;kill $! 2>/dev/null)2>/dev/null;done;
}
August 30, 2012, 17:59

Good job!

I'll reformat this for better viewing:

function portscan() {
  for p in {0..65535}; do
    (
      (
         bash -c "(>/dev/tcp/$1/$p)" 2> /dev/null && echo open: $p
      ) &
      read -t0.1
      kill $! 2>/dev/null
    ) 2>/dev/null
  done
}
Keith Brown Permalink
August 30, 2012, 23:20

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

Alexander Permalink
August 29, 2012, 15:18

Just do this in Python instead.

Warren Rattan Permalink
August 29, 2012, 18:25

Is it possible to capture a UDP datagram and convert it to TCP?

Casey Permalink
August 30, 2012, 09:01

Ok, so why not just use nmap?

August 30, 2012, 17:57

Because I can.

September 07, 2012, 02:21

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 :(

asd Permalink
November 12, 2012, 21:48

ubuntu's not a real operating system, just a non-functional failed prototype, so that is expected.

Charles Permalink
March 07, 2013, 18:36

There are no /dev files; The trickery is a feature built into BASH.

July 13, 2013, 17:13

Thank you on behalf of one more essential article. Where also may perhaps any person acquire to kind of in rank in such a complete way of text ? I suffer a presentation incoming week, and I am on the pay attention on behalf of such in rank.

August 15, 2013, 18:40

This is this very primary measure to i personally visit now. I uncovered numerous entertaining goods inside your blog, specifically its chat. Through the a intense deal of opinions on the content articles, Perhaps I am not motto abandoned having all of the leisure listed now.

October 23, 2013, 15:51

Many appreciation designed for taking this likelihood to tell concerning this, Personally i think strongly regarding it and I turn out manipulate of learning concerning this field of study. When probable, as you advance data, please inform this website with newborn in order. I’ve found it very convenient.

June 26, 2014, 11:26

Maybe that is so true

Leave a new comment

(why do I need your e-mail?)

(Your twitter name, if you have one. (I'm @pkrumins, btw.))

Type the word "computer": (just to make sure you're a human)

Please preview the comment before submitting to make sure it's OK.

Advertisements