diff --git a/.gitignore b/.gitignore index 6fb5d60..46c3668 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,26 @@ -echoc +*.la +*.lo *.o *~ +.deps +.libs +m4/*.m4 +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +compile +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +missing +stamp-* +obj* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..914ee49 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + Copyright (c) 2012-2015 Matthieu Herrb + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index ac8c90b..0000000 --- a/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# - -PROG= echoc -SRCS= echoc.c - -OBJS= $(SRCS:%.c=%.o) - -LIBS= - -all: $(PROG) - -$(PROG): $(OBJS) - $(CC) -o $@ $(OBJS) $(LIBS) - -clean: - rm -f $(OBJS) $(PROG) - -.c.o: - $(CC) -c $(CFLAGS) -o $@ $< diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..c30d877 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +bin_PROGRAMS = echoc + +AM_CFLAGS = -g -Wall + +dist_man_MANS = echoc.1 diff --git a/README b/README deleted file mode 100644 index d3b4dc8..0000000 --- a/README +++ /dev/null @@ -1,21 +0,0 @@ -echoc is a small network test program that sends UDP packets to the -echo service (it should be activated in [x]inetd on the target host). -echoc displays the start/end date of loss of connectivity. - -It's distributed under the ISC license: - -/* - * Copyright (c) 2012 Matthieu Herrb - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c3fb52 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +echoc +===== + +echoc is a small network test program that sends UDP packets to the +echo service (it should be activated in [x]inetd on the target host). +echoc displays the start/end date of loss of connectivity. + +Building +-------- + +From git, run: + +``` +./autogen.sh +make +make install +``` + +From a source tarball, run: + +``` +./configure +make +make install +``` + +Licence +------- + +Echoc is distributed under the ISC license: + + Copyright (c) 2012-2015 Matthieu Herrb + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..bb05f17 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,13 @@ +#! /bin/sh +srcdir=`dirname "$0"` +test -z "$srcdir" && srcdir=. + +ORIGDIR=`pwd` +cd "$srcdir" + +autoreconf -v --install || exit 1 +cd "$ORIGDIR" || exit $? + +if test -z "$NOCONFIGURE"; then + exec "$srcdir"/configure "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..f4e1c5d --- /dev/null +++ b/configure.ac @@ -0,0 +1,11 @@ +AC_PREREQ([2.71]) +AC_INIT([echoc],[0.6.99],[https://gitlab.laas.fr/matthieu/echoc],[echoc]) +AM_INIT_AUTOMAKE([foreign dist-bzip2]) +AC_CONFIG_MACRO_DIR([m4]) +AC_PROG_CC + +AC_CHECK_HEADERS([bsd/stdlib.h]) +AC_SEARCH_LIBS(strtonum, [bsd]) + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/echoc.1 b/echoc.1 new file mode 100644 index 0000000..9cada97 --- /dev/null +++ b/echoc.1 @@ -0,0 +1,80 @@ +.\" +.\" Copyright (c) 2012-2015 Matthieu Herrb +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: September 29 2015 $ +.Dt ECHOC 1 +.Os +.Sh NAME +.Nm echoc +.Nd send UDP packets to network hosts and wait for replies +.Sh SYNOPSYS +.Nm echoc +.Bk -words +.Op Fl dv +.Op Fl c Ar count +.Op Fl i Ar ms +.Op Fl l Ar len +.Op Fl p Ar port +.Op Fl t Ar ms +.Ar host +.Ek +.Sh DESCRIPTION +.Nm +sends UDP packets to the +.Dv ECHO +service on the remote +.Ar host . +.Nm +displays the start/end date of loss of connectivity. +The options are as follow: +.Bl -tag -width Ds +.It Fl c Ar count +Stop sending after +.Ar count +packets have been sent. +.It Fl d +Set the +.Dv Don't Fragment +bit on outgoing datagrams, on systems that allow programs to control it. +On some systems (MacOSX, OpenBSD) this option will do nothing and the +generated datagrams will have the DF bit set or not, depending on +system settings. +.It Fl i Ar ms +wait +.Ar ms +milliseconds between every packet (default: 100 ms). +.It Fl l Ar len +Send packets of length +.Ar len +bytes (default: 10). +.It Fl p Ar port +Set destination port to +.Ar port +(default: 7 - echo). +.It Fl t Ar ms +Wait at most +.Ar ms +milliseconds for the reply before declaring the connection dead +(default: 500ms). +.El +.Sh SEE ALSO +.Xr nc 1 , +.Xr ping 8 , +.Xr ping6 8 +.Sh AUTHOR +The +.Nm +utility was written for tetaneutral.net by Matthieu Herrb. + diff --git a/echoc.c b/echoc.c index f5ddb69..269f4f6 100644 --- a/echoc.c +++ b/echoc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Matthieu Herrb + * Copyright (c) 2012-2015 Matthieu Herrb * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,6 +18,11 @@ #include #include +#include +#ifdef HAVE_BSD_STDLIB_H +#include +#endif + #include #include #include @@ -32,25 +37,38 @@ int sock = -1; int verbose = 0; +int aborting = 0; struct sockaddr *server; socklen_t serverlen; unsigned int seq = 0; +size_t len = 10; static void usage(void) { - errx(2, "usage: echoc [-i ms][-t ms][-v] server"); + errx(2, "usage: echoc [-c nbr][-d][-i ms][-l len][-p port][-t ms][-v] server"); +} + +static void +sigint_handler(int unused) +{ + aborting = 1; } static void send_packet(int unused) { - if (sendto(sock, &seq, sizeof(seq), 0, server, - serverlen) != sizeof(seq)) { + char *buf; + buf = malloc(len); + if (buf == NULL) + return; + snprintf(buf, len, "%d", seq); + if (sendto(sock, buf, len, 0, server, + serverlen) != len) { if (verbose) warn("sendto"); } - if (verbose) + if (verbose) printf("sent %d\n", seq); seq++; } @@ -59,7 +77,10 @@ int main(int argc, char *argv[]) { char name[NI_MAXHOST]; - char buf[80]; + char *recvbuf; + const char *errstr; + char date[80]; + char *port = "echo"; struct sockaddr_storage client; struct addrinfo hints, *res, *res0; struct itimerval itv; @@ -68,20 +89,35 @@ main(int argc, char *argv[]) struct tm *tm; struct pollfd pfd[1]; socklen_t addrlen; - long interval = 100; /* default interval (ms) */ + long interval = 100; /* default interval (ms) */ long timeout = 500; /* default timeout (ms) */ int ch; int nfds, received = 0; - int error, buffer, last = -1; + int error, last = -1; int disconnected; + int nofragment = 0; + int we_count = 0, counter = 0; /* don't loop forever */ extern int optind; setbuf(stdout, NULL); - while ((ch = getopt(argc, argv, "i:t:v")) != -1) { + while ((ch = getopt(argc, argv, "c:di:l:p:t:v")) != -1) { switch (ch) { + case 'c': + we_count++; + counter = atoi(optarg); + break; + case 'd': + nofragment++; + break; case 'i': interval = atoi(optarg); break; + case 'l': + len = atoi(optarg); + break; + case 'p': + port = optarg; + break; case 't': timeout = atoi(optarg); break; @@ -99,7 +135,8 @@ main(int argc, char *argv[]) if (interval <= 0 || timeout <= 0) errx(2, "interval and timeout must be > 0"); - + if (we_count && counter < 2) + errx(2, "can't count down from nothing"); /* force timeout >= interval */ if (timeout < interval) { timeout = interval; @@ -111,7 +148,7 @@ main(int argc, char *argv[]) memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; - error = getaddrinfo(argv[0], "echo", &hints, &res0); + error = getaddrinfo(argv[0], port, &hints, &res0); if (error) errx(1, "%s: %s", argv[0], gai_strerror(error)); @@ -130,21 +167,38 @@ main(int argc, char *argv[]) disconnected = 0; memset(&client, 0, sizeof(client)); + recvbuf = malloc(len); + if (recvbuf == NULL) + err(2, "malloc receive buffer"); + +#ifdef IP_MTU_DISCOVER + /* set the DF flag ? */ + if (nofragment) + ch = IP_PMTUDISC_DO; + else + ch = IP_PMTUDISC_DONT; + if (setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, &ch, sizeof(ch)) < 0) + err(2, "setsockopt IP_MTU_DISCOVER"); +#endif + signal(SIGALRM, send_packet); + signal(SIGINT, sigint_handler); + /* timer values */ - itv.it_interval.tv_usec = interval*1000; + itv.it_interval.tv_usec = interval*1000; itv.it_interval.tv_sec = 0; itv.it_value.tv_usec = interval*1000; itv.it_value.tv_sec = 0; - if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) err(2, "setitimer"); - signal(SIGALRM, send_packet); - gettimeofday(&last_ts, NULL); + tm = localtime((time_t *)&last_ts.tv_sec); + strftime(date, sizeof(date), "%F %T", tm); + printf("%s.%06ld: starting\n", date, last_ts.tv_usec); - while (1) { + while (!aborting) { /* poll() loop to handle interruptions by SIGALRM */ - while (1) { + while (!aborting) { pfd[0].fd = sock; pfd[0].events = POLLIN; nfds = poll(pfd, 1, timeout); @@ -160,31 +214,33 @@ main(int argc, char *argv[]) break; } } + if (aborting) + break; if ((nfds == 0)) { - if (verbose) + if (verbose) printf("%d packet(s) dropped in %ld.%06ld s\n", - seq - last, diff.tv_sec, diff.tv_usec); - + seq - last, (long)diff.tv_sec, diff.tv_usec); + if (disconnected == 1) { tm = localtime((time_t *)&last_ts.tv_sec); - strftime(buf, sizeof(buf), "%F %T", tm); + strftime(date, sizeof(date), "%F %T", tm); printf("%s.%06ld: lost connection\n", - buf, last_ts.tv_usec); + date, last_ts.tv_usec); } continue; } addrlen = sizeof(client); - if ((received = recvfrom(sock, &buffer, sizeof(buffer), + if ((received = recvfrom(sock, recvbuf, len, MSG_DONTWAIT, (struct sockaddr *) &client, - &addrlen)) != sizeof(buffer)) { + &addrlen)) != len) { warn("recvfrom"); } if (verbose && (serverlen != addrlen || memcmp(&client, server, addrlen) != 0)) { if ((error = getnameinfo((struct sockaddr *)&client, - addrlen, name, sizeof(name), + addrlen, name, sizeof(name), NULL, 0, NI_DGRAM)) != 0) { warnx("%s", gai_strerror(error)); } else { @@ -194,17 +250,32 @@ main(int argc, char *argv[]) } if (disconnected) { tm = localtime((time_t *)&now.tv_sec); - strftime(buf, sizeof(buf), "%F %T", tm); + strftime(date, sizeof(date), "%F %T", tm); printf("%s.%06ld: connection is back " "dropped %d packets\n", - buf, now.tv_usec, seq - last); + date, now.tv_usec, seq - last); disconnected = 0; } - last = buffer; + last = strtonum(recvbuf, 0, INT_MAX, &errstr); + if (errstr) + errx(1, "invalid reply %s", errstr); memcpy(&last_ts, &now, sizeof(struct timeval)); if (verbose) - printf("received %d %ld.%06ld\n", buffer, + printf("received %d %ld.%06ld\n", last, (long)diff.tv_sec, diff.tv_usec); + if (we_count) { + counter--; + if (counter == 0) { + printf("all job done\n"); + break; + } + } + } + if (aborting) { + gettimeofday(&now, NULL); + tm = localtime((time_t *)&now.tv_sec); + strftime(date, sizeof(date), "%F %T", tm); + printf("%s.%06ld: aborting\n", date, now.tv_usec); } close(sock); exit(0);