Nmap Go implementation - TCP port scan
Tags: go, programming, security, networking
Published: 2021-12-17 15:06:02

I remember when I first started learning about computer networks. It was from the excellent [Computer Networks, 5th Edition, Andrew S. Tanenbaum](https://www.pearson.com/us/higher-education/program/Tanenbaum-Computer-Networks-5th-Edition/PGM270019.html) book. Book is accompanied by the University of Washington video course taught by [David Wetherall](https://research.google/people/105021/). Video materials are available at: 👉 **[Tanenbaum, Wetherall Computer Networks 5e, Video Notes](https://media.pearsoncmg.com/ph/streaming/esm/tanenbaum5e_videonotes/tanenbaum_videoNotes.html).** 👈
Be sure to check them out, they are great!!

After reading a bunch of posts on dev.to on [Nmap](https://nmap.org/) topic, e.g.: 
- [ Getting Started with Nmap for Pentesters ](https://dev.to/leading-edje/getting-started-with-nmap-for-pentesters-59af)
- [Nmap - Introduction (Part 1) ](https://dev.to/kavishgour/nmap-introduction-part-1-48dn)
- [Nmap](https://dev.to/vishwasnarayan5/nmap-6jl)

I have decided to refresh my knowledge on computer networks by re-implementing Nmaps` basic functionalities in Go.

This is first post in the series of posts I'll use to record that journey.

**Table of Contents:**
1. [n2map (noob network mapper)](#)
  1. [TCP connect scan technique](#)
2. [n2map requirements](#)
3. [Implementation](#)
  1. [Get target machine IP](#s1)
  2. [Connect to the target machine (hard-coded port)](#s2)
  3. [Connect to the target machine (user provided port)](#s3)
  4. [Connect to the target machine (multiple ports)](#s4)
  5. [Review](#s5)
  6. [Make it go brrrr (parallel)](#s6)
5. [Conclusion](#conclusion)

## n2map (noob network mapper)

This project is called **n2map (noob network mapper)** and it will implement a subset of Nmaps` functionalities. That's port scanning functionality for now.

If we take a look at the Nmap documentation for port scanning we'll see that is supports a dozen or so [port scan techniques](https://nmap.org/book/man-port-scanning-techniques.html):
- TCP SYN scan (half-open scanning, doesn't open a full TCP connection)
- TCP connect scan (full scanning, connect to port on the target machine)
- UDP scans (sends UDP packet to every targeted port)
- [and many more..](https://nmap.org/book/man-port-scanning-techniques.html)

n2map first functionality will add support to perform port scanning for single IP address. It will use a TCP connect scan technique.

### TCP connect scan technique

In detail description of TCP connect scan technique is described in [port scan techniques](https://nmap.org/book/man-port-scanning-techniques.html) part in the Nmap documentation.

For this post, I think it's enough to know that a TCP connect scan technique is a technique that performs a standard TCP three-way handshake to establish the connection, and does the same for closing the connection.

Let's see what packets are sent if connect:
- from `localhost` (`192.168.1.6`) 
- to target machine on the `http://scanme.nmap.org/` (`45.33.32.156`) on port `80`

![TCP open connection handshake](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jwy1uz6uystp5m16rw3q.png)

We see `green` rectangle that represents packets sent to establish the connection. We also see `red` rectangles representing the packets sent to close the connection.
 
In case a connection can't be established, we'll see following packets sent over the network:

![TCP cant open connection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/47v9du7e28mf6cglxc47.png)
 

## n2map requirements

I would like to use n2map the same way I use nmap:
- via terminal
- by providing target machine IP address
- by providing a port range to scan
- by using the TCP connect scan technique
  - this is currently only supported scan technique in n2map so no additional flags are added at the moment

The run command should looks like this: `n2map -p 80-433 127.0.0.1`

## Implementation

### Get target machine IP

First thing we need to have to perform a port scan is a target machine IP address. IP address will be provided as an argument to the `n2map` runtime as follows: `n2map 127.0.0.1`

Following Go code shows how to implement that.

```go
package main

import (
	"flag"
	"fmt"
	"os"
	"time"
)

func main() {
	flag.Parse()
        // IP is provided as an argument at position 0
	if ip := flag.Arg(0); ip != "" {
		dt := time.Now()
		fmt.Printf("Starting n2map v0.1 at %s\n", dt.Format(time.UnixDate))
		fmt.Printf("n2map scan report for %s\n", ip)
		fmt.Printf("PORT\tSTATE\n")
	} else {
		fmt.Println("error : IP address not provided")
		os.Exit(1)
	}
}
```

#### Result
```sh
% go run main.go 127.0.0.1
Starting n2map v0.1 at Fri Dec 17 13:27:55 CET 2021
n2map scan report for 127.0.0.1
PORT    STATE
```

### Connect to the target machine (hard-coded port)

Once we have a target machine IP address we are ready to make a connection. Luckily, Go provides a [`Dial`](https://pkg.go.dev/net#Dial) function that can be used to connect to a target machine. It can be used like this: `Dial("tcp", "198.51.100.1:80")`

Let's update our code to reflect the changes:

```go
		port := 80
		addr := fmt.Sprintf("%s:%d", ip, port)
		_, err := net.Dial("tcp", addr)
		if err == nil {
			fmt.Printf("%s\t%s\t\n", port, "open")
		} else {
			fmt.Printf("%s\t%s\t\n", port, "closed")
		}
```

#### Result
```sh
% go run main.go 127.0.0.1
Starting n2map v0.1 at Fri Dec 17 13:27:55 CET 2021
n2map scan report for 127.0.0.1
PORT    STATE
80      closed  
```

Notice that we have hard-coded `port` value `80`, which means we need to rerun the `n2map` for each port we want to scan. We can do better than that.


### Connect to the target machine (user provided port)

To make it possible to provide port we will introduce a new `-p` flag. 


```go
	func main() {

	var port string
	flag.StringVar(&port, "p", "80", "port to scan")
	flag.Parse()

	if ip := flag.Arg(0); ip != "" {
		dt := time.Now()
		fmt.Printf("Starting n2map v0.1 at %s\n", dt.Format(time.UnixDate))
		fmt.Printf("n2map scan report for %s : [%s]\n", ip, port)
		fmt.Printf("PORT\tSTATE\n")

		addr := fmt.Sprintf("%s:%d", ip, port)
		_, err := net.Dial("tcp", addr)
		if err == nil {
			fmt.Printf("%d\t%s\t\n", port, "open")
		} else {
			fmt.Printf("%d\t%s\t\n", port, "closed")
		}

	} else {
		fmt.Println("error : IP address not provided")
		os.Exit(1)
	}

}
```

Now we are able to use n2map with provided port as:
`n2map -p 80 127.0.0.1`

#### Result

```sh
% go run main.go -p 80 127.0.0.1
Starting n2map v0.1 at Fri Dec 17 13:27:55 CET 2021
n2map scan report for 127.0.0.1 : [80]
PORT    STATE
80      closed 
```

This is still now good enough! We want to be able to scan not only one port per `n2map` run but multiple ports. 

### Connect to the target machine (multiple ports)

To make that possible we are going to extend the existing `port` flag functionality to include a range of ports to scan by:
- extending provided port runtime argument to support port ranges like `80-100` (scan ports >= 80 and <= 100)
- making a `Dial` call for each port in provided range

```go
func main() {

	var portRangeFlag string
	flag.StringVar(&portRangeFlag, "p", "80", "port range to scan")
	flag.Parse()

	if ip := flag.Arg(0); ip != "" {
		dt := time.Now()
		fmt.Printf("Starting n2map v0.1 at %s\n", dt.Format(time.UnixDate))
		fmt.Printf("n2map scan report for %s : [%s]\n", ip, portRangeFlag)
		fmt.Printf("PORT\tSTATE\n")

		portRange := strings.Split(portRangeFlag, "-")
		startPort, _ := strconv.Atoi(portRange[0])
		endPort, _ := strconv.Atoi(portRange[1])

		for port := startPort; port <= endPort; port++ {
			addr := fmt.Sprintf("%s:%d", ip, port)
			_, err := net.Dial("tcp", addr)
			if err == nil {
				fmt.Printf("%d\t%s\t\n", port, "open")
			} else {
				fmt.Printf("%d\t%s\t\n", port, "closed")
			}
		}

	} else {
		fmt.Println("error : IP address not provided")
		os.Exit(1)
	}
}
```

Now we are able to use n2map with provided port as:
`n2map -p 80-81 127.0.0.1`

#### Result
```sh
% go run main.go -p 80-81 127.0.0.1
Starting n2map v0.1 at Fri Dec 17 13:46:36 CET 2021
n2map scan report for 127.0.0.1 : [80-81]
PORT    STATE
80      closed  
81      open  
```

### Review

Nice! If we check the initial requirements:
> I would like to use n2map the same way I use nmap:
- via terminal
- by providing target machine IP address
- by providing a port range to scan
- by using the TCP connect scan technique
  - this is currently only supported scan technique in n2map so no additional flags are added at the moment

we can see that all the requirements are implemented.


### Make it go brrrr (parallel)

Not in the requirement list, but non less important is the time this implementation takes to scan multiple ports, or even all 65535 ports. It's very, very slow.

If you have read any of my previous posts on [Go Channel Patterns](https://dev.to/b0r/series/15904) you should have enough knowledge to make improvements needed to speed up this process.

#### Battle plan for that is:
- scan ports (make TCP connection)
  - create a worker goroutines that will do the `net.Dial` call for specific port
  - create a manager goroutine that will iterate over a `portStart` and `portEnd` range, and send each port to one of the worker goroutines
  - create a channel used to pass data between manager and worker goroutines

- print results
  - make `results` channel used by worker goroutines to pass the results to the manager goroutine
  - use `for-range` loop to iterate over `results` channel and print out the results
  - create a new supervisor goroutine that will close the `results` channel once there are no more ports to process by worker goroutines
  - use `sync.WaitGroup` to tell supervisor when to close the `results` channel

## Conclusion

In this post, TCP connect scan port scanning technique was described (as described by Nmap). In addition, simple implementation was provided.

Readers are encouraged to try to speed the port scanning process up by using one of the Go concurrency primitives (goroutines, channels...).

## Resources:
- Header Photo by Ricardo Esquivel from Pexels