Apr 2009

Port Check Program (in C) Part 1

Many programs exist that have the need to check the status of a port for one reason or another. In the least simply pre-checking for an open port can save a great deal of time for a program (especially if it relies upon default timing when doing the real connection). Recently I had the need to write a sort of pre-check program that would see if a port was open and could be accessed over the network. This series examines the rapid prototyping I ended up doing to get the program from a to z. It is designed for those who simply need or want to know how to do this sort of thing. This particular example is done in C, however, there might be follow on texts that tackle the task in other languages.

Design Goal

Before going any further it is time to layout the specification for what this program should eventually be able to do. Bear in mind that the code that performs the actual work will eventually need to be something that can easily be copied and included in another program. So for this program there are two primary goals:

  1. Create a port check program that accepts two arguments, the port number and IP address or hostname.
  2. Write it in such a way that the function which performs the check can easily be ported into an existing program.

Item two implies that eventually everything except getting the port number and IP address will need to reside in a module.

Overall Include Components

The following include files are used from beginning to end of the series. Initially they will not all be used of course but fronting them all now allows the focus to center on the code and not the buttress bits:

#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

Time for Prototype 1

The requirements for prototype one are real simple:

Check a single port on a single system primitively (that is least code needed).

For simplicity the port and IP address are passed in using arguments without switches for now. First a look at the beginning of the main() routine:

int main(int argc, char **argv)
{
        u_short port; /* user specified port number */
    char addr[1023]; /* address */
        struct sockaddr_in address; /* address structures */
        struct hostent *host_info; /* host info structure */
        short int sock = -1; /* the socket descriptor */

Note that even though for now the address and not hostname is being used the char addr[1024] variable is getting a full 1024 to be useful later on when resolution is added. With all of that setup it is time to grab the values using argv[1] as the port and argv[2] as the address:

        port = atoi(argv[1]);
    addr = strncpy(addr, argv[2], 1023);
        bzero((char *)&address, sizeof(address)); /* init addr struct */
        address.sin_addr.s_addr = inet_addr(addr); /* assign the address */
        address.sin_port = htons(port); /* translate port int to netport num */

Now is the time for the scan, in this initial prototype just a plain old connect and error out of the host cannot be reached:

        sock = socket(AF_INET, SOCK_STREAM, 0);
        if(connect(sock,(struct sockaddr *)&address,sizeof(address)) == 0)
                printf("%i is open on %s\n", port, argv[2]);

        if (errno == 113) fprintf(stderr, "F*^k - no route to host\n");

        close(sock);

        return 0;
}

The program requires no additional libs so it can easily be compiled and tried out, here is some sample output:

        cc portcheck.c -o portcheck
        ./portcheck 22 192.168.1.2
        22 is open on 192.168.1.2

There are a lot of issues with the first fast prototype. For instance there is no input validation, which will cause segfaults if non IP address data is entered in as the host - that particular issue can be addressed by adding hostname resolution later on. First a look at the full source before addressing some of the issues:

#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    u_short port;               /* user specified port number */
    char addr[1023];            /* the address */
    struct sockaddr_in address; /* address structures */
    struct hostent *host_info;  /* host info structure */
    short int sock = -1;        /* the socket descriptor */

    port = atoi(argv[1]);
    addr = strncpy(addr, argv[2], 1023);
    bzero((char *)&address, sizeof(address)); /* init addr struct */
    address.sin_addr.s_addr = inet_addr(addr); /* assign the address */
    address.sin_port = htons(port);               /* translate int2port num */

        /*
         * Three simple steps:
         *     1. Open the master socket locally
         *     2. Try to connect to hostbyport, if it works
     *        print the successful message.
         *     3. If no route then complain with vulgarity 
     *        (it is just a rapid prototype after all)
         * Otherwise do nothing.
     */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if(connect(sock,(struct sockaddr *)&address,sizeof(address)) == 0)
        printf("%i is open on %s\n", port, argv[2]);

    if (errno == 113) fprintf(stderr, "F*^k - no route to host\n");

    close(sock);

    return 0;
}

Trussing and Additions

Right off the bat there exist several problems with the rapid prototyped version of the program; which is fine - it was designed to show the core function of the program will work - it just needs fleshed out:

  • No real input validation.
  • Does not check the sock() status.
  • Only managing one real error number and not very well at that.
  • The nature of connect() could cause long timeouts in certain situations.

In addition to the problems there are some missing features. First the host name capability which was in the original design goal does not yet exist, second, a usage message for the input validation is needed for the actual user side of the program. It is time to formalize the TODO list for the remainder of the program then a look at the next iteration:

  • A usage message.
  • Full Input validation.
  • Hostname resolution.
  • Check on the status of the master socket.
  • A real pre-flight check of the host to avoid long timeouts.
  • Attempt to identify and return data about all types of errors related to the actual connection.

Looking at the TODO list a thought must be kept: anything except getting the port and address must be put into a module of some sort - with that in mind it is best to break up where these items should go even though in the next iteration the module might not even exist yet:

In main.c

  • Usage message
  • Input validation.
  • Pass port and host/or IP to actual check routine....

In portcheck.c

  • Hostname resolution.
  • Check on the status of the master socket.
  • A real pre-flight check of the host to avoid long timeouts.
  • Attempt to identify and return data about all types of errors related to the actual connection.

There is another consideration - how to handle the include files. This really depends, the simplest way would be to create a header file for both but if the eventual module is to be used that is not a good idea. The smallest amount of work is to later figure out which headers are needed for which side of the program and to create a header file for the module.

What's Next?

There are two directions the program can take from this point:

  1. Break up the functionality now then add in the needed pieces.
  2. Code the current single source file version until all goals are met except the module; then create the module.

The next text will use approach two because the program itself is not going to be too large and managing it in one file until all of the outstanding functionality issues are resolved is simpler. Often what happens however is the project remains in one file until it becomes obvious that it is time to split it out for managability.

next