Let's write a TCP Port Scanner In Rust

Let's write a TCP Port Scanner In Rust

Rust Dec 9, 2022

Hi, In this post I'm going to write a TCP Port Scanner In the Rust Programming Language. This Cli Program will have Three modes:

  • Single: check a single port.
  • Range: check a range of ports.
  • List: check the list of ports.

Ok, Let's create a project:

cargo new pscan
create a new Rust binary project

Add the Clap crate with derive feature:

cargo add clap --features derive
add clap crate to the rust project

Now, declare the Command Line Interface(CLI) for our program with four arguments. Keep in mind that addr is required and single, range, and list is optional. of course each time the user gonna use just one of them

These are the CLI arguments we need:

struct Cli {
    /// address
    #[arg(short, long)]
    addr: String,
    /// pscan -a address -s 443
    #[arg(short, long)]
    single: Option<u16>,
    /// pscan -a address -r 80-443
    #[arg(short, long)]
    range: Option<String>,
    /// pscan -a address -l 80,443,1080,445
    #[arg(short, long)]
    list: Option<String>
define a Command Line Interface(CLI) in the Rust with Clap

Well done. The next thing to do is parse the arguments, and check optional arguments based on Option Enum which tells us if there is a Some value or None. This is our main function:

fn main() {
    let args = Cli::parse();
    if let Some(single) = args.single {
        scan_ports(&args.addr, vec![single]);
    if let Some(range) = args.range {
        let mut split = range.split("-");
        let start: u16 = match split.next() {
            Some(v) => v.parse().unwrap(),
            None => panic!("range value is not valid, see pscan --help for more info")
        let end: u16 = match split.next() {
            Some(v) => v.parse().unwrap(),
            None => panic!("range value is not valid, see pscan --help for more info")
        let mut list = vec![];
        (start..=end).into_iter().for_each(|x| list.push(x));
        scan_ports(&args.addr, list);
    if let Some(ports) = args.list {
        let mut list = vec![];
        let mut split = ports.split(",");
        loop {
            match split.next() {
                Some(v) => {
                None => break
        scan_ports(&args.addr, list);

So we parse the string values to the u16 values which minimum value fit in this type is 0 and maximum is 65,535 which is the maximum valid port number.

Ok, What's next? If you look at the code above, We have a scan_ports function which accepts reference of String &String and a list of ports Vec<u16>.

fn scan_ports(addr: &String, list: Vec<u16>) {
    let mut handles = vec![];
    for port in list {
        let host = format!("{}:{}", addr, port);
        let handle = std::thread::spawn(move || {
            let socket_addr = SocketAddr::from_str(host.as_str()).unwrap();
            match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(3)) {
                Ok(_) => println!("port {} is OPEN.", port),
                Err(_) => println!("port {} is NOT OPEN", port)

    for handle in handles {
scan the list of ports

The mechanism of the program is simple:

  • Connect to IP:Port with TcpStream.
  • If the connection succeeds, Then the Port is open.
  • If not, the Port is closed.

But what is the handles vector? we spawn threads for each port we have to check. If we just spawn and don't wait until they have done, the program gonna close and we will see nothing! So we have to join on each thread to be able to see the result from each of them.


pscan -s 443       	 // check the port 443
pscan -r 75-82     	 // check the range between two number
pscan -l 80,443,1080   // check three provided ports

This is great! we wrote our program 😃. Good Luck.

By the way, you can watch the youtube video of this Article:


Benyamin Eskandari

Curious, Experienced Android Developer Also in love with Rust, DevOps, and System Programming Trying to read a lot of books and learn a lot of things, I will write posts about my path on this journey.