Jun 2009

Port Check Program (in C) Part 2

In part 1 an examination of creating an ultra simple single port single host port check program was done. Part two looks at initial code reorganizing, a simple Makefile then addresses some of the potential problems with the bare bones version. Following is a recap of what needs to be fixed:

  • Missing a usage message of any sort
  • Needs more (any???) input vaidation
  • Master socket status needs to be checked

additionally there are some features it could use:

  • Hostname resolution
  • Smaller timeout window
  • Attempt to identify and return data about specific errors

After looking at the code; it made the most sense to break up the single program into at least one module then distribute where each of the changes should go:

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.

Note in this text only the bugs and some reorganization will be addressed. Here is the current full source listing for reference, in the text only affected sections will be referenced then at the end another full source listing with the changes made:

#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];            /* address */
    struct sockaddr_in address; /* address structures */
    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;
}

Before there are any major changes time to make the simplest change to cut down on the full source listing: no further extra header files will be needed so the includes can me moved into their own local header then referenced within the source build directory. To accomplish this task it is time to move portcheck.c into it's own directory and split the headers:

mkdir portcheck/
mv sourcefile.c portcheck/portcheck_main.c
cp *main.c portcheck.h
vi *

The file portcheck.h should only have the following in it:

#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

and now portcheck_main.c has only:

#include "portcheck.h"

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

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

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

Note that a few things changed; the variable declaration order for starters. This is a styling choice, sometimes ordering by size then alpha makes a compile run faster. Although the program can still be compiled using:

        cd ~portcheck/
        cc portcheck_main.c -o portcheck

Now is a good time to draft a simple Makefile for the project; this will save time later on. In the source directory a very simple Makefile will suffice:

CC=cc

all: portcheck

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

clean:
        rm -f a.out portcheck

rebuild: clean portcheck

So now all that has to be done is:

        cd ~portcheck 
        make

With the preceding changes in place it is time to tackle the bugs.

Bugs

First up is a usage message, instead of setting up an option switcher just catching it first and exiting is the simplest approach. A define of the usage message can be used so the message can be reused in the input validation:

...
#define USAGE "Usage: portcheck port address"

int main(int argc, char **argv)
...
    char addr[1023];

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

Next: input validation, since the program will eventually accept hostnames as well it will check for the proper number of arguments immediately after the usage help code:

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

Note that the usage message is also handled in the same step. Time to make sure that a socket can even be assigned; this is pretty simple since if the assignment fails the value will be -1; all that needs to be done is to check on the value immediately after assigning sock:

...
 */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        fprintf(stderr, "Error: could not assign master socket\n");
        exit (1);
    }
...

So far so good and not too complicated. Although it is difficult to display the socket failure here is some example output of the input validation and usage message:

make rebuild
rm -f a.out portcheck
cc portcheck_main.c -o portcheck
./portcheck -u
Usage: portcheck port address
echo $?
0
./portcheck 
Syntax error
Usage: portcheck port address
echo $?
1
./portcheck 22
Syntax error
Usage: portcheck port address
echo $?
1
./portcheck 22 192.168.1.3
22 is open on 192.168.1.3
echo $?
0

New Full Source Listing

With the changes now complete the portcheck_main.c file looks like:

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

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

Summary and Next Time

With the three main potential problems solved the next text in the series will address the improvements and breaking out the actual check code from the main() function. To recap; the improvements are:

  • hostname resolution
  • detailed socket errors
  • smaller variable timeout value for connection tests
prev - next