UDP Redirect
Loading...
Searching...
No Matches
udp-redirect.c
Go to the documentation of this file.
1
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <errno.h>
36#include <getopt.h>
37#include <sys/types.h>
38#include <sys/socket.h>
39#include <netinet/in.h>
40#include <net/if.h>
41#include <arpa/inet.h>
42#include <fcntl.h>
43#include <poll.h>
44#include <string.h>
45#include <math.h>
46#include <netdb.h>
47#include <time.h>
48
52#define UDP_REDIRECT_VERSION "2.2.0"
53
57#define STATISTICS_DELAY_SECONDS 60
58
62#define NETWORK_BUFFER_SIZE 65535
63
67#define EOK 0
68
72#define MAX_ERRNO 256
73
77#define ERRNO_IGNORE_INIT(X) memset((X), 0, MAX_ERRNO * sizeof(unsigned char))
78
82#define ERRNO_IGNORE_SET(X, Y) if ((Y) >= 0 && (Y) < MAX_ERRNO) (X)[(Y)] = 1
83
87#define ERRNO_IGNORE_CHECK(X, Y) ((Y) >= 0 && (Y) < MAX_ERRNO && (X)[(Y)] == 1)
88
98
104#define PARSE_PORT(src, dest, label) \
105 do { \
106 char *_endptr; \
107 long _val; \
108 errno = EOK; \
109 _val = strtol((src), &_endptr, 10); \
110 if (errno != EOK || _endptr == (src) || *_endptr != '\0' || _val < 0 || _val > 65535) { \
111 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Invalid %s: %s", (label), (src)); \
112 exit(EXIT_FAILURE); \
113 } \
114 (dest) = (int)_val; \
115 } while (0)
116
123#define DEBUG(debug_level_local, debug_level_message, fmt, ...) \
124 do { \
125 if ((debug_level_local) >= (debug_level_message)) { \
126 fprintf(stderr, "%s:%d:%lld:%s(): " fmt "\n", __FILE__, \
127 __LINE__, (long long)(time(NULL)), __func__, ##__VA_ARGS__); \
128 } \
129 } while (0)
130
134static struct option longopts[] = {
135 { "verbose", no_argument, NULL, 'v' },
136 { "debug", no_argument, NULL, 'd' },
137
138 { "listen-address", required_argument, NULL, 'a' },
139 { "listen-port", required_argument, NULL, 'b' },
140 { "listen-interface", required_argument, NULL, 'c' },
141
142 { "connect-address", required_argument, NULL, 'g' },
143 { "connect-host", required_argument, NULL, 'h' },
144 { "connect-port", required_argument, NULL, 'i' },
145
146 { "send-address", required_argument, NULL, 'm' },
147 { "send-port", required_argument, NULL, 'n' },
148 { "send-interface", required_argument, NULL, 'o' },
149
150 { "listen-address-strict", no_argument, NULL, 'x' },
151 { "connect-address-strict",no_argument, NULL, 'y' },
152
153 { "listen-sender-address", required_argument, NULL, 'k' },
154 { "listen-sender-port", required_argument, NULL, 'l' },
155
156 { "ignore-errors", no_argument, NULL, 'r' },
157 { "stop-errors", no_argument, NULL, 's' },
158
159 { "stats", no_argument, NULL, 'q' },
160
161 { "version", no_argument, NULL, 'z' },
162
163 { NULL, 0, NULL, 0 }
164};
165
169struct settings {
170 char *laddr;
171 int lport;
172 char *lif;
173
174 char *caddr;
175 char *chost;
176 int cport;
177
178 char *saddr;
179 int sport;
180 char *sif;
181
184
185 char *lsaddr;
186 int lsport;
187
189
190 int stats;
191};
192
228
229/* Function prototypes */
230
231static int socket_setup(const int debug_level, const char *desc, const char *xaddr, const int xport, const char *xif, int xfamily, struct sockaddr_storage *xsock_name);
232static char *resolve_host(int debug_level, const char *host);
233
234static int parse_addr(const char *str, int port, struct sockaddr_storage *out);
235static const char *addr_tostring(const struct sockaddr_storage *sa, char *buf, size_t len);
236static int addr_port(const struct sockaddr_storage *sa);
237static socklen_t addr_len(const struct sockaddr_storage *sa);
238static int addr_is_unset(const struct sockaddr_storage *sa);
239static int addr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b);
240
241void settings_initialize(struct settings *s);
242void usage(const char *argv0, const char *message);
243
244void statistics_initialize(struct statistics *st);
245double int_to_human_value(double value);
246char int_to_human_char(double value);
247void statistics_display(int debug_level, struct statistics *st, time_t now);
248
257int main(int argc, char *argv[]) {
258 /* Store debug level and program name */
259 int debug_level = DEBUG_LEVEL_ERROR;
260 char *argv0 = argv[0];
261
262 int ch; /* Used by getopt_long */
263
264 /* Command line arguments */
265 struct settings s;
266
267 struct statistics st;
268
269 time_t now;
270
271 int lsock; /* Listen socket */
272 int ssock; /* Send socket */
273
274 struct sockaddr_storage lsock_name; /* Listen socket name */
275 struct sockaddr_storage ssock_name; /* Send socket name */
276
277 struct sockaddr_storage caddr; /* Connect address */
278
279 /* Simplify addr_tostring() usage in DEBUG() by reserving buffers */
280 char print_buffer1[INET6_ADDRSTRLEN];
281 char print_buffer2[INET6_ADDRSTRLEN];
282
283 static char network_buffer[NETWORK_BUFFER_SIZE]; /* The network buffer. All reads and writes happen here */
284
285 struct pollfd ufds[2]; /* Poll file descriptors */
286
287 struct sockaddr_storage endpoint; /* Address where the current packet was received from */
288 struct sockaddr_storage previous_endpoint; /* Address where the previous packet was received from */
289
290 unsigned char errno_ignore[MAX_ERRNO];
291
294
295 while ((ch = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
296 switch (ch) {
297 case 'v': /* --verbose */
298 if (debug_level < DEBUG_LEVEL_VERBOSE) {
299 debug_level = DEBUG_LEVEL_VERBOSE;
300 } else {
301 debug_level = debug_level + 1;
302 }
303
304 break;
305 case 'd': /* --debug */
306 debug_level = DEBUG_LEVEL_DEBUG;
307
308 break;
309 case 'a': /* --listen-address */
310 s.laddr = optarg;
311
312 break;
313 case 'b': /* --listen-port */
314 PARSE_PORT(optarg, s.lport, "listen port");
315
316 break;
317 case 'c': /* --listeninterface */
318 s.lif = optarg;
319
320 break;
321 case 'g': /* --connect-address */
322 s.caddr = optarg;
323
324 break;
325 case 'h': /* --connect-host */
326 s.chost = optarg;
327
328 break;
329 case 'i': /* --connect-port */
330 PARSE_PORT(optarg, s.cport, "connect port");
331
332 break;
333 case 'm': /* --send-address */
334 s.saddr = optarg;
335
336 break;
337 case 'n': /* --send-port */
338 PARSE_PORT(optarg, s.sport, "send port");
339
340 break;
341 case 'o': /* --send-interface */
342 s.sif = optarg;
343
344 break;
345 case 'x': /* --listen-address-strict */
346 s.lstrict = 1;
347
348 break;
349 case 'y': /* --connect-address-strict */
350 s.cstrict = 1;
351
352 break;
353 case 'k': /* --listen-sender-address */
354 s.lsaddr = optarg;
355
356 break;
357 case 'l': /* --listen-sender-port */
358 PARSE_PORT(optarg, s.lsport, "listen sender port");
359
360 break;
361 case 'r': /* --ignore-errors */
362 s.eignore = 1;
363
364 break;
365 case 's': /* --stop-errors */
366 s.eignore = 0;
367
368 break;
369 case 'q': /* --stats */
370 s.stats = 1;
371
372 break;
373 case 'z': /* --version */
374 fprintf(stderr, "udp-redirect v%s\n", UDP_REDIRECT_VERSION);
375 exit(EXIT_SUCCESS);
376
377 break;
378 default:
379 usage(argv0, NULL);
380 break;
381 }
382 }
383 argc -= optind;
384 argv += optind;
385
386 if (argc != 0) {
387 usage(argv0, "Unknown argument");
388 }
389
390 if (s.lport == 0) {
391 usage(argv0, "Listen port not specified");
392 }
393
394 if (s.caddr == NULL && s.chost == NULL) {
395 usage(argv0, "Connect host or address not specified");
396 }
397
398 if (s.cport == 0) {
399 usage(argv0, "Connect port not specified");
400 }
401
402 if ((s.lsaddr != NULL && s.lsport == 0) ||
403 (s.lsaddr == NULL && s.lsport != 0)) {
404 usage(argv0, "Options --listen-sender-port and --list-sender-address must either both be specified or none");
405 }
406
407 /* Set strict mode if using lsport and csport */
408 if (s.lsaddr != NULL && s.lsport != 0) {
409 s.lstrict = 1;
410 }
411
412 /* Resolve connect host if available */
413 char *caddr_alloc = NULL;
414 if (s.chost != NULL) {
415 caddr_alloc = resolve_host(debug_level, s.chost);
416 s.caddr = caddr_alloc;
417 }
418
419 DEBUG(debug_level, DEBUG_LEVEL_INFO, "---- INFO ----");
420
421 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Listen address: %s", (s.laddr != NULL)?s.laddr:"ANY");
422 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Listen port: %d", s.lport);
423 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Listen interface: %s", (s.lif != NULL)?s.lif:"ANY");
424
425 if (s.chost != NULL) {
426 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Connect host: %s", s.chost);
427 }
428
429 if (s.caddr != NULL) {
430 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Connect address: %s", s.caddr);
431 }
432
433 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Connect port: %d", s.cport);
434
435 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Send address: %s", (s.saddr != NULL)?s.saddr:"ANY");
436 if (s.sport != 0) {
437 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Send port: %d", s.sport);
438 } else {
439 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Send port: %s", "ANY");
440 }
441 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Send interface: %s", (s.sif != NULL)?s.sif:"ANY");
442
443 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Listen strict: %s", s.lstrict?"ENABLED":"DISABLED");
444 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Connect strict: %s", s.cstrict?"ENABLED":"DISABLED");
445
446 if (s.lsaddr != NULL) {
447 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Listen only accepts packets from address: %s", s.lsaddr);
448 }
449 if (s.lsport != 0) {
450 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Listen only accepts packets from port: %d", s.lsport);
451 }
452
453 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Ignore errors: %s", s.eignore?"ENABLED":"DISABLED");
454 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Display stats: %s", s.stats?"ENABLED":"DISABLED");
455
456 DEBUG(debug_level, DEBUG_LEVEL_INFO, "---- START ----");
457
458 /* Set up connect address first so we know the family for the sockets below */
459 if (parse_addr(s.caddr, s.cport, &caddr) == -1) {
460 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Invalid connect address: %s", s.caddr);
461
462 if (caddr_alloc != NULL) {
463 free(caddr_alloc);
464 }
465 exit(EXIT_FAILURE);
466 }
467 if (caddr_alloc != NULL) {
468 free(caddr_alloc);
469 s.caddr = NULL;
470 }
471
472 /* Determine listen socket family independently from the connect family.
473 * When --listen-address is given its family drives lsock, enabling
474 * cross-family forwarding (e.g. IPv4 listen with IPv6 connect).
475 * When no listen address is given, fall back to the connect family. */
476 int lsock_family = (int)caddr.ss_family;
477 if (s.laddr != NULL) {
478 struct sockaddr_storage tmp;
479 memset(&tmp, 0, sizeof(tmp));
480 int f = parse_addr(s.laddr, 0, &tmp);
481 if (f != -1)
482 lsock_family = f;
483 /* parse failure is caught and reported inside socket_setup */
484 }
485
486 if (lsock_family != (int)caddr.ss_family) {
487 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Cross-family forwarding: listen %s, connect/send %s",
488 (lsock_family == AF_INET6) ? "IPv6" : "IPv4",
489 ((int)caddr.ss_family == AF_INET6) ? "IPv6" : "IPv4");
490 }
491
492 /* Validate: --send-address family must match --connect-address family */
493 if (s.saddr != NULL) {
494 struct sockaddr_storage tmp;
495 memset(&tmp, 0, sizeof(tmp));
496 int f = parse_addr(s.saddr, 0, &tmp);
497 if (f != -1 && f != (int)caddr.ss_family) {
498 DEBUG(debug_level, DEBUG_LEVEL_ERROR,
499 "Send address family (%s) does not match connect address family (%s)",
500 (f == AF_INET6) ? "IPv6" : "IPv4",
501 ((int)caddr.ss_family == AF_INET6) ? "IPv6" : "IPv4");
502 exit(EXIT_FAILURE);
503 }
504 }
505
506 lsock = socket_setup(debug_level, "Listen", s.laddr, s.lport, s.lif, lsock_family, &lsock_name); /* Set up listening socket */
507 ssock = socket_setup(debug_level, "Send", s.saddr, s.sport, s.sif, (int)caddr.ss_family, &ssock_name); /* Set up send socket */
508
509 memset(&endpoint, 0, sizeof(endpoint)); /* No packet received, no endpoint */
510
511 /* previous_endpoint: ss_family == 0 (zero-init) means "no endpoint seen yet" */
512 memset(&previous_endpoint, 0, sizeof(previous_endpoint));
513 if (s.lsaddr != NULL && s.lsport != 0) {
514 if (parse_addr(s.lsaddr, s.lsport, &previous_endpoint) == -1) {
515 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Invalid listen sender address: %s", s.lsaddr);
516
517 exit(EXIT_FAILURE);
518 }
519 }
520
521 socklen_t endpoint_len = sizeof(endpoint);
522
523 ERRNO_IGNORE_INIT(errno_ignore);
524 ERRNO_IGNORE_SET(errno_ignore, EINTR); /* Always ignore EINTR */
525
526 if (s.eignore == 1) { /* List of harmless recvfrom / sendto errors. Possibly incorrect. */
527 ERRNO_IGNORE_SET(errno_ignore, EAGAIN);
528 ERRNO_IGNORE_SET(errno_ignore, EHOSTUNREACH);
529 ERRNO_IGNORE_SET(errno_ignore, ENETDOWN);
530 ERRNO_IGNORE_SET(errno_ignore, ENETUNREACH);
531 ERRNO_IGNORE_SET(errno_ignore, ENOBUFS);
532 ERRNO_IGNORE_SET(errno_ignore, EPIPE);
533 ERRNO_IGNORE_SET(errno_ignore, EADDRNOTAVAIL);
534 }
535
536 DEBUG(debug_level, DEBUG_LEVEL_VERBOSE, "entering infinite loop");
537
538 st.time_display_first = st.time_display_last = time(NULL);
539
540 /* Main loop */
541 while (1) {
542 int poll_retval;
543 ssize_t recvfrom_retval;
544 ssize_t sendto_retval;
545
546 now = time(NULL);
547
548 ufds[0].fd = lsock; ufds[0].events = POLLIN | POLLPRI; ufds[0].revents = 0;
549 ufds[1].fd = ssock; ufds[1].events = POLLIN | POLLPRI; ufds[1].revents = 0;
550
551 DEBUG(debug_level, DEBUG_LEVEL_DEBUG, "waiting for readable sockets");
552
553 if (s.stats && (now - st.time_display_last) >= STATISTICS_DELAY_SECONDS) {
554 statistics_display(debug_level, &st, now);
555 st.time_display_last = now;
556 }
557
558 if ((poll_retval = poll(ufds, 2, 1000)) == -1) {
559 if (errno == EINTR) {
560 continue;
561 }
562
563 perror("poll");
564 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Could not check readable sockets (%d)", errno);
565
566 exit(EXIT_FAILURE);
567 }
568 if (poll_retval == 0) {
569 DEBUG(debug_level, DEBUG_LEVEL_DEBUG, "poll timeout");
570 continue;
571 }
572
573 /* New data on the LISTEN socket */
574 if (ufds[0].revents & POLLIN || ufds[0].revents & POLLPRI) {
575 endpoint_len = sizeof(endpoint);
576 if ((recvfrom_retval = recvfrom(lsock, network_buffer, sizeof(network_buffer), 0,
577 (struct sockaddr *)&endpoint, &endpoint_len)) == -1) {
578 if (!ERRNO_IGNORE_CHECK(errno_ignore, errno)) {
579 perror("recvfrom");
580 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Listen cannot receive (%d)", errno);
581
582 exit(EXIT_FAILURE);
583 }
584 }
585 if (recvfrom_retval >= 0) {
587 st.count_listen_byte_receive += recvfrom_retval;
588
589 DEBUG(debug_level, DEBUG_LEVEL_DEBUG, "RECEIVE (%s, %d) -> (%s, %d) (LISTEN PORT): %zd bytes",
590 addr_tostring(&endpoint, print_buffer1, sizeof(print_buffer1)), addr_port(&endpoint),
591 addr_tostring(&lsock_name, print_buffer2, sizeof(print_buffer2)), addr_port(&lsock_name),
592 recvfrom_retval);
593
599 if ((addr_is_unset(&previous_endpoint) || !s.lstrict) ||
600 addr_equal(&previous_endpoint, &endpoint)) {
601
602 if (addr_is_unset(&previous_endpoint) || !s.lstrict) {
603 if (!addr_equal(&previous_endpoint, &endpoint)) {
604 DEBUG(debug_level, DEBUG_LEVEL_DEBUG, "LISTEN remote endpoint set to (%s, %d)", addr_tostring(&endpoint, print_buffer1, sizeof(print_buffer1)), addr_port(&endpoint));
605 }
606
607 memcpy(&previous_endpoint, &endpoint, sizeof(endpoint));
608 }
609
610 if ((sendto_retval = sendto(ssock, network_buffer, recvfrom_retval, 0,
611 (struct sockaddr *)&caddr, addr_len(&caddr))) == -1) {
612 if (!ERRNO_IGNORE_CHECK(errno_ignore, errno)) {
613 perror("sendto");
614 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot send packet to send port (%d)", errno);
615
616 exit(EXIT_FAILURE);
617 }
618 } else { // At least one byte was sent, record it
620 st.count_connect_byte_send += sendto_retval;
621 }
622
623 DEBUG(debug_level, (sendto_retval == recvfrom_retval || s.eignore == 1)?DEBUG_LEVEL_DEBUG:DEBUG_LEVEL_ERROR,
624 "SEND (%s, %d) -> (%s, %d) (SEND PORT): %zd bytes (%s WRITE %zd bytes)",
625 addr_tostring(&ssock_name, print_buffer1, sizeof(print_buffer1)), addr_port(&ssock_name),
626 addr_tostring(&caddr, print_buffer2, sizeof(print_buffer2)), addr_port(&caddr),
627 sendto_retval,
628 (sendto_retval == recvfrom_retval)?"FULL":"PARTIAL", recvfrom_retval);
629 } else {
630 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "LISTEN PORT invalid source (%s, %d), was expecting (%s, %d)",
631 addr_tostring(&endpoint, print_buffer1, sizeof(print_buffer1)), addr_port(&endpoint),
632 addr_tostring(&previous_endpoint, print_buffer2, sizeof(print_buffer2)), addr_port(&previous_endpoint));
633 }
634 }
635 }
636
637 /* New data on the SEND socket */
638 if (ufds[1].revents & POLLIN || ufds[1].revents & POLLPRI) {
639 endpoint_len = sizeof(endpoint);
640 if ((recvfrom_retval = recvfrom(ssock, network_buffer, sizeof(network_buffer), 0,
641 (struct sockaddr *)&endpoint, &endpoint_len)) == -1) {
642 if (!ERRNO_IGNORE_CHECK(errno_ignore, errno)) {
643 perror("recvfrom");
644 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Send cannot receive packet (%d)", errno);
645
646 exit(EXIT_FAILURE);
647 }
648 }
649 if (recvfrom_retval >= 0) {
651 st.count_connect_byte_receive += recvfrom_retval;
652
653 DEBUG(debug_level, DEBUG_LEVEL_DEBUG, "RECEIVE (%s, %d) -> (%s, %d) (SEND PORT): %zd bytes",
654 addr_tostring(&endpoint, print_buffer1, sizeof(print_buffer1)), addr_port(&endpoint),
655 addr_tostring(&ssock_name, print_buffer2, sizeof(print_buffer2)), addr_port(&ssock_name),
656 recvfrom_retval);
657
663 if (!addr_is_unset(&previous_endpoint) &&
664 (!s.cstrict || addr_equal(&caddr, &endpoint))) {
665
666 if ((sendto_retval = sendto(lsock, network_buffer, recvfrom_retval, 0,
667 (struct sockaddr *)&previous_endpoint, addr_len(&previous_endpoint))) == -1) {
668 if (!ERRNO_IGNORE_CHECK(errno_ignore, errno)) {
669 perror("sendto");
670 DEBUG(debug_level, DEBUG_LEVEL_INFO, "Cannot send packet to listen port (%d)", errno);
671
672 exit(EXIT_FAILURE);
673 }
674 } else { // At least one byte was sent, record it
676 st.count_listen_byte_send += sendto_retval;
677 }
678
679 DEBUG(debug_level, (sendto_retval == recvfrom_retval || s.eignore == 1)?DEBUG_LEVEL_DEBUG:DEBUG_LEVEL_ERROR,
680 "SEND (%s, %d) -> (%s, %d) (LISTEN PORT): %zd bytes (%s WRITE %zd bytes)",
681 addr_tostring(&lsock_name, print_buffer1, sizeof(print_buffer1)), addr_port(&lsock_name),
682 addr_tostring(&previous_endpoint, print_buffer2, sizeof(print_buffer2)), addr_port(&previous_endpoint),
683 sendto_retval,
684 (sendto_retval == recvfrom_retval)?"FULL":"PARTIAL", recvfrom_retval);
685 } else {
686 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "SEND PORT invalid source (%s, %d), was expecting (%s, %d)",
687 addr_tostring(&endpoint, print_buffer1, sizeof(print_buffer1)), addr_port(&endpoint),
688 addr_tostring(&caddr, print_buffer2, sizeof(print_buffer2)), addr_port(&caddr));
689 }
690 }
691 }
692 }
693
694 /* Never reached. */
695 return 0;
696}
697
698/* Network helper functions below */
699
708static int parse_addr(const char *str, int port, struct sockaddr_storage *out) {
709 struct sockaddr_in *a4 = (struct sockaddr_in *)out;
710 struct sockaddr_in6 *a6 = (struct sockaddr_in6 *)out;
711
712 memset(out, 0, sizeof(*out));
713
714 /* Reject IPv6 zone IDs (e.g. "fe80::1%en0"): inet_pton on macOS silently
715 * accepts them but drops the scope, leading to inconsistent behaviour
716 * across platforms. Zone IDs are not supported. */
717 if (strchr(str, '%') != NULL)
718 return -1;
719
720 if (inet_pton(AF_INET, str, &a4->sin_addr) == 1) {
721 out->ss_family = AF_INET;
722 a4->sin_port = htons(port);
723 return AF_INET;
724 }
725 if (inet_pton(AF_INET6, str, &a6->sin6_addr) == 1) {
726 out->ss_family = AF_INET6;
727 a6->sin6_port = htons(port);
728 return AF_INET6;
729 }
730 return -1;
731}
732
737static const char *addr_tostring(const struct sockaddr_storage *sa, char *buf, size_t len) {
738 const void *ptr;
739 if (sa->ss_family == AF_INET6)
740 ptr = &((const struct sockaddr_in6 *)sa)->sin6_addr;
741 else
742 ptr = &((const struct sockaddr_in *)sa)->sin_addr;
743 return inet_ntop((int)sa->ss_family, ptr, buf, (socklen_t)len) ? buf : "?";
744}
745
749static int addr_port(const struct sockaddr_storage *sa) {
750 if (sa->ss_family == AF_INET6)
751 return ntohs(((const struct sockaddr_in6 *)sa)->sin6_port);
752 return ntohs(((const struct sockaddr_in *)sa)->sin_port);
753}
754
758static socklen_t addr_len(const struct sockaddr_storage *sa) {
759 return (sa->ss_family == AF_INET6)
760 ? (socklen_t)sizeof(struct sockaddr_in6)
761 : (socklen_t)sizeof(struct sockaddr_in);
762}
763
767static int addr_is_unset(const struct sockaddr_storage *sa) {
768 return sa->ss_family == 0;
769}
770
774static int addr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b) {
775 if (a->ss_family != b->ss_family)
776 return 0;
777 if (a->ss_family == AF_INET) {
778 const struct sockaddr_in *a4 = (const struct sockaddr_in *)a;
779 const struct sockaddr_in *b4 = (const struct sockaddr_in *)b;
780 return a4->sin_addr.s_addr == b4->sin_addr.s_addr &&
781 a4->sin_port == b4->sin_port;
782 }
783 if (a->ss_family == AF_INET6) {
784 const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)a;
785 const struct sockaddr_in6 *b6 = (const struct sockaddr_in6 *)b;
786 return memcmp(&a6->sin6_addr, &b6->sin6_addr, sizeof(struct in6_addr)) == 0 &&
787 a6->sin6_port == b6->sin6_port;
788 }
789 return 0;
790}
791
806static int socket_setup(const int debug_level, const char *desc, const char *xaddr, const int xport, const char *xif, int xfamily, struct sockaddr_storage *xsock_name) {
807 int xsock;
808 const int enable = 1;
809 int family;
810 struct sockaddr_storage addr;
811
812 memset(&addr, 0, sizeof(addr));
813
814 /* Determine address family and fill bind address */
815 if (xaddr != NULL) {
816 if (parse_addr(xaddr, xport, &addr) == -1) {
817 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "%s address invalid: %s", desc, xaddr);
818
819 exit(EXIT_FAILURE);
820 }
821 family = (int)addr.ss_family;
822 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: bind to address %s", desc, xaddr);
823 } else {
824 family = xfamily;
825 addr.ss_family = (sa_family_t)family;
826 if (family == AF_INET6) {
827 ((struct sockaddr_in6 *)&addr)->sin6_addr = in6addr_any;
828 ((struct sockaddr_in6 *)&addr)->sin6_port = htons(xport);
829 } else {
830 ((struct sockaddr_in *)&addr)->sin_addr.s_addr = INADDR_ANY;
831 ((struct sockaddr_in *)&addr)->sin_port = htons(xport);
832 }
833 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: bind to address %s", desc, "ANY");
834 }
835
836 /* Port specified or any */
837 if (xport != 0) {
838 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: bind to port %d", desc, xport);
839 } else {
840 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: bind to port %s", desc, "ANY");
841 }
842
843 /* Set up socket */
844 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: create", desc);
845 if ((xsock = socket(family, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
846 perror("socket");
847 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot create DGRAM socket (%d)", errno);
848
849 exit(EXIT_FAILURE);
850 }
851
852 if (xif != NULL) {
853#ifdef __MACH__
854 unsigned int xif_idx;
855
856 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: bind to interface %s", desc, xif);
857
858 if ((xif_idx = if_nametoindex(xif)) == 0) {
859 perror("if_nametoindex");
860 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot get the interface ID (%d)", errno);
861
862 exit(EXIT_FAILURE);
863 }
864
865 {
866 int if_level = (family == AF_INET6) ? IPPROTO_IPV6 : IPPROTO_IP;
867 int if_optname = (family == AF_INET6) ? IPV6_BOUND_IF : IP_BOUND_IF;
868 if (setsockopt(xsock, if_level, if_optname, &xif_idx, sizeof(xif_idx)) == -1) {
869 perror("setsockopt");
870 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot set socket interface (%d)", errno);
871
872 exit(EXIT_FAILURE);
873 }
874 }
875#elif __unix__
876 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: bind to interface %s", desc, xif);
877
878 if (strlen(xif) >= IFNAMSIZ) {
879 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Interface name too long (max %d): %s", IFNAMSIZ - 1, xif);
880 exit(EXIT_FAILURE);
881 }
882
883 if (setsockopt(xsock, SOL_SOCKET, SO_BINDTODEVICE, xif, strlen(xif) + 1) == -1) {
884 perror("setsockopt");
885 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot set socket interface (%d)", errno);
886
887 exit(EXIT_FAILURE);
888 }
889#endif
890 } else {
891 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: bind to interface %s", desc, "ANY");
892 }
893
894 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: reuse local address", desc);
895
896 if (setsockopt(xsock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
897 perror("setsockopt");
898 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot set socket SO_REUSEADDR (%d)", errno);
899
900 exit(EXIT_FAILURE);
901 }
902
903 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: set nonblocking", desc);
904 if (fcntl(xsock, F_SETFL, O_NONBLOCK) == -1) {
905 perror("fcntl");
906 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot set socket O_NONBLOCK (%d)", errno);
907
908 exit(EXIT_FAILURE);
909 }
910
911 DEBUG(debug_level, DEBUG_LEVEL_INFO, "%s socket: bind", desc);
912 if (bind(xsock, (struct sockaddr *)&addr, addr_len(&addr)) == -1) {
913 perror("bind");
914 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot bind socket (%d)", errno);
915
916 exit(EXIT_FAILURE);
917 }
918
919 socklen_t xsock_name_len = sizeof(*xsock_name);
920 if (getsockname(xsock, (struct sockaddr *)xsock_name, &xsock_name_len) == -1) {
921 perror("getsockname");
922 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Cannot get socket name (%d)", errno);
923
924 exit(EXIT_FAILURE);
925 }
926
927 return xsock;
928}
929
939static char *resolve_host(int debug_level, const char *host) {
940 struct addrinfo hints;
941 struct addrinfo *res;
942 char buf[INET6_ADDRSTRLEN];
943 char *retval;
944 int rc;
945
946 memset(&hints, 0, sizeof(hints));
947 hints.ai_family = AF_UNSPEC; /* accept IPv4 or IPv6 */
948 hints.ai_socktype = SOCK_DGRAM;
949
950 if ((rc = getaddrinfo(host, NULL, &hints, &res)) != 0) {
951 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Could not resolve host %s: %s", host, gai_strerror(rc));
952
953 exit(EXIT_FAILURE);
954 }
955
956 /* Use the first result; inet_ntop converts it back to a string for parse_addr() */
957 {
958 const void *addr_ptr = (res->ai_family == AF_INET6)
959 ? (const void *)&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr
960 : (const void *)&((struct sockaddr_in *)res->ai_addr)->sin_addr;
961
962 if (inet_ntop(res->ai_family, addr_ptr, buf, sizeof(buf)) == NULL) {
963 perror("inet_ntop");
964 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Could not format resolved address (%d)", errno);
965 freeaddrinfo(res);
966
967 exit(EXIT_FAILURE);
968 }
969 }
970
971 freeaddrinfo(res);
972
973 if ((retval = strdup(buf)) == NULL) {
974 perror("strdup");
975 DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Could not duplicate resolved address string (%d)", errno);
976
977 exit(EXIT_FAILURE);
978 }
979
980 DEBUG(debug_level, DEBUG_LEVEL_DEBUG, "Resolved %s to %s", host, retval);
981
982 return retval;
983}
984
985/* Settings helper functions below */
986
992 s->laddr = NULL;
993 s->lport = 0;
994 s->lif = NULL;
995
996 s->caddr = NULL;
997 s->chost = NULL;
998 s->cport = 0;
999
1000 s->saddr = NULL;
1001 s->sport = 0;
1002 s->sif = NULL;
1003
1004 s->lstrict = 0;
1005 s->cstrict = 0;
1006
1007 s->lsaddr = NULL;
1008 s->lsport = 0;
1009
1010 s->eignore = 1;
1011 s->stats = 0;
1012}
1013
1021void usage(const char *argv0, const char *message) {
1022 if (message != NULL)
1023 fprintf(stderr, "%s\n", message);
1024
1025 fprintf(stderr, "Usage: %s\n", argv0);
1026 fprintf(stderr, " [--listen-address <address>] --listen-port <port> [--listen-interface <interface>]\n");
1027 fprintf(stderr, " [--connect-address <address> | --connect-host <hostname> --connect-port <port>\n");
1028 fprintf(stderr, " [--send-address <address>] [--send-port <port>] [--send-interface <interface>]\n");
1029 fprintf(stderr, " [--listen-address-strict] [--connect-address-strict]\n");
1030 fprintf(stderr, " [--listen-sender-address <address>] [--listen-sender-port <port>]\n");
1031 fprintf(stderr, " [--ignore-errors] [--stop-errors]\n");
1032 fprintf(stderr, " [--stats] [--verbose] [--debug] [--version]\n");
1033 fprintf(stderr, "\n");
1034 fprintf(stderr, "--stats Display sent/received bytes statistics every 60 seconds (optional)\n");
1035 fprintf(stderr, "--verbose Verbose mode, can be specified multiple times (optional)\n");
1036 fprintf(stderr, "--debug Debug mode (optional)\n");
1037 fprintf(stderr, "--version Display the version and exit\n");
1038 fprintf(stderr, "\n");
1039 fprintf(stderr, "--listen-address <address> Listen address, IPv4 or IPv6 (optional)\n");
1040 fprintf(stderr, "--listen-port <port> Listen port (required)\n");
1041 fprintf(stderr, "--listen-interface <interface> Listen interface name (optional)\n");
1042 fprintf(stderr, "--listen-address-strict Only receive packets from the same source as the first packet (optional)\n");
1043 fprintf(stderr, "\n");
1044 fprintf(stderr, "--connect-address <address> Connect address, IPv4 or IPv6 (optional if --connect-host is specified)\n");
1045 fprintf(stderr, "--connect-host <hostname> Connect host, overwrites --connect-address if both are specified (optional if --connect-address is specified)\n");
1046 fprintf(stderr, "--connect-port <port> Connect port (required)\n");
1047 fprintf(stderr, "--connect-address-strict Only receive packets from --connect-address / --connect-port (optional)\n");
1048 fprintf(stderr, "\n");
1049 fprintf(stderr, "--send-address <address> Send packets from address, IPv4 or IPv6 (optional)\n");
1050 fprintf(stderr, "--send-port <port> Send packets from port (optional)\n");
1051 fprintf(stderr, "--send-interface <interface> Send packets from interface (optional)\n");
1052 fprintf(stderr, "\n");
1053 fprintf(stderr, "--listen-sender-address <address> Listen endpoint only accepts packets from this source address, IPv4 or IPv6 (optional)\n");
1054 fprintf(stderr, "--listen-sender-port <port> Listen endpoint only accepts packets from this source port (optional)\n");
1055 fprintf(stderr, " (must be set together, --listen-address-strict is implied)\n");
1056 fprintf(stderr, "\n");
1057 fprintf(stderr, "--ignore-errors Ignore most receive or send errors (unreachable, etc.) instead of exiting (optional) (default)\n");
1058 fprintf(stderr, "--stop-errors Exit on most receive or send errors (unreachable, etc.) (optional)\n");
1059 fprintf(stderr, "\n");
1060
1061 exit(EXIT_FAILURE);
1062}
1063
1064/* Statistics helper functions below */
1065
1069#define HUMAN_READABLE_FORMAT "%.1lf%c"
1070
1074#define HRF HUMAN_READABLE_FORMAT
1075
1079#define HUMAN_READABLE(X) int_to_human_value((X)), int_to_human_char((X))
1080
1084#define HUMAN_READABLE_SIZES { ' ', 'K', 'M', 'G', 'T', 'P', 'E' }
1085
1089#define HUMAN_READABLE_SIZES_COUNT 7
1090
1123
1132static double int_to_human_scale(double value, int *count_out) {
1133 int count = 0;
1134
1135 while (value >= 1000 && count < (HUMAN_READABLE_SIZES_COUNT - 1)) {
1136 value = value / 1000;
1137 count = count + 1;
1138 }
1139
1140 *count_out = count;
1141 return value;
1142}
1143
1149double int_to_human_value(double value) {
1150 int count;
1151 return int_to_human_scale(value, &count);
1152}
1153
1159char int_to_human_char(double value) {
1160 static const char human_readable_sizes[] = HUMAN_READABLE_SIZES;
1161 int count;
1162 int_to_human_scale(value, &count);
1163 return human_readable_sizes[count];
1164}
1165
1172void statistics_display(int debug_level, struct statistics *st, time_t now) {
1173 /* Stats were explicitly requested: ensure they're visible at any verbosity level. */
1174 if (debug_level < DEBUG_LEVEL_INFO)
1175 debug_level = DEBUG_LEVEL_INFO;
1176
1177 time_t time_delta = now - st->time_display_last;
1178 time_t time_delta_total = now - st->time_display_first;
1179
1180 /* Clamp to 1 to avoid division by zero when called within the same second. */
1181 if (time_delta < 1)
1182 time_delta = 1;
1183 if (time_delta_total < 1)
1184 time_delta_total = 1;
1185
1190
1195
1196 DEBUG(debug_level, DEBUG_LEVEL_INFO, "---- STATS %ds ----", STATISTICS_DELAY_SECONDS);
1197
1198 DEBUG(debug_level, DEBUG_LEVEL_INFO, "listen:receive:packets: " HRF " (" HRF "/s), listen:receive:bytes: " HRF " (" HRF "/s)",
1200 HUMAN_READABLE((double)st->count_listen_packet_receive / time_delta),
1202 HUMAN_READABLE((double)st->count_listen_byte_receive / time_delta));
1203 DEBUG(debug_level, DEBUG_LEVEL_INFO, "listen:send:packets: " HRF " (" HRF "/s), listen:send:bytes: " HRF " (" HRF "/s)",
1205 HUMAN_READABLE((double)st->count_listen_packet_send / time_delta),
1207 HUMAN_READABLE((double)st->count_listen_byte_send / time_delta));
1208 DEBUG(debug_level, DEBUG_LEVEL_INFO, "connect:receive:packets: " HRF " (" HRF "/s), connect:receive:bytes: " HRF " (" HRF "/s)",
1210 HUMAN_READABLE((double)st->count_connect_packet_receive / time_delta),
1212 HUMAN_READABLE((double)st->count_connect_byte_receive / time_delta));
1213 DEBUG(debug_level, DEBUG_LEVEL_INFO, "connect:send:packets: " HRF " (" HRF "/s), connect:send:bytes: " HRF " (" HRF "/s)",
1215 HUMAN_READABLE((double)st->count_connect_packet_send / time_delta),
1217 HUMAN_READABLE((double)st->count_connect_byte_send / time_delta));
1218
1219 DEBUG(debug_level, DEBUG_LEVEL_INFO, "---- STATS TOTAL ----");
1220
1221 DEBUG(debug_level, DEBUG_LEVEL_INFO, "listen:receive:packets: " HRF " (" HRF "/s), listen:receive:bytes: " HRF " (" HRF "/s)",
1223 HUMAN_READABLE((double)st->count_listen_packet_receive_total / time_delta_total),
1225 HUMAN_READABLE((double)st->count_listen_byte_receive_total / time_delta_total));
1226 DEBUG(debug_level, DEBUG_LEVEL_INFO, "listen:send:packets: " HRF " (" HRF "/s), listen:send:bytes: " HRF " (" HRF "/s)",
1228 HUMAN_READABLE((double)st->count_listen_packet_send_total / time_delta_total),
1230 HUMAN_READABLE((double)st->count_listen_byte_send_total / time_delta_total));
1231 DEBUG(debug_level, DEBUG_LEVEL_INFO, "connect:receive:packets: " HRF " (" HRF "/s), connect:receive:bytes: " HRF " (" HRF "/s)",
1233 HUMAN_READABLE((double)st->count_connect_packet_receive_total / time_delta_total),
1235 HUMAN_READABLE((double)st->count_connect_byte_receive_total / time_delta_total));
1236 DEBUG(debug_level, DEBUG_LEVEL_INFO, "connect:send:packets: " HRF " (" HRF "/s), connect:send:bytes: " HRF " (" HRF "/s)",
1238 HUMAN_READABLE((double)st->count_connect_packet_send_total / time_delta_total),
1240 HUMAN_READABLE((double)st->count_connect_byte_send_total / time_delta_total));
1241
1246}
Definition udp-redirect.c:169
int lstrict
Strict mode for listener (set endpoint on first packet arrival).
Definition udp-redirect.c:182
char * lsaddr
Listen port expects packets from this address.
Definition udp-redirect.c:185
int cstrict
Strict mode for sender (only accept from caddr / cport).
Definition udp-redirect.c:183
char * laddr
Listen address.
Definition udp-redirect.c:170
char * lif
Listen interface.
Definition udp-redirect.c:172
int lport
Listen port.
Definition udp-redirect.c:171
int sport
Send packets from port.
Definition udp-redirect.c:179
int eignore
Ignore most recvfrom / sendto errors.
Definition udp-redirect.c:188
int stats
Display stats every 60 seconds.
Definition udp-redirect.c:190
char * caddr
Connect address.
Definition udp-redirect.c:174
int lsport
Listen port only expects packets from this port.
Definition udp-redirect.c:186
char * saddr
Send packets from address.
Definition udp-redirect.c:178
char * sif
Send packets from interface.
Definition udp-redirect.c:180
int cport
Connect port.
Definition udp-redirect.c:176
char * chost
Connect host.
Definition udp-redirect.c:175
Definition udp-redirect.c:200
unsigned long count_listen_byte_receive_total
Cumulative bytes received on the listen socket.
Definition udp-redirect.c:217
unsigned long count_connect_packet_send
Packets forwarded to the remote endpoint this window.
Definition udp-redirect.c:213
time_t time_display_last
Wall-clock time of the last statistics_display() call; used to compute the window duration.
Definition udp-redirect.c:201
unsigned long count_connect_byte_send_total
Cumulative bytes forwarded to the remote endpoint.
Definition udp-redirect.c:226
unsigned long count_connect_byte_receive
Bytes received on the send socket this window.
Definition udp-redirect.c:211
unsigned long count_connect_byte_receive_total
Cumulative bytes received on the send socket.
Definition udp-redirect.c:223
unsigned long count_connect_byte_send
Bytes forwarded to the remote endpoint this window.
Definition udp-redirect.c:214
unsigned long count_connect_packet_send_total
Cumulative packets forwarded to the remote endpoint.
Definition udp-redirect.c:225
unsigned long count_listen_byte_send
Bytes sent back on the listen socket this window.
Definition udp-redirect.c:208
unsigned long count_connect_packet_receive_total
Cumulative packets received on the send socket.
Definition udp-redirect.c:222
unsigned long count_listen_packet_send
Packets sent back on the listen socket (connect→client) this window.
Definition udp-redirect.c:207
unsigned long count_connect_packet_receive
Packets received on the send socket (from remote endpoint) this window.
Definition udp-redirect.c:210
unsigned long count_listen_byte_receive
Bytes received on the listen socket this window.
Definition udp-redirect.c:205
unsigned long count_listen_packet_receive
Packets received on the listen socket this window.
Definition udp-redirect.c:204
unsigned long count_listen_byte_send_total
Cumulative bytes sent back on the listen socket.
Definition udp-redirect.c:220
unsigned long count_listen_packet_receive_total
Cumulative packets received on the listen socket.
Definition udp-redirect.c:216
time_t time_display_first
Wall-clock time of the first statistics_display() call; used to compute the cumulative duration.
Definition udp-redirect.c:202
unsigned long count_listen_packet_send_total
Cumulative packets sent back on the listen socket.
Definition udp-redirect.c:219
#define main
Definition udp-redirect-test.c:627
#define PARSE_PORT(src, dest, label)
Definition udp-redirect.c:104
DEBUG_LEVEL
The available debug levels.
Definition udp-redirect.c:92
@ DEBUG_LEVEL_ERROR
Error messages.
Definition udp-redirect.c:93
@ DEBUG_LEVEL_DEBUG
Debug messages.
Definition udp-redirect.c:96
@ DEBUG_LEVEL_VERBOSE
Verbose messages.
Definition udp-redirect.c:95
@ DEBUG_LEVEL_INFO
Informational messages.
Definition udp-redirect.c:94
char int_to_human_char(double value)
Definition udp-redirect.c:1159
#define HUMAN_READABLE_SIZES_COUNT
Definition udp-redirect.c:1089
#define NETWORK_BUFFER_SIZE
Definition udp-redirect.c:62
double int_to_human_value(double value)
Definition udp-redirect.c:1149
#define DEBUG(debug_level_local, debug_level_message, fmt,...)
Definition udp-redirect.c:123
static int addr_is_unset(const struct sockaddr_storage *sa)
Definition udp-redirect.c:767
static socklen_t addr_len(const struct sockaddr_storage *sa)
Definition udp-redirect.c:758
static int addr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b)
Definition udp-redirect.c:774
#define ERRNO_IGNORE_SET(X, Y)
Definition udp-redirect.c:82
void settings_initialize(struct settings *s)
Definition udp-redirect.c:991
#define ERRNO_IGNORE_INIT(X)
Definition udp-redirect.c:77
static int socket_setup(const int debug_level, const char *desc, const char *xaddr, const int xport, const char *xif, int xfamily, struct sockaddr_storage *xsock_name)
Definition udp-redirect.c:806
static char * resolve_host(int debug_level, const char *host)
Definition udp-redirect.c:939
static int addr_port(const struct sockaddr_storage *sa)
Definition udp-redirect.c:749
static struct option longopts[]
Definition udp-redirect.c:134
#define MAX_ERRNO
Definition udp-redirect.c:72
#define STATISTICS_DELAY_SECONDS
Definition udp-redirect.c:57
#define HUMAN_READABLE_SIZES
Definition udp-redirect.c:1084
static const char * addr_tostring(const struct sockaddr_storage *sa, char *buf, size_t len)
Definition udp-redirect.c:737
static double int_to_human_scale(double value, int *count_out)
Definition udp-redirect.c:1132
#define HUMAN_READABLE(X)
Definition udp-redirect.c:1079
#define ERRNO_IGNORE_CHECK(X, Y)
Definition udp-redirect.c:87
#define UDP_REDIRECT_VERSION
Definition udp-redirect.c:52
void usage(const char *argv0, const char *message)
Definition udp-redirect.c:1021
static int parse_addr(const char *str, int port, struct sockaddr_storage *out)
Definition udp-redirect.c:708
void statistics_display(int debug_level, struct statistics *st, time_t now)
Definition udp-redirect.c:1172
void statistics_initialize(struct statistics *st)
Definition udp-redirect.c:1095
#define HRF
Definition udp-redirect.c:1074