Building a Simple Port-Scanner with Go Link to heading
These days I wanted to make a new project with go, so I went to the programming challenges page and searched for something, the port scanner caught my attention, so I decided to went for it.
Disclaimer Link to heading
What we’re gonna use build can’t be used against servers which you don’t have a permission to run. Read this to understand more
Initial Version Link to heading
So first we’ll define a function called PortScan
which will accept a server
parameter that will be the server which we’ll scan for it and return a list with the available ports.
A port number is a 16-bit unsigned integer, thus ranging from 0 to 65535, but 0 is reserved and can’t be used, so we know the number of ports that we’ve to scan. Also we’ve to think about how we’re gonna check if the port is available, for this we can use the net
package, he provides us the Dial
function, which we can use to test the connection, but a better one would be the DialTimeout
because it gives us the possibility to set a timeout to the connection. So let’s define our PortScan()
func PortScan(server string) []int {
var available []int
for i := 1; i <= 65535; i++ {
ip := server + ":" + strconv.Itoa(i)
_, err := net.DialTimeout("tcp", ip, time.Duration(300)*time.Millisecond)
if err == nil {
available = append(available, i)
}
}
return available
}
For this code to work we have to import the following packages:
net
: Package for the DialTimeout
function
strconv
: To convert integers to strings and build our server variable
time
: To create a *time.Second parameter
os
: To get arguments from the command line (we’ll see where ahead)
fmt
: To print results to the command line
Now for our main function
func main() {
fmt.Println("Cheking for available ports...")
ports := PortScan(os.Args[1])
fmt.Println("Ports available: " ,ports)
}
Now that we have our program, we want to use it, to use you can just run with go run main.go <server>
where server is the ip address which you’ll want to check. Let’s use time
command to check for the time it takes to run:
┌─[nivaldogmelo@yggdrasil] - [/port-scanner]
└─[$] time go run port-scanner-no-goroutines.go localhost
Checking for available ports...
Ports available: [4000 40031 42987 57621]
real 20.40s
user 8.74s
sys 12.14s
Note that it took a reasonable time, that’s because we check one port at a time, so let’s try to check for multiple ports at the same time
Using Goroutines Link to heading
To increase the speed of our test we could check at multiple ports at the same time. For this we can use go routines, which are similar to threads in languages like Java. If you don’t know what a routine is I recommend reading the Golang Bot tutorial (sections 20–23) to have an idea about what we’re gonna handle.
The first thing we’re gonna do is import the sync
package to deal with the goroutines. Now we’re gonna do some changes at the structure of the code. First we’re gonna make the available
variable a global one, because it will create be manipulated by multiple routines. Second it’s to create a struct to define a job that will be executed
type Job struct {
server string
port int
}
var available []int
var jobs = make(chan Job, 10)
The jobs
variable keeps a buffered channel, which is a channel that will store a buffer of jobs to be executed, we defined the channel to have a length of 10, which means it can hold a maximum of 10 jobs at the same time, any other job will be blocked until some one of the jobs is terminated.
Now we’re gonna define our createWorkerPool()
, which will create our workers to execute the job. Basically it’s here where we define how many concurrent jobs we’ll run at the same time.
To initiate a new goroutine we just need to execute our function with a go
preceding it. We use the wg.Add(1)
to add a new routine to execute our jobs. At the end the wg.Wait()
is required to our main routine to wait for the subsequent routines to be completed before moving to the next step. The go worker(&wg)
needs to use a pointer so that way it will use the same WaitGroup
created by the function, otherwise would create a new one and the tasks would be executed at another goroutines and our original wg group of routines would never be finished
func createWorkerPool(noOfWorkers int) {
var wg sync.WaitGroup
for i := 0; i < noOfWorkers; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
}
Now we need to create a worker()
to execute our job.
func worker(wg *sync.WaitGroup) {
for job := range jobs{
ip := job.server + ":" + strconv.Itoa(job.port)
_, err := net.DialTimeout("tcp", ip, time.Duration(300)*time.Millisecond)
if err == nil {
available = append(available, job.port)
}
}
wg.Done()
}
Now we handle the ip assembly in the worker function, with parameters that we’ll receive from the jobs
buffer, composed by variables with the Job
struct type, which contains a server and a
port. The net.DialTimeout will be executed as in our previous PortScan()
and to finish the function we need to pass a wg.Done()
, indicating the goroutine that the task is completed.
As we introduced these changes, we’ll have to change our original PortScan()
. Now the function will be called with an additional parameter, a channel which will be written once all the jobs
are executed. For each port we’ll build a Job
type variable and send it to our jobs list. At the end we’ll close the jobs channel since all jobs have been assigned and no other job will be
written to the channel. At the end we pass the true
value to the done
channel, to indicate we’ve finished the execution of all our goroutines.
func PortScan(done chan bool, server string) {
for i := 1; i <= 65535; i++ {
job := Job{server, i}
jobs <- job
}
close(jobs)
done <- true
}
To end our implementation, we make the needed change at our main()
func main() {
fmt.Println("Cheking for available ports...")
done := make(chan bool)
go PortScan(done, os.Args[1])
noOfWorkers := 10
createWorkerPool(noOfWorkers)
<-done
fmt.Println("Ports available: " , available)
}
First we create our done
channel, then we create a goroutine to run our PortScan
getting the parameter that we’ll pass at the command line. Then we set a number of workers and
create a worker pool of this size, for the sake of this demo we’ll go with 100 workers. Then we wait for the done
channel to return a true
value. So we only need to need to print
the available ports.
Ok so we’ve done a few changes on our code, but what have we achieved with this, so let’s use our time
command to measure the performance:
┌─[nivaldogmelo@yggdrasil] - [/port-scanner]
└─[$] time go run port-scanner-goroutines.go localhost
Checking for available ports...
Ports available: [4000 40031 42987 57621]
real 4.37s
user 4.93s
sys 5.21s
Now we’ve reached a smaller time
Final considerations Link to heading
When hiting a remote server be careful with the number of workers used, because some routers limit the number of concurrent threads, so some ports will be skipped
And that’s it, i hope you guys enjoyed, if you have any questions you can send me an email or reach me through any of my social media accounts