Write Port Scanner In C

Parampreet Rai
10 min readMay 7, 2021
Photo by luis gomes from Pexels

Hi everyone, I hope you are doing great. I’m writing my first medium article on port scanner in C. At time of writing this article I found lack of C Port Scanner tutorials and some tutorial exist that don’t work or work very slow. Slow means scan 1 port in 5 seconds :( . So I will show you how we can create a fast port scanner. As C itself is a fast language, port scanner can be created in python too but who cares slow port scanner.

Prerequisites:

  • Basic Understanding with C and socket handling
  • Some knowledge with multi threading
  • Basic Networking
  • That’s all

I am not strictly enforcing these prerequisites so little deficiency is accepted.

What are ports?

According to Wikipedia:

In computer networking, a port is a communication endpoint. At the software level, within an operating system, a port is a logical construct that identifies a specific process or a type of network service. A port is identified for each transport protocol and address combination by a 16-bit unsigned number, known as the port number.

This is a professional definition somewhat difficult to understand. Firstly look at the back of the CPU or side of laptop. If you saw then you have surely noticed some plugs and and you know that each plug is used for separate task. These plugs are used to connect other devices to our PC.

So now I can talk about network ports in sense of those plugs. Network ports are used for same purpose that is to communicate with other computers and exchange data through network. Each network service runs on some port like HTTP on 80 and HTTPS on 443, SMB on 445 etc. But there are some difference between hardware plugs and network ports. First you can’t see them and each port can run any service but that’s not a good practice, like HTTP can also run on 445. There are 65535 total ports ranging from 0 to 65535. If any service is running on any port then that port is considered open and others are closed ports. Filtered ports are covered by some firewall or any other obstacle that is blocking our access to that port.

What is port scanner?

Port scanner is a program that can scan all network ports and checks for open, close or filtered ports. Normally one can use advance scanners like nmap or rustscan but writing own port scanner gives deep understanding about things going on behind the scenes. If you are here I appreciate you curiosity.

So that’s all with introduction. Lets dive into coding section.

All code is available at https://github.com/ParampreetR/port_scanner_quieso

Header Files

I created 2 header files. One just contain declarations and other one for parsing args.

Declarations in quieso.h:

/* This file carries all functions declaration and structure definations */
struct thread_opts {
char host[INET_ADDRSTRLEN];
unsigned int port, timeout, thread_id, start, end;
};

int quieso_error(const char *s, int sock);

void *worker(void *thread_args);

int scanner(const char * host, unsigned int *port, unsigned int timeout, unsigned int *start, unsigned int *end);

Argument Parsing logic in arg_parse.h

/* This file carries all functions and other stuff responsible for parsing arguments */

#include <argp.h>
#include <stdlib.h>
#include <error.h>

struct arguments
{
char host[INET_ADDRSTRLEN]; /* host-name or IP to scan */
int timeout; /* timeout for each port */
int version; /* a flag for '-v' to check version */
char file_to_output[30]; /* output to file */
};


struct argp_option options[] = {
{"host", 'h', "HOST", 0, "Target host to scan" },
{"timeout", 't', "SECONDS", 0, "Speed of scanning aka seconds of timeout." },
{"output", 'o', "FILE", 0, "Output to FILE instead of standard output" },
{"version", 'v', 0, 0, "Print version and exit"},
{ 0 }
};

char doc[] =
"quieso is a simple port scanner that is intended to show some logic behind port scanning.\
\vThis is still in development, more things will be added soon";

/* A description of the arguments we accept. */
char args_doc[] = "";

/* Keys for options without short-options. */
#define OPT_ABORT 1 /* –abort */


error_t parse_opt (int key, char *arg, struct argp_state *state)
{
struct arguments *arguments = state->input;

switch (key)
{
case 'h':
strncpy(arguments->host, arg, (size_t) INET_ADDRSTRLEN);
break;
case 't':
//printf("%s", arg);
arguments->timeout = atoi(arg);
break;
case 'o':
strncpy(arguments->file_to_output, arg, 30);
break;
case 'v':
arguments->version = 1;
break;

default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}

struct argp argp = { options, parse_opt, args_doc, doc };


struct arguments *parse_args(int argc, char *argv[]) {
static struct arguments args;
argp_parse (&argp, argc, argv, 0, 0, &args);
return &args;
}

Coding Port Scanner

I will be calling this port scanner Quieso. We will also be using argp.h to parse args passed through command line but will not include it in tutorial that may increase difficulty level of tutorial.

Lets start!

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/fcntl.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include "arg_parse.h"
#include "quieso.h"
-----SNIP-----

Import everything we need in creating Quieso. You don’t need know what all header files are doing accept few. Pthread is used to create multiple threads so fast port scanning. Stdio is a needed for Input output functionality like using scanf and printf. Sys/socket is needed for doing anything with low level sockets. String is used to use some strings functionality like copying string buffer from one location to other. Unistd let us use OS system calls. Errno is used to get errno variable in program. After calling any socketfunction we can inspect value of this variable and predict type of error. Rest are not as important as we will be using some structure or function that is defined in those header files.

As I said, I will not be covering parsing args section.

So I created a header file called args_parse.h. Just call parse_args() function with argc and argv and you will get arranged structure of arguments provided by user. Just take a look at structure definition whose instance will be returned by parse_args(). Lets code main function.

int main(int argc, char *argv[])
{

struct arguments *user_args;

user_args = parse_args(argc, argv);

// If user specified -v then print version and exit
if(user_args->version) {
printf("scanner v0.1");
exit(0);
}

// If user do not specified host then print error and exit
if(strlen(user_args->host) == 0) {
quieso_error("[-] Please specify host\n", 1);
}
-----SNIP-----
}

Lets start main function. We called parse_args() and got back a structure of filled arguments. We checked if user specified -v if so then we print version and exit. Then we checked host if user don’t specified host then print error and exit. quieso_error() is nothing but a simple function that will print error and exit.

-----SNIP-----
struct hostent *target;
// Resolve hostname
target = gethostbyname(user_args->host);

// Clear out space
bzero(user_args->host, sizeof(user_args->host));
// Copy to struct with typecasting
strcpy(user_args->host , inet_ntoa(*( (struct in_addr *)target->h_addr_list[0] )));
printf("Scanning %s\n", user_args->host);
-----SNIP-----

Above code is self explanatory. We called gethostbyname() to resolve host and got pointer to structure of type hostent. We then cleared out space and copy resolved host back to user_arg struct.

-----SNIP-----
int thread_id;
pthread_t threads[MAX_THREADS];
struct thread_opts opts[MAX_THREADS];
int unsigned port_scan = 1;

for(thread_id = 0; thread_id < MAX_THREADS; thread_id++) {
opts[thread_id].start = 0;
opts[thread_id].end = 0;
opts[thread_id].port = 0;
opts[thread_id].timeout = user_args->timeout;
opts[thread_id].thread_id = thread_id; strncpy(opts[thread_id].host, user_args->host, (size_t) INET_ADDRSTRLEN); /* Set target host */

/* Create threads */
if (pthread_create(&threads[thread_id], NULL, worker, (void *) &opts[thread_id])) {
#ifdef DEBUGING
perror("pthread_create() error"); /* Print error in thread creation */
#endif
return EXIT_FAILURE;
}
}

thread_id = 0;
printf("--> Created %d threads.\n", MAX_THREADS);

// Loop till over all ports are scanned
while(port_scan < 65535) {
/* Iterate through all threads */
for(int i = 0; i < MAX_THREADS; i++) {
if(opts[i].port == 0) {
opts[i].port = port_scan;
port_scan++;
opts[i].start = 1;
}
}
}

This portion looks scary but not really, lets take a look at it one by one. A for loop to create threads and pass each thread pointer to separate arguments. pthread_create() will create new thread and insert its handle in to element of array we passed as address. If you need deep details about pthread_create() then its here. If you are wondering what is worker passed as third argument in pthread_create() its name of the function that a thread will execute when created. We will soon be creating this function. And last argument of pthread_create() is a pointer to a structure that contain all arguments we are passing to function and we are typecasting them as void * so each argument will be void * in declaration of worker function.

As a reminder we had not passed whole structure but only a pointer to structure that contains arguments. So if we modify anything that structure other thread will also be affected.

And while loop to iterate till all ports scanned and internal for loop to iterate thorough threads and if iterated thread’s passed argument is 0 then we will give him some other port to scan. But how it will be 0? don’t worry we will soon code worker() function. When passed port is scanned by thread, thread will print the port if its open and set argument to 0 so main process can give it another port to process.

void *worker(void *thread_opts)	// Be careful, it is void *.
{
// Create pointer to struct which carries all options passed by main
struct thread_opts *opts;

// Now opt will point to thread_opt passed by main
opts = thread_opts;

// Call a core function will do entire work of scanning
scanner(opts->host, &opts->port, opts->timeout, &opts->start, &opts->end);

// Exit current thread
pthread_exit(NULL);
}

This function will run by each thread. In this function, we initialized struct to the parameter got from main function. After we call another function scanner() with host, port, timeout, start, end. Port, start and end are pointers so if these parameters are changed in main function that will also change parameters passed to scanner() function. So lets code scanner() which is a meat part of scanner. Lets give it a heading for those people who are blindly scrolling.

Meat Part

Now we will be coding scanner function which will scan a port and change port variable by dereferencing to zero and wait for main function to set port variable to another port to scan. Once main function changed port variable to next port to scan, thread will scan that port too.

int scanner(const char * host, unsigned int *port, unsigned int timeout, unsigned int *start, unsigned int *end)
{
// This struct has all information which is required to connect to target
struct sockaddr_in address, bind_addr;
// This struct is used in select(). It contains timeout information.
struct timeval tv;
fd_set write_fds;
socklen_t so_error_len;
// The socket descriptor, error status and yes.
int sd, so_error = 1, yes = 1;

int write_permission;

// Wait until start flag is not enabled by main process
while(!*start) {
sleep(2); /* Wait for 2 seconds */
}
-----SNIP-----

Nothing special, declared variables and structures required further. While loop to wait until main function changes start variable to 1.

Next comes the final part of the scanner.

-----SNIP-----
// Process until end flag is not set by main process
while(!*end) {
// Wait for 2 seconds till port is 0
while(*port == 0) {
sleep(2);
}

// Fill sockaddr_in struct.
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(host);
/* inet_addr() converts string of host IP to int */
address.sin_port = htons(*port);
/* htons() returns int with data set as big endian. Most computers follow little endian and network devices only know big endian. */

// Seconds to timeout
tv.tv_sec = timeout;
// Microseconds to timeout
tv.tv_usec = 0;

FD_ZERO(&write_fds);

so_error_len = sizeof(so_error);

// Create a socket
if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return quieso_error("socket() An error has occurred", 0);


// Set port as reuseable. So we may not use up all avilable ports.
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
return quieso_error("setsockopt() An error has occured", 0);

// Make our socket non-blocking. Program will not stop until connection is made.
if(fcntl(sd, F_SETFL, O_NONBLOCK) == -1)
return quieso_error("fcntl() caused error", 1);;

// Now connect() function will always returns -1 as we are in non-blocking flag.
if (connect(sd, (struct sockaddr *) &address, sizeof(address)) == -1) {

switch (errno) {
case EWOULDBLOCK: /* Processing going on */
case EINPROGRESS: /* Connection in progress */
break;

default: /* We want to give error on every other case */
return quieso_error("connect() An error has occurred", sd);
}
}

FD_SET(sd, &write_fds);

// Waiting for time when we can write on socket or timeout occurs
if((write_permission = select(sd + 1, NULL, &write_fds, NULL, &tv)) == -1)
return quieso_error("select() An error has occurred", sd);

// If we got write permission
if(write_permission)
if(getsockopt(sd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len) != -1) {
if(so_error == 0)
printf("%d OPEN\n", *port);
}

// Set port to 0. So we do not process one port again and again
*port = 0;
}

I apologize, this code is not properly arranged as I can’t copy tabs. Medium plz do something about this. So lets analyse above code.

First while loop until end flag is not set. Inner while loop to wait for main function to give us port to scan. We filled sockaddr_t type structure which is required to connect remote host. Then we also filled timeval type structure which is required in select() function and it contains timeout. Next we created socket and set flag as reuseaddr so we don’t block any port for long time. fcntl() sets socket to non-blocking, our code will not stuck at connect() when connection is in progress. With all error handing, select() function which will wait for connection progress to complete so we can get write permission on socket or if we get timeout then port is closed .We are handing both cases but if connection is successful means we got write permission then get check socket status if its really connected or not. If its connected then finally we can conclude that port is open.

Quieso in action

#> gcc quieso.c -o quieso_x64#> ./quieso_x64 -h 1.1.1.1 -t 3 Scanning 1.1.1.1 
--> Created 500 threads.
53 OPEN
80 OPEN
443 OPEN
853 OPEN

Github repo: https://github.com/ParampreetR/port_scanner_quieso

Thanks for reading. Feel free to fork or contribute if you can improve it in some way.

--

--

Parampreet Rai

Tech enthusiast, Full Stack Web Development, Cyber Security