Tuesday, February 15, 2011

Port Scanner

For fun, I thought I'd show how to build a simple port scanner using Factor

We start by listing vocabularies that we will be using and a namespace for our work:

USING: continuations formatting kernel math.ranges
io.encodings.binary io.sockets make sequences ;

IN: port-scan

We need to decide how to check for an open port. In this case, we are going to attempt to establish a TCP connection. If it succeeds, then the port is open, otherwise it will throw an error connecting and we will assume the port was not open.

: open-port? ( host port -- ? )
    <inet> [ binary [ t ] with-client ] [ 2drop f ] recover ;
Note: we should be setting a connection timeout, so that we do not let a connection attempt last forever. However, I'm not quite sure how to do that -- the documentation for io.sockets and io.timeouts didn't make it obvious.

Next, we will make a word that returns an array of all open ports (checking ports from 1 to 1024). We use the make vocabulary to build the sequence dynamically.

: open-ports ( host -- seq )
    1024 [1,b] [
        [ 2dup open-port? [ , ] [ drop ] if ] each drop
    ] { } make ;

Finally, we can make a word that provides some visual output back to the user:

: scan-ports ( host -- )
    [ "Scanning %s...\n" printf ]
    [ open-ports [ "%d is open\n" printf ] each ]
    bi ;

Using this on my laptop returns the following:

( scratchpad ) "127.0.0.1" scan-ports
Scanning 127.0.0.1...
631 is open

It is quite simple and functional as is. However, some obvious improvements could be made:

  • adding the connection timeout as mentioned above
  • providing the output of scan-ports to the user as open ports are found
  • using the concurrent combinators to test ports in parallel
  • using a list of port numbers to identify services that might be on open ports

The code for this is on my Github.

2 comments:

kobi said...

well, this works but it's pretty slow. what is wrong here?

USING: calendar concurrency.combinators concurrency.semaphores
continuations fry io io.encodings.binary io.sockets io.timeouts
kernel math.parser sequences shuffle ;
IN: jbq-scanner


: open-port? ( host port -- ? ) [ binary [ t ] with-client ] [ 2drop f ] recover ;

: peach ( seq quot #limit -- )
swap [ ] dip '[ _ _ with-semaphore ] parallel-each ;

: scanit ( host range -- )
[ tuck
20 milliseconds [ open-port? ] with-timeout*
[ number>string "port " " is opened." surround print ] [ number>string print ] if ] with 8 peach ;

! "127.0.0.1" 1024 [1,b] scanit

kobi said...

oops, there was a lt semaphore gt in the peach word, between "swap [" and "] dip".