06 February 2010

This is an anemic clone of the famous hacker tool nmap. It takes advantage of F# asynchronous workflows to parallelize the network and port scanning. It is notable that without any low level TCP trickery this little program achieves performance comparable to the original nmap.

open System
open System.Net
open System.Net.Sockets
open System.Net.NetworkInformation

let parallelize input f = input |> Seq.map (f)  |> Async.Parallel

let is_port_open (ip:IPAddress) (port:int)=
    try
       let ipe = new IPEndPoint(ip, port)
       use tempSocket = 
            new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
       tempSocket.Connect(ipe);
       tempSocket.Connected
    with
       |_ -> false

let inline is_port_open_async (ip:IPAddress) (port:int) = 
    async { return (port, is_port_open ip port) }

let scan_ports ports ip = parallelize ports (is_port_open_async ip)

let inline q (x:uint32[]) = 
    (x.[0] <<< 24) 
    ||| (x.[1] <<< 16) 
    ||| (x.[2] <<< 8) 
    ||| x.[3]

let getip z =
    ((z &&& uint32(0xFF000000))>>>24),
    ((z &&& uint32(0x00FF0000))>>>16),
    ((z &&& uint32(0x0000FF00))>>> 8),
    ( z &&& uint32(0x000000FF))

let _generate n m =
    seq {
          let j = ref n
          while (!j<m) do
             let (a,b,c,d) = getip !j;
             yield (new IPAddress([| byte(a);byte(b);byte(c);byte(d) |]));
             j := !j + uint32(1)
        }

let generate (ip:IPAddress) bits =
    let mask = ~~~(UInt32.MaxValue >>> bits)
    let ipBytes = ip.GetAddressBytes();
    let maskBytes = Array.rev(BitConverter.GetBytes(mask));
    let firstIPBytes = Array.map2 (fun x y -> uint32(x &&& y)) ipBytes maskBytes
    let lastIPBytes = Array.map2 (fun x y -> uint32(x ||| ~~~y)) ipBytes maskBytes
    let n = q (firstIPBytes)
    let m = q (lastIPBytes)
    _generate n m

let pingSender = new Ping()

let is_host_up (t:int) (ip:IPAddress)  =
    let reply = pingSender.Send(ip,t)
    reply.Status = IPStatus.Success

let inline is_host_up_async (t:int) (ip:IPAddress)  
    =  async { return (ip, is_host_up t ip) }

let scan_hosts ip bits = 
    parallelize (generate ip bits) (is_host_up_async 1)

let parallel_port_scanner ports host =
    async {
        let! ports = scan_ports ports host
        for (port,_open) in ports do
        if _open then printf "port\t%d\tis open\n" port
    }

let parallel_host_scanner ip bits f =
    async  {
        let! hosts = scan_hosts ip bits
        for (host,up) in hosts do
            if up then do! f host
 }

let mutable ip = IPAddress.Parse("192.168.13.146");
let mutable bits = 24;

let args = System.Environment.GetCommandLineArgs()

let all_ports     = {1 .. 65535}
let default_ports = {1 .. 1024}

let mutable port_scanning_enabled = false
let mutable range_scanning_enabled = false
let mutable ports  = default_ports

let set_port_params param_index =
    port_scanning_enabled <- true
    if Array.length  args > param_index then
        if args.[param_index].Equals("all") then ports <- all_ports
        else failwith "bad input"

let set_params _ =
    if Array.length  args < 2 then failwith "bad input"
    else
        ip <- IPAddress.Parse(args.[1])
        if Array.length args > 2 then
            if args.[2].Equals("-p") then set_port_params 3
            else
                bits <- Int32.Parse(args.[2])
                range_scanning_enabled <- true
                if Array.length args > 3 && args.[3].Equals("-p") 
                then set_port_params 4

try
    set_params()
with
    |_  ->  printf "usage: netdiscover ipaddress [bits] [-p [all]]\n"
            exit(1);

let timer = new System.Diagnostics.Stopwatch()
timer.Start();

Async.RunSynchronously <|
match range_scanning_enabled, port_scanning_enabled with
|true,true  ->  parallel_host_scanner ip bits 
                  (fun x -> async { printf "%A is up\n" x; 
                                    do! parallel_port_scanner ports x })
|true,false ->  parallel_host_scanner ip bits 
                  (fun x -> async { printf "%A is up\n" x } )
|_          ->  parallel_port_scanner ports ip

printf  "took %dms\n" timer.ElapsedMilliseconds;

For example to scan the network 10.0.1.1/24 including a port scan you would go like this:

sh-3.2# mono discover.exe 10.0.1.1 24 -p
10.0.1.1 is up
port	53	is open
port	554	is open
10.0.1.200 is up
port	53	is open
10.0.1.201 is up
10.0.1.205 is up
port	88	is open
port	548	is open
took 37742ms 



blog comments powered by Disqus

about