Aug 2009
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:
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.
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 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:
selectint to catch the connect
statusint to catch the select
statusselect call that is API
compatibleIn 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;
}
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.