Jan 2010

Network Connection Program Part 5

In the previous parts of this series writing a simple program to see if a port is available was covered, in this the final text the last three points from the previous text are discussed:

  • Migrate the pre-check into its own module entirely.
  • Add in more descriptive error handling locally and for the connection errors.
  • Any last thoughts about the program itself and where it could go.

Recap

Following is the code thusfar excepting the Makefile and header file (see previous text for details):

#include "portcheck.h"

#define USAGE "Usage: portcheck port address"

static int isalive(struct sockaddr_in scanaddr)
{
    short int sock;          /* our main socket */
    long arg;                /* for non-block */
    fd_set wset;             /* file handle for bloc mode */
    struct timeval timeout;  /* timeout struct for connect() */

    sock = -1;

    sock = socket(AF_INET, SOCK_STREAM, 0);

    if( (arg = fcntl(sock, F_GETFL, NULL)) < 0) { 
        fprintf(stderr,
        "Error fcntl(..., F_GETFL) (%s)\n",
            strerror(errno));
        return 1;
    }

    arg |= O_NONBLOCK;
    if(fcntl(sock, F_SETFL, arg) < 0) {
        fprintf(stderr,
        "Error fcntl(..., F_SETFL)  (%s)\n",                                            strerror(errno));
        return 1;
    }

    /* 
     * set result stat then try a select if it can take
     * awhile. This is dirty but works 
     */
    int res = connect(sock,(struct sockaddr *)&scanaddr,
                      sizeof(scanaddr));

    if (res < 0) {
        if (errno == EINPROGRESS) {
            timeout.tv_sec = DEFAULT_TIMEOUT;
            timeout.tv_usec = 0;
            FD_ZERO(&wset);
            FD_SET(sock, &wset);
            int rc = select(sock + 1,NULL,
            &wset,NULL,&timeout);

            /* This works great on dead hosts */
            if (rc == 0 && errno != EINTR) {
                                printf("Error connecting\n");
                                close (sock);
                return 1;
            }
        }
    }
    close(sock);
    return 0;
}



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

        /* Hostname resolution */
        if ((host_info = gethostbyname(addr))) 
                bcopy(host_info->h_addr,
                  (char *)&address.sin_addr,host_info->h_length);
        else if ((address.sin_addr.s_addr = inet_addr(argv[2])) == INADDR_NONE) {
                fprintf(stderr, "Could not resolve host\n" );
                return 1;
        }

    /*
     *     1. Make sure the host is alive
     *     2. Connect to hostbyport works? print success
     */
        if (isalive(address)) return 0;

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

Moving Out the Check

Moving the isalive() function into its own module requires three things:

  • Its own header file
  • A source file
  • Included in the program build

For speed, the header file used for portcheck can be copied then paired down to only include the needed includes. Assuming the code for isalive() is copied into a file called isalive.c adding it to the build is trivial, simply add it to the compile line in the Makefile or perhaps be a little more creative and create a variable for all the source files like so:

CC=cc
SRCS=portcheck_main.c isalive.c

all: portcheck

portcheck:
    ${CC} ${SRCS} -o $@

clean:
    rm -f a.out portcheck

rebuild: clean portcheck

Lastly the code for isalive() can simply be moved because the the function only returns a number it is already well prepared for becoming a module with one exception, it requires the timeout as an argument in addition to the address now in order to keep the data separate and the declaration is no longer static:

#include <isalive.>
int isalive(struct sockaddr_in scanaddr, int timeout)
{
    short int sock;          /* our main socket */
    long arg;                /* for non-block */
    fd_set wset;             /* file handle for bloc mode */
    struct timeval timeout;  /* timeout struct for connect() */

    sock = -1;

    sock = socket(AF_INET, SOCK_STREAM, 0);

    if( (arg = fcntl(sock, F_GETFL, NULL)) < 0) {
        fprintf(stderr,
        "Error fcntl(..., F_GETFL) (%s)\n",
            strerror(errno));
        return 1;
    }

    arg |= O_NONBLOCK;
    if(fcntl(sock, F_SETFL, arg) < 0) {
        fprintf(stderr,
        "Error fcntl(..., F_SETFL)  (%s)\n",                                            strerror(errno));
        return 1;
    }

    /*
     * set result stat then try a select if it can take
     * awhile. This is dirty but works
     */
    int res = connect(sock,(struct sockaddr *)&scanaddr,
                      sizeof(scanaddr));

    if (res < 0) {
        if (errno == EINPROGRESS) {
            timeout.tv_sec = timeout;
            timeout.tv_usec = 0;
            FD_ZERO(&wset);
            FD_SET(sock, &wset);
            int rc = select(sock + 1,NULL,
            &wset,NULL,&timeout);

            /* This works great on dead hosts */
            if (rc == 0 && errno != EINTR) {
                                printf("Error connecting\n");
                                close (sock);
                return 1;
            }
        }
    }
    close(sock);
    return 0;
}

Finally inside the main() routine the callout to isalive() now needs the timeout added to it:

...
   /*
    *     1. Make sure the host is alive
    *     2. Connect to hostbyport works? print success
    */
    if (isalive(address),DEFAULT_TIMEOUT) return 0;
...

Adding More Error Handling

With networks the level of error handling can be as exotic or not as a programmer may wish. In the header files can be found many types and the socket programming manual pages also cover the many errors that can be sent back under certain circumstances, for the sake of simplicity this program will deal with a few of the more probable ones that might be seen. First a function that deals with a returned error:

/* capture any strange socket errors here */
static void sockerr(int res)
{
    fprintf(stderr, "Connect error: ");

    switch (res) {
    case EADDRINUSE: /* Is this address in use here? */
        fprintf(stderr, "EADDRINUSE\n");
        break;
    case EADDRNOTAVAIL: /* This address is not available */
        fprintf(stderr, "EADDRNOTAVAIL\n");
        break;
    case EALREADY: /* Socket is already in use */
        fprintf(stderr, "EALREADY\n");
        break;
    case ECONNREFUSED: /* Connection was refused by host */
        fprintf(stderr, "ECONNREFUSED\n");
        break;
    case EHOSTUNREACH: /* Could not reach host */
        fprintf(stderr, "EHOSTUNREACH\n");
        break;
    case ETIMEDOUT: /* Timed out */
        fprintf(stderr, "ETIMEDOUT\n");
        break;
    default: /* Not sure but assuming undetectable timeout */
        fprintf(stderr, "Host timed out (exists?)\n");
        break;
    }
}

At this point all that needs to be done is if a connection cannot be made and it is not interrupted, pass the results along to the sockerr() function.

          /* This works great on dead hosts */
          if (rc == 0 && errno != EINTR) {
                 sockerr(res);
                 close (sock);
                 return 1;
          }

Summary

This series has only touched the tip of the iceberg regarding testing a socket. It could easily be transformed into something larger by looping through addresses to check for multiple ports or even be adapted to connect to a customized service.