Sep 2009

Port Check Program (in C) Part 4

In part 1 an examination of creating an ultra simple single port single host port check program was done. Part two 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. The most recent installment added a timed host pre-check component to ensure that a connect() would not potentially sit and spin when a host is not available. In this the fourth in the series the following items will be tackled:

  • Factor down the code to functions where possible (even just helpers if need be)
  • Add host resolution checking, right now if a host does not resolve the error is simply that a port is not open

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

Breaking up By Function

Taking a step back; the program essentially does four things:

  1. Get date from user and initialize
  2. Run a timed pre-check of the host (this involves a lot of intermediary steps)
  3. Check for a successful port connection
  4. Report on the port connection results

The first thing to think about is which of those steps actually changes data? The answer - none; which makes breaking up the code incredibly simple. In fact the first logical step is to take the pre-check code and move it to another function, thus returning the the main() function to needing to care about the core algorithm of the program which is:

Is port N open on host X?

Surprisingly creating a pre-check function is not too difficult, since all that is needed is the address, it ends up looking pretty simple:

static int isalive(struct sockaddr_in scanaddr)
{
    short int sock;          /* our 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 *)&ampscanaddr,
                      sizeof(scanaddr));

    if (res -lt; 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;
}

Where the code for the pre-check was before can now be replaced with:

if (isalive(address)) return 0;

Adding Resolution Checking

With resolution checking since the amount of code is nowhere near the same as the pre-check there is a choice - put inline with the initialization or break it out? Following is all that is needed anywhere before the isalive() check is done:

        ...
        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;
        }

That really isn't much code at all so for now it will remain inline. With the breakout of the pre-check complete, here is the new source listing for the main program file:

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

In Action

Now a quick peek at the program in action with the changes made in this part of the series - note of course that fubu does not exist:

./portcheck 22 argos
22 is open on argos
./portcheck 22 192.168.0.10
22 is open on 192.168.0.10
./portcheck 5555 argos
5555 is not open on argos
./portcheck 80 fubu
Could not resolve host

Summary & Next Time

The program is starting to really shape up, as it stands it does the job (and well) but it could use some polishing. In the next installment of the series the last items of the big TODO will be addressed:

  • 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.

prev