I’m always a very curious person, especially when it comes to IT and tech. When I ask myself how does something work, I want to figure out the solution. The same happened to me when I asked myself “how do port scanners work”? There was so much magic inside, but as always, when you get the idea behind it, it is anything but magic :-)

Here I want to describe the very simple concept of a port scanner I’ve written in Go. The whole program is less than 100 lines of code. The whole idea of the port scanner is trying to establish a network connection on a particular port. If the connection is established, the port is open and you can close the connection immediately. When the port is not open, the connection won’t establish and the connection attempt would just timeout. Yep, that’s already all of the magic ;-)

In Go, there is the net.DialTimeout function and the code may look like this:

func scan() {
    conn, err := net.DialTimeout("tcp", "localhost:80", 100 * time.Millisecond)
    if err != nil {
        if *listClosedFlag == true {
            log.Println(err)
        }
    } else {
        conn.Close()
        fmt.Printf("open %d\n", port)
    }
    wg.Done()
}

The first argument describes the protocol, which is set to TCP, the second argument is the host machine to scan and the port, the third and last argument is the time to wait for the connection to establish. If the connection doesn’t establish within this duration, an error will be returned and the port is considered to be closed.
The timeout (and possibly other errors) are handled by the if err != nil condition. I’ve used a command line flag to check if closed ports should be printed to the console too (but this is not the default behaviour, it would be annoying if you scanned a wide range of ports).
When there was no error, and the connection is established, the code in the else block will be executed, which closes the just established connection and prints the open port to the output. Let’s ignore the wg.Done() at the end for a moment.

Using variables for the host and port, you can run this code in a loop, ranging over a certain port range, maybe 1000-1500. With Go, it is very easy to speed this up using goroutines, doing several scans concurrently. Using the scan function from above, the main method can use a loop and call this method as shown below.
Inside the loop, again there is the wg variable, which is a sync.WaitGroup. When we start a new goroutine to scan the port, we first increment the internal counter of the WaitGroup with .Add(1). The call to wg.Wait() at the end of the code block will wait until wg is zero again. If this part would be missing, the main thread of the program would not wait for the goroutine to finish, and exit immediately.
Of course, we have to decrement the counter again, when the goroutine is finished, which is done with wg.Done() (at the end of the code block above).

func main()
    for port := startPort; port <= endPort; port++ {
        wg.Add(1)
        go scan(port)
    }

    wg.Wait()
}

Based on this concepts and code snippets, we have a very basic port scanner. You can find the whole source-code on Github. I’m using command line flags to manually set the host, ports and various other things. Here is an exemplary output:

$ ./portscan -host=google.com
open 80
scan finished

(This post was written in March 2018. The project started in March 2015 and the date of this post was set accordingly, to have a correct chronological order of all projects.)