Sep 2009
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:
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;
}
Taking a step back; the program essentially does four things:
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 *)&scanaddr,
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;
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;
}
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
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: