Skip to content

Commit

Permalink
Merge pull request #4 from mkirchner/features/refactor
Browse files Browse the repository at this point in the history
Refactor code into library and CLI
  • Loading branch information
mkirchner authored Mar 25, 2023
2 parents 87eccd4 + 11f47b2 commit 813cae1
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 162 deletions.
26 changes: 11 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
FILES=README tcping.c Makefile LICENSE
VERNUM=`grep VERSION tcping.c | cut -d" " -f3`
FILES=README cli.c tcping.h tcping.c Makefile LICENSE
VERNUM=$$(grep TCPING_VERSION tcping.h | cut -d" " -f3 | sed -e 's/"//g')
VER=tcping-$(VERNUM)

CCFLAGS=-Wall
CC=gcc

tcping.linux: tcping.c
$(CC) -o tcping $(CCFLAGS) tcping.c
$(CC) -o tcping $(CCFLAGS) cli.c tcping.c

tcping.macos: tcping.linux

Expand All @@ -15,18 +15,14 @@ tcping.openbsd: tcping.linux
readme: man/tcping.1
groff -man -Tascii man/tcping.1 | col -bx > README

deb-linux: tcping.linux
mkdir -p debian/usr/bin
cp tcping debian/usr/bin
mkdir debian/DEBIAN
cat deb/control | sed -e "s/VERSION/$(VERNUM)/" > debian/DEBIAN/control
md5sum debian/usr/bin/tcping | sed -e 's#debian/##g' > debian/DEBIAN/md5sums
dpkg-deb --build debian/ $(VER).deb
rm -rf debian

.PHONY: clean dist
clean:
rm -f tcping core *.o *.deb
rm -rf debian/
rm -f tcping core *.o

dist:
mkdir $(VER) ; cp $(FILES)
mkdir $(VER)
mkdir $(VER)/man
cp $(FILES) $(VER)/
cp man/tcping.1 $(VER)/man/
tar cvzf $(VER).tar.gz $(VER)
rm -rf $(VER)
114 changes: 114 additions & 0 deletions cli.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* tcping command line utility
*
* Copyright (c) 2002-2023 Marc Kirchner
*
*/

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>

#include "tcping.h"

void usage(char *prog)
{
fprintf(stderr,
"error: Usage: %s [-q] [-f <4|6>] [-t timeout_sec] [-u "
"timeout_usec] <host> <port>\n",
prog);
exit(-1);
}

int main(int argc, char *argv[])
{

int force_ai_family = AF_UNSPEC;
long timeout_sec = 0, timeout_usec = 0;
struct timeval timeout;
int verbosity = 1;

if (argc < 3) {
usage(argv[0]);
}

char *cptr;
int c;
while ((c = getopt(argc, argv, "qf:t:u:")) != -1) {
switch (c) {
case 'q':
verbosity = 0;
break;
case 'f':
cptr = NULL;
long fam = strtol(optarg, &cptr, 10);
if (cptr == optarg || (fam != 4 && fam != 6))
usage(argv[0]);
force_ai_family = fam == 4 ? AF_INET : AF_INET6;
break;
case 't':
cptr = NULL;
timeout_sec = strtol(optarg, &cptr, 10);
if (cptr == optarg)
usage(argv[0]);
break;
case 'u':
cptr = NULL;
timeout_usec = strtol(optarg, &cptr, 10);
if (cptr == optarg)
usage(argv[0]);
break;
default:
usage(argv[0]);
break;
}
}
if (!argv[optind + 1]) {
usage(argv[0]);
}

timeout.tv_sec = timeout_sec + timeout_usec / 1000000;
timeout.tv_usec = timeout_usec % 1000000;

struct hostinfo *host;
int err;
int retval = 0;
if ((err = tcping_gethostinfo(argv[optind], argv[optind + 1],
force_ai_family, &host)) != 0) {
log(verbosity, stderr, "error: %s\n", gai_strerror(err));
retval = 255;
goto quit;
}
int sockfd = tcping_socket(host);
int result = tcping_connect(sockfd, host, &timeout);
tcping_close(sockfd);
switch (result) {
case TCPING_ERROR:
log(verbosity, stderr, "error: %s port %s: %s\n", host->name,
host->serv, strerror(errno));
retval = 255;
break;
case TCPING_OPEN:
log(verbosity, stdout, "%s port %s open.\n", host->name, host->serv);
break;
case TCPING_CLOSED:
log(verbosity, stdout, "%s port %s closed.\n", host->name, host->serv);
retval = 1;
break;
case TCPING_TIMEOUT:
log(verbosity, stdout, "%s port %s user timeout.\n", host->name,
host->serv);
retval = 2;
break;
default:
log(verbosity, stderr, "error: invalid return value\n");
retval = 255;
break;
}
quit:
tcping_freehostinfo(host);
return retval;
}
195 changes: 48 additions & 147 deletions tcping.c
Original file line number Diff line number Diff line change
@@ -1,174 +1,75 @@
/*
* tcping.c
*
* Copyright (c) 2002-2023 Marc Kirchner
*
* tcping does a nonblocking connect to test if a port is reachable.
* Its exit codes are:
* 255 an error occured
* 0 port is open
* 1 port is closed
* 2 user timeout
*/
#include "tcping.h"

#define VERSION 2.0.0

#define log(verbosity, fmt, ...) \
do { \
if (verbosity) \
fprintf(fmt, __VA_ARGS__); \
} while (0)

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

void usage();

int main(int argc, char *argv[])
int tcping_gethostinfo(char *node, char *serv, int ai_family,
struct hostinfo **hi)
{

char *cptr;
struct addrinfo hints, *res;
int c;
int err;
socklen_t errlen;
int error = 0;
long fam;
fd_set fdrset, fdwset;
int force_ai_family = AF_UNSPEC;
char hostname[INET6_ADDRSTRLEN];
int ret;
char servicename[6];
int sockfd;
long timeout_sec = 0, timeout_usec = 0;
struct timeval timeout;
int verbosity = 1;

if (argc < 3) {
usage(argv[0]);
}

while ((c = getopt(argc, argv, "qf:t:u:")) != -1) {
switch (c) {
case 'q':
verbosity = 0;
break;
case 'f':
cptr = NULL;
fam = strtol(optarg, &cptr, 10);
if (cptr == optarg || (fam != 4 && fam != 6))
usage(argv[0]);
force_ai_family = fam == 4 ? AF_INET : AF_INET6;
break;
case 't':
cptr = NULL;
timeout_sec = strtol(optarg, &cptr, 10);
if (cptr == optarg)
usage(argv[0]);
break;
case 'u':
cptr = NULL;
timeout_usec = strtol(optarg, &cptr, 10);
if (cptr == optarg)
usage(argv[0]);
break;
default:
usage(argv[0]);
break;
}
}
if (!argv[optind + 1]) {
usage(argv[0]);
}

*hi = (struct hostinfo *)calloc(1, sizeof(struct hostinfo));
/* collect all ipv4 and ipv6 address info */
struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = force_ai_family;
hints.ai_family = ai_family;
hints.ai_socktype = SOCK_STREAM;
if ((err = getaddrinfo(argv[optind], argv[optind + 1], &hints, &res)) !=
0) {
log(verbosity, stderr, "error: %s\n", gai_strerror(err));
exit(-1);
}
if ((err = getnameinfo(res->ai_addr, res->ai_addrlen, hostname,
sizeof(hostname), servicename, sizeof(servicename),
NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
log(verbosity, stderr, "error: %s\n", gai_strerror(err));
exit(-1);
}
/* we only look at the first resolution result */
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
int err;
if ((err = getaddrinfo(node, serv, &hints, &(*hi)->ai)) != 0)
return err;
if ((err = getnameinfo((*hi)->ai->ai_addr, (*hi)->ai->ai_addrlen,
(char *)&(*hi)->name, INET6_ADDRSTRLEN,
(char *)&(*hi)->serv, INET_NUMERICSERVSTRLEN,
NI_NUMERICHOST | NI_NUMERICSERV)) != 0)
return err;
return 0;
}

void tcping_freehostinfo(struct hostinfo *hi)
{
freeaddrinfo(hi->ai);
free(hi);
}

/* attempt the connection */
int tcping_socket(struct hostinfo *host)
{
int sockfd = socket(host->ai->ai_family, host->ai->ai_socktype,
host->ai->ai_protocol);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
ret = connect(sockfd, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
if (ret) {
return sockfd;
}

int tcping_connect(int sockfd, struct hostinfo *host, struct timeval *timeout)
{
int ret;
if ((ret = connect(sockfd, host->ai->ai_addr, host->ai->ai_addrlen)) != 0) {
if (errno != EINPROGRESS) {
log(verbosity, stderr, "error: %s port %s: %s\n", hostname,
servicename, strerror(errno));
exit(-1);
return TCPING_ERROR;
}

fd_set fdrset, fdwset;
FD_ZERO(&fdrset);
FD_SET(sockfd, &fdrset);
fdwset = fdrset;

timeout.tv_sec = timeout_sec + timeout_usec / 1000000;
timeout.tv_usec = timeout_usec % 1000000;

if ((ret = select(sockfd + 1, &fdrset, &fdwset, NULL,
timeout.tv_sec + timeout.tv_usec > 0 ? &timeout
: NULL)) == 0) {
timeout->tv_sec + timeout->tv_usec > 0 ? timeout
: NULL)) ==
0) {
/* timeout */
close(sockfd);
log(verbosity, stdout, "%s port %s user timeout.\n", hostname,
servicename);
return 2;
return TCPING_TIMEOUT;
}
int error = 0;
if (FD_ISSET(sockfd, &fdrset) || FD_ISSET(sockfd, &fdwset)) {
errlen = sizeof(error);
socklen_t errlen = sizeof(error);
if ((ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error,
&errlen)) != 0) {
/* getsockopt error */
log(verbosity, stderr, "error: %s port %s: getsockopt: %s\n",
hostname, servicename, strerror(errno));
close(sockfd);
exit(-1);
return TCPING_ERROR;
}
if (error != 0) {
log(verbosity, stdout, "%s port %s closed.\n", hostname,
servicename);
close(sockfd);
return 1;
/* closed */
return TCPING_CLOSED;
}
} else {
log(verbosity, stderr, "error: select: sockfd not set\n");
exit(-1);
return TCPING_ERROR;
}
}
/* OK, connection established */
close(sockfd);
log(verbosity, stdout, "%s port %s open.\n", hostname, servicename);
return 0;
/* connection established */
return TCPING_OPEN;
}

void usage(char *prog)
{
fprintf(stderr,
"error: Usage: %s [-q] [-f <4|6>] [-t timeout_sec] [-u "
"timeout_usec] <host> "
"<port>\n",
prog);
exit(-1);
}
int tcping_close(int sockfd) { return close(sockfd); }
Loading

0 comments on commit 813cae1

Please sign in to comment.