Port Scanner

In the previous article, we developed a simple command line program that counted how many times a word occurred in a text file. The article gave us a brief introduction to the env, collections, and fs modules. In this article, we'll take a look at the TcpStream struct from the net module to develop a simple port scanner.

Port Scanning


There are several strategies for scanning ports. The simplest strategy is to try and establish a connection with a host on a particular port. If a connection is made you can assume the port on the host is open. This type of scan is a full connect scan as its comprises of a full TCP handshake.

We'll use the full connect strategy to develop a simple port scanner program, that will take a host IP and a range of ports to scan as command line arguments.

Our program will take three command line arguments (Host, IP, a start and stop port). We can use the args() function from the env module to collect these arguments. Recall from the previous article that the args() function always contains one item, which is the name of the program. Knowing this, we can add some basic validation to ensure that the program won't crash if an incorrect number of arguments were supplied.

Listing 1

use std::env;

fn main() {

    let args : Vec<String> = env::args().collect();

    if args.len() == 4 {

    }else{
        println!("Command requires 3 arguments");
    }
}

Now that we have the arguments, we'll need to convert the start and stop ports to an i32 so that we can use them in a while loop. The String type, has a handy parse() method that can convert the value it holds into another type. When using the parse() method, we need to specify the type we want to convert to using the ‘turbofish’: ::<> syntax. Without it, rust won't know which type to convert to.

The parse() method returns a Result, because it might not be possible to convert to the specified type. For example, trying to parse non numeric characters will result in a error. As such we'll need to handle the parse Results for both the start and stop ports.

Listing 2

let ip = &args[1];

if let Ok(start_port) = &args[2].parse::<i32>(){

    if let Ok(stop_port) = &args[3].parse::<i32>(){
        // To do
    }else{
        println!("Stop port is not a valid number");
    }

}else{
    println!("Start port is not a valid number");
}

With the start and stop ports converted to an integer, we can finally construct a while loop that increments a counter that will be used as the port number. On each iteration, we can use the connect method of the TcpStream struct to try and connect to the host on the current port. Listing 3 below contains the full program code.

Listing 3

use std::env;
use std::net::TcpStream;

fn main() {

    let args : Vec<String> = env::args().collect();

    if args.len() == 4 {

        let ip = &args[1];

        if let Ok(start_port) = &args[2].parse::<i32>(){

            if let Ok(stop_port) = &args[3].parse::<i32>(){
                
                let mut port = *start_port;

                while port <= *stop_port{

                    let result = TcpStream::connect(format!("{}:{}", ip, port));

                    match result{
                        Ok(_stream) => {
                            println!("Port {} open", start_port);
                        },
                        Err(_e) => {}
                    }

                    port += 1;
                }
            }else{
                println!("Can't parse stop port.")
            }

        }else{
            println!("Can't parse start port.")
        }

    }else{
        println!("Command requires 3 arguments");
    }
}

The connect() method takes a single parameter that implements the ToSocketAddrs trait. As String implements this trait, we can use a string literal as an argument. Alternatively we could have used an array of SocketAddr which also implements the ToSocketAddrs trait.

Because connections can fail, the connect() method returns a Result type as seen in the code above. When the connection is successful, the Ok variant will contain the TCP stream that can be used to read and write to.

Our program doesn't need to read or write to the stream, so we'll print a message indicating that the port is open. The Err variant is left with a blank implementation because we don't want to print anything when the connection failed.

HANA DB

Rust

Java

SAP Business One

Node.js