aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--GNUmakefile13
-rw-r--r--GNUmakefile.version37
-rw-r--r--src/akcom-udpechod.c496
3 files changed, 543 insertions, 3 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 49e50f2..b046e6c 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -31,7 +31,18 @@
31# 31#
32 32
33 33
34CFLAGS = -Wall -Wno-unknown-pragmas 34include GNUmakefile.version
35
36
37GIT_VERSION = $(shell git describe --long 2>/dev/null|sed -e s/-/./g -e s/^v//g)
38ifeq ($(GIT_VERSION),)
39 PACKAGE_VERSION = $(VERSION)
40else
41 PACKAGE_VERSION = $(GIT_VERSION)
42endif
43
44
45CFLAGS = -Wall -Wno-unknown-pragmas -DPACKAGE_VERSION='"$(PACKAGE_VERSION)"'
35LIBTOOL ?= libtool 46LIBTOOL ?= libtool
36INSTALL ?= install 47INSTALL ?= install
37PREFIX ?= /usr/local 48PREFIX ?= /usr/local
diff --git a/GNUmakefile.version b/GNUmakefile.version
new file mode 100644
index 0000000..98acd73
--- /dev/null
+++ b/GNUmakefile.version
@@ -0,0 +1,37 @@
1#
2# Alaska Communications UDP Echo Tools
3# Copyright (C) 2020 Alaska Communications
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# 1. Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#
12# 2. Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in the
14# documentation and/or other materials provided with the distribution.
15#
16# 3. Neither the name of the copyright holder nor the names of its
17# contributors may be used to endorse or promote products derived from
18# this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
21# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
24# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31#
32
33
34VERSION := 0.0
35
36
37# end of Makefile
diff --git a/src/akcom-udpechod.c b/src/akcom-udpechod.c
index b9765f5..8f71f3a 100644
--- a/src/akcom-udpechod.c
+++ b/src/akcom-udpechod.c
@@ -56,14 +56,27 @@
56/////////////// 56///////////////
57#pragma mark - Headers 57#pragma mark - Headers
58 58
59#include <stdint.h>
60#include <inttypes.h>
59#include <stdio.h> 61#include <stdio.h>
60#include <stdlib.h> 62#include <stdlib.h>
63#include <fcntl.h>
61#include <unistd.h> 64#include <unistd.h>
65#include <stdarg.h>
66#include <errno.h>
62#include <string.h> 67#include <string.h>
63#include <strings.h> 68#include <strings.h>
69#include <sys/types.h>
70#include <sys/socket.h>
71#include <netinet/in.h>
72#include <arpa/inet.h>
73#include <netdb.h>
64#include <time.h> 74#include <time.h>
65#include <getopt.h> 75#include <getopt.h>
66#include <assert.h> 76#include <assert.h>
77#include <syslog.h>
78#include <signal.h>
79#include <poll.h>
67 80
68 81
69/////////////////// 82///////////////////
@@ -73,6 +86,19 @@
73/////////////////// 86///////////////////
74#pragma mark - Definitions 87#pragma mark - Definitions
75 88
89#define MY_BUFF_SIZE 4096 // default buffer size
90
91
92#ifndef PROGRAM_NAME
93#define PROGRAM_NAME "akcom-udpechod"
94#endif
95#ifndef PACKAGE_NAME
96#define PACKAGE_NAME "akcom-udpecho"
97#endif
98#ifndef PACKAGE_VERSION
99#define PACKAGE_VERSION "0.0"
100#endif
101
76 102
77///////////////// 103/////////////////
78// // 104// //
@@ -81,6 +107,15 @@
81///////////////// 107/////////////////
82#pragma mark - Datatypes 108#pragma mark - Datatypes
83 109
110struct udp_echo_plus
111{
112 uint32_t req_sn;
113 uint32_t res_sn;
114 uint32_t recv_time;
115 uint32_t reply_time;
116 uint32_t failures;
117};
118
84 119
85///////////////// 120/////////////////
86// // 121// //
@@ -89,6 +124,33 @@
89///////////////// 124/////////////////
90#pragma mark - Variables 125#pragma mark - Variables
91 126
127static int should_stop = 0;
128
129struct
130{
131 const char * prog_name;
132 const char * pidfile;
133 uint16_t port; // UDP port number
134 uint16_t echoplus; // enable echo plus
135 int drop_perct; // drop percentage
136 int32_t delay; // Delay range in microseconds
137 int32_t verbose; // runtime verbosity
138 int facility; // syslog facility
139 int dont_fork;
140}
141cnf =
142{
143 .prog_name = "a.out",
144 .pidfile = "/var/run/" PROGRAM_NAME ".pid",
145 .port = 30006,
146 .echoplus = 1,
147 .drop_perct = 10,
148 .delay = 10000,
149 .verbose = 0,
150 .facility = LOG_DAEMON,
151 .dont_fork = 0,
152};
153
92 154
93////////////////// 155//////////////////
94// // 156// //
@@ -100,6 +162,21 @@
100// main statement 162// main statement
101int main(int argc, char * argv[]); 163int main(int argc, char * argv[]);
102 164
165// daemonize process
166int my_daemonize(void);
167
168// main loop
169int my_loop(int s);
170
171// signal handler
172void my_sighandler(int signum);
173
174// display program usage
175void my_usage(void);
176
177// display program usage error
178void my_usage_error(const char * fmt, ...);
179
103 180
104///////////////// 181/////////////////
105// // 182// //
@@ -111,10 +188,425 @@ int main(int argc, char * argv[]);
111// main statement 188// main statement
112int main(int argc, char * argv[]) 189int main(int argc, char * argv[])
113{ 190{
114 if (argc < 0) 191 char * ptr;
115 printf("hello %s\n", argv[0]); 192 int c;
193 int opts;
194 int s;
195
196 // determines program name
197 cnf.prog_name = argv[0];
198 if ((ptr = rindex(argv[0], '/')) != NULL)
199 cnf.prog_name = &ptr[1];
200
201 // process arguments
202 while ((c = getopt(argc, argv, "d:D:eEhnp:P:vV")) != -1)
203 {
204 switch(c)
205 {
206 case -1: // no more arguments
207 case 0: // long options toggles
208 break;
209
210 case 'd':
211 cnf.drop_perct = atol(optarg);
212 if ((cnf.drop_perct < 0) || (cnf.drop_perct > 99))
213 {
214 my_usage_error("invalid value for `-d'");
215 return(1);
216 };
217 break;
218
219 case 'e':
220 cnf.echoplus = 1;
221 break;
222
223 case 'E':
224 cnf.echoplus = 0;
225 break;
226
227 case 'f':
228 if (!(strcasecmp(optarg, "daemon"))) { cnf.facility = LOG_DAEMON; }
229 else if (!(strcasecmp(optarg, "local0"))) { cnf.facility = LOG_LOCAL0; }
230 else if (!(strcasecmp(optarg, "local1"))) { cnf.facility = LOG_LOCAL1; }
231 else if (!(strcasecmp(optarg, "local2"))) { cnf.facility = LOG_LOCAL2; }
232 else if (!(strcasecmp(optarg, "local3"))) { cnf.facility = LOG_LOCAL3; }
233 else if (!(strcasecmp(optarg, "local4"))) { cnf.facility = LOG_LOCAL4; }
234 else if (!(strcasecmp(optarg, "local5"))) { cnf.facility = LOG_LOCAL5; }
235 else if (!(strcasecmp(optarg, "local6"))) { cnf.facility = LOG_LOCAL6; }
236 else if (!(strcasecmp(optarg, "local7"))) { cnf.facility = LOG_LOCAL7; }
237 else if (!(strcasecmp(optarg, "user"))) { cnf.facility = LOG_USER; }
238 else
239 {
240 my_usage_error("invalid or unsupported syslog facility -- `%s'", optarg);
241 return(1);
242 };
243 break;
244
245 case 'h':
246 my_usage();
247 return(0);
248
249 case 'n':
250 cnf.dont_fork = 1;
251 break;
252
253 case 'p':
254 cnf.port = (uint16_t)(atoi(optarg) & 0xffff);
255 break;
256
257 case 'P':
258 cnf.pidfile = optarg;
259 break;
260
261 case 'v':
262 cnf.verbose++;
263 break;
264
265 case 'V':
266 printf("%s (%s) %s\n", cnf.prog_name, PACKAGE_NAME, PACKAGE_VERSION);
267 return(0);
268
269 case '?':
270 fprintf(stderr, "Try `%s --help' for more information.\n", cnf.prog_name);
271 return(1);
272
273 default:
274 my_usage_error("unrecognized option `--%c'", c);
275 return(1);
276 };
277 };
278
279 // configure signals
280 signal(SIGPIPE, SIG_IGN);
281 signal(SIGINT, my_sighandler);
282 signal(SIGQUIT, my_sighandler);
283 signal(SIGTERM, my_sighandler);
284
285 // open syslog
286 opts = LOG_PID;
287 if ((cnf.verbose))
288 opts |= LOG_PERROR;
289 openlog(cnf.prog_name, opts, cnf.facility);
290 syslog(LOG_NOTICE, "daemon starting");
291
292
293 // starts daemon functions
294 switch(s = my_daemonize())
295 {
296 case -1:
297 syslog(LOG_NOTICE, "daemon stopping");
298 closelog();
299 return(1);
300 break;
301
302 case 0:
303 return(0);
304
305 default:
306 break;
307 };
308
309 // loops
310 while(!(should_stop))
311 my_loop(s);
312
313 // close syslog
314 syslog(LOG_NOTICE, "daemon stopping");
315 close(s);
316 unlink(cnf.pidfile);
317 closelog();
318
116 return(0); 319 return(0);
117} 320}
118 321
119 322
323// daemonize process
324int my_daemonize(void)
325{
326 int s;
327 int rc;
328 int fd;
329 int opt;
330 socklen_t socklen;
331 char pidfile[512];
332 char buff[512];
333 pid_t pid;
334 pid_t oldpid;
335 union
336 {
337 struct sockaddr sa;
338 struct sockaddr_in6 s6;
339 struct sockaddr_storage ss;
340 } sin;
341
342 // create lock file
343 strncpy(pidfile, cnf.pidfile, sizeof(pidfile)-7);
344 strcat(pidfile, "XXXXXX");
345 if ((fd = mkstemp(pidfile)) == -1)
346 {
347 syslog(LOG_ERR, "error: mkstemp(): %s", strerror(errno));
348 return(-1);
349 };
350 if ((rc = link(pidfile, cnf.pidfile)) == -1)
351 {
352 syslog(LOG_ERR, "error: mkstemp(): %s", strerror(errno));
353 close(fd);
354 unlink(pidfile);
355 return(-1);
356 };
357 unlink(pidfile);
358 syslog(LOG_DEBUG, "pidfile: %s", cnf.pidfile);
359
360 // creates socket
361 if ((s = socket(PF_INET6, SOCK_DGRAM, 0)) == -1)
362 {
363 syslog(LOG_ERR, "socket error: %s", strerror(errno));
364 close(fd);
365 unlink(cnf.pidfile);
366 return(-1);
367 };
368 if ((s = socket(PF_INET6, SOCK_DGRAM, 0)) == -1)
369 {
370 syslog(LOG_ERR, "error: socket(): %s", strerror(errno));
371 close(s);
372 close(fd);
373 unlink(cnf.pidfile);
374 return(-1);
375 };
376
377 // set socket options
378 opt = 1;
379 if ((rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(int))) == -1)
380 {
381 syslog(LOG_ERR, "error: setsockopt(SO_REUSEADDR): %s", strerror(errno));
382 close(s);
383 close(fd);
384 unlink(cnf.pidfile);
385 return(-1);
386 };
387
388 // bind socket to interface
389 bzero(&sin, sizeof(sin));
390 sin.s6.sin6_family = AF_INET6;
391 sin.s6.sin6_addr = in6addr_any;
392 sin.s6.sin6_port = htons(cnf.port);
393 socklen = sizeof(struct sockaddr_in6);
394 if ((rc = bind(s, (struct sockaddr *)&sin, socklen)) == -1)
395 {
396 syslog(LOG_ERR, "error: bind(): %s", strerror(errno));
397 close(s);
398 close(fd);
399 unlink(cnf.pidfile);
400 return(-1);
401 };
402
403 // log socket address
404 socklen = sizeof(struct sockaddr_in6);
405 if ((rc = getsockname(s, &sin.sa, &socklen)) == -1)
406 {
407 syslog(LOG_ERR, "error: getsockname(): %s", strerror(errno));
408 close(s);
409 close(fd);
410 unlink(cnf.pidfile);
411 return(-1);
412 };
413 inet_ntop(sin.sa.sa_family, &sin, buff, sizeof(buff));
414 syslog(LOG_INFO, "listening on [%s]:%i", buff, cnf.port);
415
416 // fork process
417 if ((cnf.dont_fork))
418 return(s);
419 oldpid = getpid();
420 switch(pid = fork())
421 {
422 case -1:
423 syslog(LOG_ERR, "error: fork(): %s", strerror(errno));
424 break;
425
426 case 0:
427 closelog();
428 openlog(cnf.prog_name, LOG_PID, cnf.facility);
429 syslog(LOG_INFO, "forked from %i", oldpid);
430 break;
431
432 default:
433 syslog(LOG_INFO, "forking to %i", pid);
434 close(fd);
435 close(s);
436 return(0);
437 };
438
439 // record PID in pidfile
440 snprintf(buff, sizeof(buff), "%i", pid);
441 write(fd, buff, strlen(buff));
442 close(fd);
443
444 return(s);
445}
446
447
448// main loop
449int my_loop(int s)
450{
451 socklen_t sinlen;
452 ssize_t ssize;
453 useconds_t delay;
454 char addr_str[INET6_ADDRSTRLEN];
455 short port;
456 struct timespec ts;
457 uint64_t ms;
458 struct pollfd fds[2];
459 union
460 {
461 struct sockaddr sa;
462 struct sockaddr_in s4;
463 struct sockaddr_in6 s6;
464 struct sockaddr_storage ss;
465 } sin;
466 union
467 {
468 char bytes[MY_BUFF_SIZE];
469 struct udp_echo_plus * msg;
470 } udpbuff;
471
472 // setup poller
473 fds[0].fd = s;
474 fds[0].events = POLLIN;
475 fds[0].revents = 0;
476 if ((poll(fds, 1, 1)) < 1)
477 return(0);
478
479 // read data
480 sinlen = sizeof(struct sockaddr_storage);
481 if ((ssize = recvfrom(s, udpbuff.bytes, sizeof(udpbuff), 0, &sin.sa, &sinlen)) == -1)
482 return(-1);
483
484 // grab timestamp
485 clock_gettime(CLOCK_REALTIME, &ts);
486 ms = (uint64_t)(ts.tv_sec * 1000000000);
487 ms += (uint64_t)ts.tv_nsec;
488
489 // parse UDP source address
490 switch(sin.ss.ss_family)
491 {
492 case AF_INET:
493 inet_ntop(AF_INET, &sin.s4, addr_str, sizeof(addr_str));
494 port = ntohs(sin.s4.sin_port);
495 break;
496
497 case AF_INET6:
498 inet_ntop(AF_INET6, &sin.s6, addr_str, sizeof(addr_str));
499 port = ntohs(sin.s6.sin6_port);
500 break;
501
502 default:
503 return(0);
504 };
505
506 // process echo+ packet
507 if ((cnf.echoplus))
508 {
509 udpbuff.msg->res_sn = udpbuff.msg->req_sn;
510 udpbuff.msg->recv_time = htonl(ms & 0xFFFFFFFFLL);
511 udpbuff.msg->failures = 0;
512 };
513 syslog(LOG_INFO,
514 "client: [%s]:%i; bytes: %zi; timestamp: %lu.%09lu; seq: %u;",
515 addr_str,
516 port,
517 ssize,
518 ts.tv_sec,
519 ts.tv_nsec,
520 (((cnf.echoplus)) ? ntohl(udpbuff.msg->req_sn) : 0)
521 );
522
523 // randomly drop packets
524 if (cnf.drop_perct > 0)
525 {
526 if ( (rand() % 100) < cnf.drop_perct)
527 {
528 syslog(LOG_INFO, "dropping echo request");
529 return(0);
530 };
531 };
532
533 // insert random delay
534 delay = 0;
535 if (cnf.delay > 0)
536 {
537 delay = rand() % cnf.delay;
538 usleep(delay);
539 };
540
541 // grab timestamp
542 clock_gettime(CLOCK_REALTIME, &ts);
543 ms = (uint64_t)(ts.tv_sec * 1000000000);
544 ms += (uint64_t)ts.tv_nsec;
545
546 // send response
547 if ((cnf.echoplus))
548 udpbuff.msg->reply_time = htonl(ms & 0xFFFFFFFFLL);
549 sendto(s, udpbuff.bytes, ssize, 0, &sin.sa, sinlen);
550 syslog(LOG_INFO,
551 "client: [%s]:%i; bytes: %zi; timestamp: %lu.%09lu; seq: %u; delay: %u usec;",
552 addr_str,
553 port,
554 ssize,
555 ts.tv_sec,
556 ts.tv_nsec,
557 (((cnf.echoplus)) ? ntohl(udpbuff.msg->req_sn) : 0),
558 delay
559 );
560
561 return(0);
562}
563
564
565// signal handler
566void my_sighandler(int signum)
567{
568 signal(signum, my_sighandler);
569 should_stop = 1;
570 return;
571}
572
573
574// display program usage
575void my_usage(void)
576{
577
578 printf("Usage: %s [options]\n", cnf.prog_name);
579 printf("OPTIONS:\n");
580 printf(" -d percent set packet drop probability percentage\n");
581 printf(" -D microseconds set echo delay range to microseconds\n");
582 printf(" -e enable echo plus\n");
583 printf(" -E disable echo plus\n");
584 printf(" -h display this message and exit\n");
585 printf(" -n do not fork\n");
586 printf(" -p port list on port number\n");
587 printf(" -P file PID file\n");
588 printf(" -v enable verbose output\n");
589 printf(" -V display version and exit\n");
590 printf("\n");
591 return;
592}
593
594
595// display program usage error
596void my_usage_error(const char * fmt, ...)
597{
598 va_list args;
599
600 fprintf(stderr, "%s: ", cnf.prog_name);
601
602 va_start(args, fmt);
603 vfprintf(stderr, fmt, args);
604 va_end(args);
605
606 fprintf(stderr, "\nTry `%s --help' for more information.\n", cnf.prog_name);
607
608 return;
609}
610
611
120/* end of source file */ 612/* end of source file */