Aug 2009

Port Check Program (in C) Part 3

In part 1 an examination of creating an ultra simple single port single host port check program was done. In the second part of the series the code was broken out between a header file and source file, input validation added, a usage message defined and a Makefile was setup for simple recompiling. In this part of the series one of the items of the TODO list will be tackled: smaller timeout value for connection test by way of a host pre-check. Which leaves the program with two final TODO items for the next installment:

  1. Breaking up the program functionally
  2. Detailed error handling
  3. Hostname resolution (or not)
  4. Any last minute details...

Normally the program should be broken up from the get go, however, it still has not started to become large enough, even from a functional point of view to really need it yet; that will definitely change in this version.

Source So Far

Makefile

CC=cc

all: portcheck

portcheck:
    ${CC} $@_main.c -o $@

clean:
    rm -f a.out portcheck

rebuild: clean portcheck

portcheck.h

#ifndef _PORTCHECK_H
#define _PORTCHECK_H

#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>

#endif

portcheck.c

#include "portcheck.h"

#define USAGE "Usage: portcheck port address"

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

    if (argv[1])
        if (!strcmp(argv[1], "-u")) {
            printf("%s\n", USAGE);
            return 0;
        }

    if (argv[1])
        port = atoi(argv[1]);
    else {
        fprintf(stderr, "No port specified\n");
        fprintf(stderr,"%s\n", USAGE);
        return 1;
    }

    if (argv[2])
        strncpy(addr, argv[2], 1023);
    else {
        fprintf(stderr, "No address specified\n");
        fprintf(stderr,"%s\n", USAGE);
        return 1;
    }

    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 */

    /*
     *     1. Open the master socket locally
     *     2. Connect to hostbyport works? print success
     *     3. No route to host? complain with vulgarity
     */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        fprintf(stderr, "Error: could not assign master socket\n");
        exit (1);
    }
    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;
}

Adding a Timeout

Adding timers is pretty straightforward, all that is needed is the ability to set the timeval structure. In this example only seconds will be modified, however, both seconds and useconds could be modified if so desired. First a decision has to be made - what to set it to? Assuming the program is for use on a LAN 3 seconds (the default for telnet) seems like a good number, however, this can be changed or even made into a command line argument - that is completely up to the programmer. In the example, for now, it will be a #define in the header file:

...
#include <time.h>
#include <unistd.h>

#define DEFAULT_TIMEOUT 3 /* The Default Timeout Value */

#endif _PORTCHECK_H

Next the code to actually leverage the timeout. The timeout should be used for the actual connect() area of the code but the timeval struct needs to be initialized first:

...
    char addr[1023];            /* copy of address from stdin */
    struct timeval timeout;     /* timers data structure */

    if (argv[1])
...

There is little point in setting the timeouts until the program is ready to make a connection, so the setting and using the timeouts comes into play nearly at the end of the file:

...
        timeout.tv_sec = DEFAULT_TIMEOUT; /* Defined in portcheck.h */
        timeout.tv_usec = 0;              /* Set usec to 0 */

    if(connect(sock,(struct sockaddr *)&address,sizeof(address)) == 0)
...

There is a problem here, the connect() call does not support the timeout by itself, instead the return value is used. If the connect() is still in progress then a select() on the connection can be used with the timeout passed as an argument. This requires a precheck to see if the host is up, then an actual port check to follow. In the precheck the following must be done:

  • Configure non-blocking mode for a select
  • One int to catch the connect status
  • One int to catch the select status
  • A file handle for the select call that is API compatible
  • Close the socket
  • Reopen the socket
  • Rerun the connect and check results

In fact what the new code will be doing is running a is the host alive? pre-check. Following are the new variables at the top of the file:

...
        struct timeval timeout;     /* timers data structure */
        fd_set wset;                /* File Handle for select() */
        int res_conn, res_sel;      /* Connect result and select result */
        long arg;                   /* Non-block descriptor */
...

The rest will require a lot of code so the following is the new functional code starting after the port is setup; note that it is heavily commented to explain each of the detailed steps mentioned above:

...
   address.sin_port = htons(port);               /* translate int2port num */

    /*
     *     1. Open the master socket locally
     *     2. Configure non-blocking mode
     *     3. Check connect() status
     *     4. If bad check select() status
     *     5. If all goes well close sock and check port
     *     6. Connect to hostbyport works? print success
     */
        sock = -1;
    sock = socket(AF_INET, SOCK_STREAM, 0);

   /* Setup non-blocking for select first get the descriptor */
   if( (arg = fcntl(sock, F_GETFL, NULL)) < 0) {
        fprintf(stderr,
        "Error fcntl(..., F_GETFL) (%s)\n",
            strerror(errno));
        return 1;
    }

    /* Now setup actual non block mode */
    arg |= O_NONBLOCK;
    if(fcntl(sock, F_SETFL, arg) < 0) {
        fprintf(stderr,
        "Error fcntl(..., F_SETFL)  (%s)\n", strerror(errno));
        return 1;
    }
        /* Run a connect and store the result */
    res_conn = connect(sock,(struct sockaddr *)&address,sizeof(address));

        /* If were still going, attach a timeout to it, open a select descriptor
       then do a basic is this host alive check using select and the timeout
       values */
        if (res_conn < 0) {
                if (errno == EINPROGRESS) {
                        timeout.tv_sec = DEFAULT_TIMEOUT; /* Defined in portcheck.h */
                        timeout.tv_usec = 0;              /* Set usec to 0 */
                        FD_ZERO(&wset);                   /* Zero out desc */
                        FD_SET(sock, &wset);              /* Set and do select */
                        res_sel = select(sock + 1,NULL, &wset, NULL, &timeout);
                        if (res_sel == 0 && errno != EINTR) {
                                printf("Error connecting\n");
                                close (sock);
                                return 1; /* NUTS! */
                        }
                }
        }

    /* So far so good - the host exists and is up; check the port and report */
    close (sock);
    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]);
        else
                printf("%i is not open on %s\n", port, argv[2]);

    close(sock);

        return 0;
}

It should be pretty easy to see where some functions can be added now. The entire pre-check should probably be offloaded into its own function. Additionally the final portcheck could be as well for cleanliness. Following is the updated full source code for portcheck:

portcheck.h

#ifndef _PORTCHECK_H
#define _PORTCHECK_H

#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 <time.h>
#include <unistd.h>

#define DEFAULT_TIMEOUT 3 /* The Default Timeout Value */

#endif

portcheck_main.c

#include "portcheck.h"

#define USAGE "Usage: portcheck port address"

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

    if (argv[1])
        if (!strcmp(argv[1], "-u")) {
            printf("%s\n", USAGE);
            return 0;
        }

    if (argv[1])
        port = atoi(argv[1]);
    else {
        fprintf(stderr, "No port specified\n");
        fprintf(stderr,"%s\n", USAGE);
        return 1;
    }

    if (argv[2])
        strncpy(addr, argv[2], 1023);
    else {
        fprintf(stderr, "No address specified\n");
        fprintf(stderr,"%s\n", USAGE);
        return 1;
    }

    bzero((char *)&address, sizeof(address)); /* init addr struct */
    address.sin_addr.s_addr = inet_addr(addr); /* assign the address */
    /*
     *     1. Open the master socket locally
     *     2. Configure non-blocking mode
     *     3. Check connect() status
     *     4. If bad check select() status
     *     5. If all goes well close sock and check port
     *     6. Connect to hostbyport works? print success
     */
    sock = -1;
    sock = socket(AF_INET, SOCK_STREAM, 0);

    /* Setup non-blocking for select first get the descriptor */
   if( (arg = fcntl(sock, F_GETFL, NULL)) < 0) {
        fprintf(stderr,
        "Error fcntl(..., F_GETFL) (%s)\n",            strerror(errno));
        return 1;
    }

    /* Now setup actual non block mode */
    arg |= O_NONBLOCK;
    if(fcntl(sock, F_SETFL, arg) < 0) {
        fprintf(stderr,
        "Error fcntl(..., F_SETFL)  (%s)\n",                                    
        strerror(errno));
        return 1;
    }
    /* Run a connect and store the result */
    res_conn = connect(sock,(struct sockaddr *)&address,sizeof(address));

    /* If were still going, attach a timeout to it, open a select descriptor
       then do a basic is this host alive check using select and the timeout
       values */
    if (res_conn < 0) {
        if (errno == EINPROGRESS) {
            timeout.tv_sec = DEFAULT_TIMEOUT; /* Defined in portcheck.h */
            timeout.tv_usec = 0;              /* Set usec to 0 */
            FD_ZERO(&wset);                   /* Zero out desc */
            FD_SET(sock, &wset);              /* Set and do select */
            res_sel = select(sock + 1,NULL, &wset, NULL, &timeout);
            if (res_sel == 0 && errno != EINTR) {
                printf("Error connecting\n");
                close (sock);
                return 1; /* NUTS! */
            }
    /* So far so good - the host exists and is up; check the port and report */
    close (sock);
    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]);
    else
        printf("%i is not open on %s\n", port, argv[2]);

    close(sock);

    return 0;
}

Summary & Next Time

This installment of the series covered a lot of ground which is why only the timeout/precheck code was addressed. In the next installment, hopefully the last one, step one will be to factor the existing code out into functions where possible to make adding the error handling plus host resolution code simpler.

prev - next