Jan 2010
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:
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 the isalive() function into its own module requires
three things:
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;
...
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;
}
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.