From 294f34bc5eee2d70510e37522fb435b75cd48ba3 Mon Sep 17 00:00:00 2001 From: Simon Leinen Date: Sun, 25 Sep 2011 22:36:33 +0000 Subject: [PATCH] Added basic IPv6 support. In all places where IPv4 addresses were recognized before, the program now accepts hostnames or IPv6 addresses. The parsing code has been mostly rewritten for this. It now has slightly better diagnostics as well. Note that spoofing is not supported for IPv6 yet. Unfortunately IPv6 raw sockets don't quite work in the same way as IPv4 raw sockets, so supporting this won't be trivial. --- Makefile.am | 4 +- configure.in | 1 - parsetest.c | 15 +- rawsend.c | 10 +- rawsend.h | 2 +- rawtest.c | 2 +- read_config.c | 848 ++++++++++++++++++++++++++++++++++---------------- samplicate.c | 498 ++++++++++++++++++++++------- samplicator.h | 31 +- 9 files changed, 1008 insertions(+), 403 deletions(-) diff --git a/Makefile.am b/Makefile.am index a654402..62a4b27 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,9 +3,9 @@ PACKAGE = samplicator bin_PROGRAMS = samplicate -samplicate_SOURCES = samplicate.c samplicator.h rawsend.c rawsend.h read_config.c read_config.h +samplicate_SOURCES = samplicate.c samplicator.h rawsend.c rawsend.h read_config.c read_config.h inet.c inet.h samplicate_LDADD = @LIBOBJS@ EXTRA_PROGRAMS = rawtest parsetest rawtest_SOURCES = rawtest.c rawsend.c rawsend.h -parsetest_SOURCES = parsetest.c read_config.c rawsend.c read_config.h rawsend.h samplicator.h +parsetest_SOURCES = parsetest.c read_config.c rawsend.c read_config.h rawsend.h samplicator.h inet.c inet.h diff --git a/configure.in b/configure.in index 3836fb5..26dbd58 100644 --- a/configure.in +++ b/configure.in @@ -8,7 +8,6 @@ AC_CHECK_LIB(socket,bind) AC_STDC_HEADERS AC_CHECK_HEADERS(stdlib.h unistd.h ctype.h arpa/inet.h netinet/in_systm.h sys/uio.h) AC_CHECK_FUNCS(memcpy strchr) -AC_REPLACE_FUNCS(inet_aton) AC_DEFINE([HAVE_STRUCT_IP], 1, [Define if the system has `struct ip'.]) AC_DEFINE([HAVE_STRUCT_IPHDR], 1, diff --git a/parsetest.c b/parsetest.c index 4064434..8daa3b8 100644 --- a/parsetest.c +++ b/parsetest.c @@ -58,9 +58,6 @@ #ifdef HAVE_CTYPE_H # include #endif -#ifndef HAVE_INET_ATON -extern int inet_aton (const char *, struct in_addr *); -#endif #include "samplicator.h" #include "read_config.h" @@ -90,7 +87,7 @@ check_receiver (receiver, addr, port, af, freq, ttl) else if (af == AF_INET6) check_int_equal (receiver->addrlen, sizeof (struct sockaddr_in6)); else - return test_fail (); + test_fail (); check_address_equal ((struct sockaddr *) &receiver->addr, addr, port, af); check_int_equal (receiver->freq, freq); check_int_equal (receiver->ttl, ttl); @@ -121,17 +118,15 @@ main (int argc, char **argv) check_int_equal (ctx.fork, 0); check_null (ctx.sources); -#ifdef NOTYET check_int_equal (parse_cf_string ("1.2.3.4/30+ 6.7.8.9/1234\n", &ctx), -1); -#endif - check_int_equal (parse_cf_string ("1.2.3.4/255.255.255.252: 6.7.8.9/1234\n2.3.4.5: 7.8.9.0/4321", &ctx), 0); + check_int_equal (parse_cf_string ("1.2.3.4/255.255.255.252: 6.7.8.9/1234/10,237\n2.3.4.5: 7.8.9.0/4321", &ctx), 0); check_int_equal (ctx.fork, 0); if (check_non_null (sctx = ctx.sources)) { check_source_address_mask (sctx, "1.2.3.4", "255.255.255.252", AF_INET); check_int_equal (sctx->nreceivers, 1); - check_receiver (&sctx->receivers[0], "6.7.8.9", 1234, AF_INET, 1, DEFAULT_TTL); + check_receiver (&sctx->receivers[0], "6.7.8.9", 1234, AF_INET, 10, 237); if (check_non_null (sctx = sctx->next)) { check_source_address_mask (sctx, "2.3.4.5", "255.255.255.255", AF_INET); @@ -141,7 +136,6 @@ main (int argc, char **argv) } } -#ifdef NOTYET check_int_equal (parse_cf_string ("1.2.3.4/30: 6.7.8.9/1234\n2.3.4.5: 7.8.9.0/4321", &ctx), 0); check_int_equal (ctx.fork, 0); if (check_non_null (sctx = ctx.sources)) @@ -158,6 +152,7 @@ main (int argc, char **argv) } } +#ifdef NOTYET check_int_equal (parse_cf_string ("1.2.3.4/30: localhost/1234", &ctx), 0); check_int_equal (ctx.fork, 0); if (check_non_null (sctx = ctx.sources)) @@ -166,6 +161,7 @@ main (int argc, char **argv) check_int_equal (sctx->nreceivers, 1); check_receiver (&sctx->receivers[0], "127.0.0.1", 1234, AF_INET, 1, DEFAULT_TTL); } +#endif check_int_equal (parse_cf_string ("1.2.3.4/30: ip6-localhost/1234", &ctx), 0); check_int_equal (ctx.fork, 0); @@ -209,6 +205,7 @@ main (int argc, char **argv) check_receiver (&sctx->receivers[0], "2001:db8:0::1", 2000, AF_INET6, 1, DEFAULT_TTL); check_null (sctx->next); } +#ifdef NOTYET #endif return 0; } diff --git a/rawsend.c b/rawsend.c index b15d380..922c27c 100644 --- a/rawsend.c +++ b/rawsend.c @@ -184,10 +184,16 @@ raw_send_from_to (s, msg, msglen, saddr_generic, daddr_generic, ttl, flags) #undef daddr extern int -make_raw_udp_socket (sockbuflen) - long sockbuflen; +make_raw_udp_socket (sockbuflen, af) + size_t sockbuflen; + int af; { int s; + if (af == AF_INET6) + { + fprintf (stderr, "Spoofing not supported for IPv6\n"); + return -1; + } if ((s = socket (PF_INET, SOCK_RAW, IPPROTO_RAW)) == -1) return s; if (sockbuflen != -1) diff --git a/rawsend.h b/rawsend.h index fc72142..e76f083 100644 --- a/rawsend.h +++ b/rawsend.h @@ -9,7 +9,7 @@ #define RAWSEND_COMPUTE_UDP_CHECKSUM 0x0001 -extern int make_raw_udp_socket (long); +extern int make_raw_udp_socket (size_t, int); extern int raw_send_from_to (int, const void *, size_t, struct sockaddr *, diff --git a/rawtest.c b/rawtest.c index 6ddd6c6..bd46b27 100644 --- a/rawtest.c +++ b/rawtest.c @@ -24,7 +24,7 @@ main (int argc, char **argv) { int s; - if ((s = make_raw_udp_socket (0)) == -1) + if ((s = make_raw_udp_socket (0, AF_INET)) == -1) { fprintf (stderr, "socket: %s\n", strerror (errno)); diff --git a/read_config.c b/read_config.c index 0a8669f..88bc7d6 100644 --- a/read_config.c +++ b/read_config.c @@ -35,28 +35,87 @@ #ifdef HAVE_CTYPE_H # include #endif -#ifndef HAVE_INET_ATON -extern int inet_aton (const char *, struct in_addr *); -#endif #include "samplicator.h" #include "read_config.h" +#include "inet.h" #include "rawsend.h" -static int make_cooked_udp_socket (long); -static void usage (const char *); - #define PORT_SEPARATOR '/' #define FREQ_SEPARATOR '/' #define TTL_SEPARATOR ',' -#define FLOWPORT 2000 +#define FLOWPORT "2000" #define DEFAULT_SOCKBUFLEN 65536 #define MAX_PEERS 100 #define MAX_LINELEN 8000 +static int parse_line (struct samplicator_context *, char *, const char *); +static int parse_addr_mask (const char *, const char *, + const struct samplicator_context *, + struct sockaddr_storage *, + struct sockaddr_storage *, + socklen_t *); +static void short_usage (const char *); +static void usage (const char *); + +static int +parse_error (const struct samplicator_context *ctx, const char *fmt, ...) +{ + va_list fmt_args; + va_start (fmt_args, fmt); + fprintf (stderr, "%s, line %d: ", ctx->config_file_name, ctx->config_file_lineno); + vfprintf (stderr, fmt, fmt_args); + fprintf (stderr, "\n"); + va_end (fmt_args); + return -1; +} + + +/* copy_string_start_end (start, end) + + Copy a string defined by a start and end pointer to a fresh + null-terminated string. This is useful when using library + functions that expect such strings. + + Will return a null pointer if allocating space fails. + + The copy should be free()'d when the caller is done with it. + */ +static char * +copy_string_start_end (start, end) + const char *start; + const char *end; +{ + size_t len = end-start; + char *copy = malloc (len+1); + if (copy == 0) + return 0; + strncpy (copy, start, len); + copy[len] = 0; + return copy; +} + +/* read_cf_file (file, ctx) + + Read configuration file FILE into samplicator context CTX. + + The file is opened and parsed line by line. The parser fills in + values of CTX. + + The file name and a line counter are stored in CTX for use in + parser error messages. + + Return value: + + 0, if the file could be parsed. + -1, if an error occurred during parsing. + + If an error occurs, the parser will give up immediately. Contents + preceding the errors may have been processed and filled into CTX. + */ int read_cf_file (file, ctx) const char *file; @@ -64,96 +123,525 @@ read_cf_file (file, ctx) { FILE *cf; char tmp_s[MAX_LINELEN]; - int argc; - const char *argv[MAX_PEERS]; - char *c, *slash, *e; - struct source_context *sctx; + ctx->config_file_name = file; + ctx->config_file_lineno = 0; - if ((cf = fopen(file,"r")) == NULL) + if ((cf = fopen (file,"r")) == NULL) { - fprintf(stderr, "read_cf_file: cannot open %s. Aborting.\n",file); - exit(1); + fprintf (stderr, "read_cf_file: cannot open %s. Aborting.\n", file); + return -1; } - while (!feof(cf)) + while (!feof (cf)) { - if (fgets (tmp_s, MAX_LINELEN - 1, cf) == (char *) 0) + if (fgets (tmp_s, MAX_LINELEN, cf) == (char *) 0) { break; } - if ((c = strchr(tmp_s, '#')) != 0) - continue; - - /* lines look like this: + ++ctx->config_file_lineno; + if (parse_line (ctx, tmp_s, tmp_s + strlen (tmp_s)) == -1) + { + return -1; + } + } + fclose (cf); + return 0; +} - ipadd[/mask]: dest[:port[/freq][,ttl]] dest2[:port2[/freq2][,ttl2]]... +/* resolve_addr (string, ctx, addrp, addrlenp) - */ - - if ((c = strchr(tmp_s, ':')) != 0) + Parses, and possibly resolves, an IP address given as a string. + + The preferences in CTX are used to determine whether to return an + IPv4 or IPv6 address. + + The address is stored in the sockaddr_storage structure pointed to + by ADDRP. + + If ADDRLENP is non-null, it the length of the address structure + will be stored to it. + + If an error occurs during parsing or resolution, the function will + return -1, and nothing will be stored in ADDRP or ADDRLENP. + */ +static int +resolve_addr (const char *addrstring, + const struct samplicator_context *ctx, + struct sockaddr_storage *addrp, + socklen_t *addrlenp) +{ + struct addrinfo hints, *res; + + init_hints_from_preferences (&hints, ctx); + + if (getaddrinfo (addrstring, 0, &hints, &res) != 0 || res == 0) + { + return parse_error (ctx, "Could not parse address %s", addrstring); + } + memcpy (addrp, res->ai_addr, res->ai_addrlen); + if (addrlenp != 0) + *addrlenp = res->ai_addrlen; + return 0; +} + +static int +parse_addr_1 (const char *start, + const char *end, + const struct samplicator_context *ctx, + struct sockaddr_storage *addrp, + socklen_t *addrlenp) +{ + char *copy = copy_string_start_end (start, end); + int result; + + if (copy == 0) + { + return parse_error (ctx, "Out of memory"); + } + result = resolve_addr (copy, ctx, addrp, addrlenp); + free (copy); + return result; +} + +static int +parse_addr (const char *start, + const char *end, + const struct samplicator_context *ctx, + struct sockaddr_storage *addrp, + socklen_t *addrlenp) +{ + while (start < end && isspace (*start)) + ++start; + while (start < end && isspace (*(end-1))) + --end; + if (start < end && *start == '[') + { + ++start; + if (start < end && *(end-1) == ']') + --end; + else { - *c++ = 0; + return parse_error (ctx, "Mismatched brackets"); + } + } + return parse_addr_1 (start, end, ctx, addrp, addrlenp); +} - sctx = calloc(1, sizeof(struct source_context)); - if ((slash = strchr (tmp_s, '/')) != 0) - { - *slash++ = 0; +static void +set_ipv4_netmask(struct sockaddr_in *maskp, int preflen) +{ + in_addr_t *addrp = &maskp->sin_addr.s_addr; + int k; + unsigned long bit; + uint32_t haddr; + + haddr = 0xffffffff; + preflen = 32-preflen; + for (k = 0, bit = 1; k < preflen; ++k, bit <<= 1) + { + haddr &= ~bit; + } + *addrp = htonl (haddr); +} - ((struct sockaddr_in *) &sctx->mask)->sin_family = AF_INET; - /* fprintf (stderr, "parsing IP address mask (%s)\n",slash); */ - if (inet_aton (slash, &(((struct sockaddr_in *) &sctx->mask)->sin_addr)) == 0) +static void +set_ipv6_netmask(struct sockaddr_in6 *maskp, int preflen) +{ + uint8_t *addrp = maskp->sin6_addr.s6_addr; + int k; + unsigned bit; + unsigned octet_index; + + memset (addrp, 0, 16); + for (k = 0, bit = 128, octet_index = 0; + k < preflen; + ++k, bit >>= 1) + { + if (bit < 1) + { + bit = 128, octet_index++; + } + addrp[octet_index] |= bit; + } +} + +static int +parse_mask (const char *start, + const char *end, + const struct samplicator_context *ctx, + struct sockaddr_storage *maskp, + struct sockaddr_storage *addrp) +{ + if (addrp->ss_family == AF_INET) + { + const char *cp = start; + while (cp < end && *cp != '.') + ++cp; + if (cp < end) /* contains a dot, assume full netmask */ + { + socklen_t addrlen1; + if (parse_addr_1 (start, end, ctx, maskp, &addrlen1) != 0) + { + return parse_error (ctx, "Cannot parse mask"); + } + else + { + if (addrlen1 != sizeof (struct sockaddr_in)) { - fprintf (stderr, "parsing IP address mask (%s) failed\n",slash); - exit (1); + return parse_error (ctx, "Inconsistent addr/mask structures"); } + return 0; } - else + } + else + { /* no dot, assume this is a prefix length */ + int preflen; + char *int_end; + preflen = strtol (start, &int_end, 10); + if (int_end == start || int_end < end || preflen < 0 || preflen > 32) { - ((struct sockaddr_in *) &sctx->mask)->sin_family = AF_INET; - inet_aton ("255.255.255.255", &(((struct sockaddr_in *) &sctx->mask)->sin_addr)); + return parse_error (ctx, "Bogus prefix length"); } - /* fprintf (stderr, "parsing IP address (%s)\n",tmp_s); */ - ((struct sockaddr_in *) &sctx->source)->sin_family = AF_INET; - if (inet_aton (tmp_s, &((struct sockaddr_in *) &sctx->source)->sin_addr) == 0) - { - fprintf (stderr, "parsing IP address (%s) failed\n",tmp_s); - exit (1); + bzero (maskp, sizeof (struct sockaddr_in)); + ((struct sockaddr_in *)maskp)->sin_family = AF_INET; + set_ipv4_netmask((struct sockaddr_in *)maskp, preflen); + return 0; + } + } + else if (addrp->ss_family == AF_INET6) + { + int preflen; + char *int_end; + preflen = strtol (start, &int_end, 10); + if (int_end == start || int_end < end || preflen < 0 || preflen > 128) + { + return parse_error (ctx, "Bogus prefix length"); + } + bzero (maskp, sizeof (struct sockaddr_in6)); + ((struct sockaddr_in6 *)maskp)->sin6_family = AF_INET6; + set_ipv6_netmask((struct sockaddr_in6 *)maskp, preflen); + return 0; + } + else + { + return parse_error (ctx, "Unsupported address family"); + } +} + +static int +set_default_mask (const struct samplicator_context *ctx, + struct sockaddr_storage *maskp, + struct sockaddr_storage *addrp) +{ + if (addrp->ss_family == AF_INET) + { + bzero (maskp, sizeof (struct sockaddr_in)); + maskp->ss_family = AF_INET; + memset (&((struct sockaddr_in *) maskp)->sin_addr, 0xff, 4); + return 0; + } + else if (addrp->ss_family == AF_INET6) + { + bzero (maskp, sizeof (struct sockaddr_in6)); + maskp->ss_family = AF_INET6; + memset (&((struct sockaddr_in6 *) maskp)->sin6_addr, 0xff, 16); + return 0; + } + return parse_error (ctx, "Unknown address family %d", addrp->ss_family); +} + +static int +parse_addr_mask (start, end, ctx, addrp, maskp, addrlenp) + const char *start, *end; + const struct samplicator_context *ctx; + struct sockaddr_storage *addrp; + struct sockaddr_storage *maskp; + socklen_t *addrlenp; +{ + const char *c, *slash = 0; + + c = start; + while (c < end && *c != '/') + ++c; + if (c < end) + slash = c; + + if (parse_addr (start, slash == 0 ? end : slash, ctx, addrp, addrlenp) == -1) + return -1; + + if (slash != 0) + { + if (parse_mask (slash+1, end, ctx, maskp, addrp) == -1) + return -1; + } + else + set_default_mask (ctx, maskp, addrp); + return 0; +} + +/* + parse_line (ctx, start, end) + + Parse a single line of configuration file. + +*/ +static int +parse_line (ctx, start, end) + struct samplicator_context *ctx; + char *start; + const char *end; +{ + struct source_context *sctx; + int argc; + const char *argv[MAX_PEERS]; + const char *c, *e; + const char *lhs_start, *lhs_end; + const char *rhs_start; + + /* move end before the start of any comment at the end of the line, + and before any whitepace preceding such a comment or EOL. */ + for (c = start; c < end && *c != '#'; ++c) ; + if (c < end) + end = c; + while (end > start && isspace (*(end-1))) + --end; + if (start == end) + return 0; /* empty line; skip. */ + + /* non-empty lines should look like this: + + ipadd[/mask]: dest[:port[/freq][,ttl]] dest2[:port2[/freq2][,ttl2]]... + + The problematic case is where the ipadd is an IPv6 address, + because IPv6 addresses usually contain colons. We insist on the + convention that IPv6 addresses be enclosed in brackets. In + addition, for IPv6 we only accept prefix lengths, not arbitrary + netmasks. + + [2001:db0:0:1::]/64: [2001:db0:0:2::3]:8000 [2001:db0:0:2::4]:8000 + */ + + c = start; + while (c < end && isspace (*c)) + ++c; + lhs_start = c; + if (*c == '[') { + while (c < end && *c != ']') + ++c; + while (c < end && *c != ':') + ++c; + } else { + c = start; + while (c < end && *c != ':') + ++c; + } + /* Now c either points at the colon that separates the left-hand + address/mask from the right hand destinations, or c is equal to + end because such a colon was not found. */ + + if (c < end) + { + lhs_end = c; + ++c; /* skip colon */ + while (c < end && isspace (*c)) + ++c; + rhs_start = c; + sctx = calloc (1, sizeof (struct source_context)); + + if (parse_addr_mask (lhs_start, lhs_end, ctx, + &sctx->source, &sctx->mask, &sctx->addrlen) != 0) + return -1; + + argc = 0; + while (c < end) + { + while (c < end && isspace (*c)) + c++; + if (c >= end) break; + e = c; + while((*e != 0) && !isspace ((int) *e)) + e++; + argv[argc++] = copy_string_start_end (c, e); + c = e; + if (c < end) + c++; + } + if (argc > 0) + { + if (parse_receivers (argc, argv, ctx, sctx) == -1) + { + return -1; } + } + } + else + { + return parse_error (ctx, "Missing colon"); + } + return 0; +} - /* - fprintf (stderr, "parsed into %s/", - inet_ntoa(sctx->source)); - fprintf (stderr, "%s\n", - inet_ntoa(sctx->mask)); - */ +static int +parse_receiver (struct receiver *receiverp, + const char *arg, + struct samplicator_context *ctx) +{ + const char *start, *end; + const char *host_start, *host_end; + char portspec[NI_MAXSERV]; + struct addrinfo hints, *res; + int result; + + receiverp->flags = ctx->default_receiver_flags; + receiverp->freqcount = 0; + receiverp->freq = 1; + receiverp->ttl = DEFAULT_TTL; + + start = arg; end = start + strlen (arg); + while (start < end && isspace (*start)) + ++start; + while (start < end && isspace (*(end-1))) + --end; + + if (start < end && *start == '[') + { + host_end = host_start = start+1; + while (host_end < end && *host_end != ']') + ++host_end; + if (host_end == end) + { + return parse_error (ctx, "Missing closing bracket"); + } + start = host_end+1; + } + else + { + host_end = host_start = start; + while (host_end < end && *host_end != PORT_SEPARATOR) + ++host_end; + start = host_end; + } + + /* extract the port part */ + if (*start == PORT_SEPARATOR) + { + const char *port_start, *port_end; + + ++start; + port_end = port_start = start; + while (port_end < end && *port_end != FREQ_SEPARATOR) + ++port_end; + if (port_end < end) + { + const char *freq_start, *freq_end; + freq_end = freq_start = port_end + 1; - argc = 0; - while (*c != 0) + /* extract the frequency part */ + while (freq_end < end && *freq_end != TTL_SEPARATOR) + ++freq_end; + + if (freq_start < freq_end) { - while ((*c != 0) && isspace ((int) *c)) - c++; - if (*c == 0 ) break; - - e = c; - while((*e != 0) && !isspace ((int) *e)) - e++; - argv[argc++] = c; - c = e; - if (*c != 0) - c++; - *e = 0; + int freq; + char *freq_parse_end; + + freq = strtol (freq_start, &freq_parse_end, 10); + if (freq_parse_end == freq_start + || freq_parse_end != freq_end + || freq < 1) + { + return parse_error (ctx, "Illegal frequency .*s", + freq_end-freq_start, freq_start); + } + else + { + receiverp->freq = freq; + } } - if (argc > 0) + if (freq_end < end) { - if (parse_receivers (argc, argv, ctx, sctx) == -1) + int ttl; + const char *ttl_start = freq_end + 1; + const char *ttl_end = end; + char *ttl_parse_end; + + ttl = strtol (ttl_start, &ttl_parse_end, 10); + if (ttl_parse_end == ttl_start + || ttl_parse_end != ttl_end + || ttl < 1 || ttl > 255) { - usage (argv[0]); - exit (1); + return parse_error (ctx, "Illegal TTL"); + } + else + { + receiverp->ttl = ttl; } } } + if (port_end - port_start >= NI_MAXSERV) + { + return parse_error (ctx, "Service name/port number (%.*s) too long", + port_end-port_start, port_start); + } + strncpy (portspec, port_start, port_end-port_start); + portspec[port_end-port_start] = 0; } - fclose (cf); + else + strcpy (portspec, FLOWPORT); + + init_hints_from_preferences (&hints, ctx); + { + char *tmp_buf = copy_string_start_end (host_start, host_end); + if (tmp_buf == 0) + { + return parse_error (ctx, "Out of memory"); + } + result = getaddrinfo (tmp_buf, portspec, &hints, &res); + if (result != 0) + { + return parse_error (ctx, "Parsing IP address (%s with port spec %s) failed: %s", + tmp_buf, portspec, gai_strerror (result)); + } + memcpy (&receiverp->addr, res->ai_addr, res->ai_addrlen); + receiverp->addrlen = res->ai_addrlen; + return 0; + } +} + +int +parse_receivers (argc, argv, ctx, sctx) + int argc; + const char **argv; + struct samplicator_context *ctx; + struct source_context *sctx; +{ + int i; + + /* allocate for argc receiver entries */ + sctx->nreceivers = argc; + + if (!(sctx->receivers = (struct receiver*) calloc (sctx->nreceivers, sizeof (struct receiver)))) { + return parse_error (ctx, "Out of memory"); + } + + /* fill in receiver entries */ + for (i = 0; i < argc; ++i) + { + if (parse_receiver (&sctx->receivers[i], argv[i], ctx) != 0) + { + return -1; + } + } + if (ctx->sources == NULL) + { + ctx->sources = sctx; + } + else + { + struct source_context *ptr; + for (ptr = ctx->sources; ptr->next != NULL; ptr = ptr->next); + ptr->next = sctx; + } return 0; } @@ -174,29 +662,33 @@ parse_args (argc, argv, ctx) return -1; } + ctx->config_file_name = ""; + ctx->config_file_lineno = 1; sctx->nreceivers = 0; ctx->sources = sctx; sctx->next = (struct source_context *) NULL; ctx->sockbuflen = DEFAULT_SOCKBUFLEN; - ctx->faddr.s_addr = htonl (INADDR_ANY); - ctx->fport = FLOWPORT; + ctx->faddr_spec = 0; + bzero (&ctx->faddr, sizeof ctx->faddr); + ctx->fport_spec = FLOWPORT; ctx->debug = 0; + ctx->ipv4_only = 0; + ctx->ipv6_only = 0; ctx->fork = 0; ctx->pid_file = (const char *) 0; ctx->sources = 0; - ctx->defaultflags = pf_CHECKSUM; + ctx->default_receiver_flags = pf_CHECKSUM; /* assume that command-line supplied receivers want to get all data */ - ((struct sockaddr_in *) &sctx->source)->sin_family = AF_INET; + sctx->source.ss_family = AF_INET; ((struct sockaddr_in *) &sctx->source)->sin_addr.s_addr = 0; - ((struct sockaddr_in *) &sctx->mask)->sin_family = AF_INET; ((struct sockaddr_in *) &sctx->mask)->sin_addr.s_addr = 0; sctx->tx_delay = 0; optind = 1; - while ((i = getopt (argc, (char **) argv, "hb:d:m:p:s:x:c:fSn")) != -1) + while ((i = getopt (argc, (char **) argv, "hb:d:m:p:s:x:c:fSn46")) != -1) { switch (i) { @@ -207,40 +699,25 @@ parse_args (argc, argv, ctx) ctx->debug = atoi (optarg); break; case 'n': /* no UDP checksums */ - ctx->defaultflags &= ~pf_CHECKSUM; + ctx->default_receiver_flags &= ~pf_CHECKSUM; break; case 'p': /* flow port */ - ctx->fport = atoi (optarg); - if (ctx->fport < 0 - || ctx->fport > 65535) - { - fprintf (stderr, - "Illegal receive port %d - \ -should be between 0 and 65535\n", - ctx->fport); - usage (argv[0]); - exit (1); - } + ctx->fport_spec = optarg; break; case 'm': /* make PID file */ ctx->pid_file = optarg; break; case 's': /* flow address */ - if (inet_aton (optarg, &ctx->faddr) == 0) - { - fprintf (stderr, "parsing IP address (%s) failed\n", optarg); - usage (argv[0]); - exit (1); - } + ctx->faddr_spec = optarg; break; case 'x': /* transmit delay */ sctx->tx_delay = atoi (optarg); break; case 'S': /* spoof */ - ctx->defaultflags |= pf_SPOOF; + ctx->default_receiver_flags |= pf_SPOOF; break; case 'c': /* config file */ - if (read_cf_file(optarg, ctx) != 0) + if (read_cf_file (optarg, ctx) != 0) { return -1; } @@ -252,10 +729,17 @@ should be between 0 and 65535\n", usage (argv[0]); exit (0); break; - default: - usage (argv[0]); - exit (1); + case '4': + ctx->ipv6_only = 0; + ctx->ipv4_only = 1; + break; + case '6': + ctx->ipv4_only = 0; + ctx->ipv6_only = 1; break; + default: + short_usage (argv[0]); + return -1; } } @@ -263,177 +747,13 @@ should be between 0 and 65535\n", { if (parse_receivers (argc - optind, argv + optind, ctx, sctx) == -1) { - usage (argv[0]); - exit (1); - } - } - return 0; -} - -int -parse_receivers (argc, argv, ctx, sctx) - int argc; - const char **argv; - struct samplicator_context *ctx; - struct source_context *sctx; -{ - int i; - char tmp_buf[256]; - static int cooked_sock = -1; - static int raw_sock = -1; - char *c; - struct source_context *ptr; - - /* allocate for argc receiver entries */ - sctx->nreceivers = argc; - - if (!(sctx->receivers = (struct receiver*) calloc (sctx->nreceivers, sizeof (struct receiver)))) { - fprintf(stderr, "calloc(): failed.\n"); - return -1; - } - - /* fill in receiver entries */ - for (i = 0; i < argc; ++i) - { - sctx->receivers[i].flags = ctx->defaultflags; - sctx->receivers[i].addrlen = sizeof (struct sockaddr_in); - - if (strlen (argv[i]) > 255) - { - fprintf (stderr, "ouch!\n"); - return -1; - } - strcpy (tmp_buf, argv[i]); - - /* skip to end or port seperator */ - for (c = tmp_buf; (*c != PORT_SEPARATOR) && (*c); ++c); - - /* extract the port part */ - if (*c == PORT_SEPARATOR) - { - int port; - *c = 0; - ++c; - port = atoi(c); - if (port < 0 || port > 65535) - { - fprintf (stderr, "Illegal destination port %d - \ -should be between 0 and 65535\n", port); - return -1; - } - ((struct sockaddr_in *) &sctx->receivers[i].addr)->sin_port = htons (port); - } - else - ((struct sockaddr_in *) &sctx->receivers[i].addr)->sin_port = htons (FLOWPORT); - - /* extract the frequency part */ - sctx->receivers[i].freqcount = 0; - sctx->receivers[i].freq = 1; - for (; (*c != FREQ_SEPARATOR) && (*c); ++c) - if (*c == TTL_SEPARATOR) goto TTL; - if (*c == FREQ_SEPARATOR) - { - *c = 0; - ++c; - sctx->receivers[i].freq = atoi(c); - } - - /* printf("Frequency: %d\n", sctx->receivers[i].freq); */ - - /* extract the TTL part */ - for (; (*c != TTL_SEPARATOR) && (*c); ++c); - TTL: - if ((*c == TTL_SEPARATOR) && (*(c+1) > 0)) - { - *c = 0; - ++c; - sctx->receivers[i].ttl = atoi (c); - if (sctx->receivers[i].ttl < 1 - || sctx->receivers[i].ttl > 255) - { - fprintf (stderr, - "Illegal value %d for TTL - should be between 1 and 255.\n", - sctx->receivers[i].ttl); - return -1; - } - } - else - sctx->receivers[i].ttl = DEFAULT_TTL; - - /* extract the ip address part */ - if (inet_aton (tmp_buf, & ((struct sockaddr_in *) &sctx->receivers[i].addr)->sin_addr) == 0) - { - fprintf (stderr, "parsing IP address (%s) failed\n", tmp_buf); + short_usage (argv[0]); return -1; } - - sctx->receivers[i].addrlen = sizeof (struct sockaddr_in); - sctx->receivers[i].addr.ss_family = AF_INET; - - if (sctx->receivers[i].flags & pf_SPOOF) - { - if (raw_sock == -1) - { - if ((raw_sock = make_raw_udp_socket (ctx->sockbuflen)) < 0) - { - if (errno == EPERM) - { - fprintf (stderr, "Not enough privilege for -S option---try again as root.\n"); - } - else - { - fprintf (stderr, "creating raw socket: %s\n", strerror(errno)); - } - return -1; - } - } - sctx->receivers[i].fd = raw_sock; - } - else - { - if (cooked_sock == -1) - { - if ((cooked_sock = make_cooked_udp_socket (ctx->sockbuflen)) < 0) - { - fprintf (stderr, "creating cooked socket: %s\n", - strerror(errno)); - return -1; - } - } - sctx->receivers[i].fd = cooked_sock; - } - } - if (ctx->sources == NULL) - { - ctx->sources = sctx; } - else - { - for (ptr = ctx->sources; ptr->next != NULL; ptr = ptr->next); - ptr->next = sctx; - } return 0; } -static int -make_cooked_udp_socket (sockbuflen) - long sockbuflen; -{ - int s; - if ((s = socket (PF_INET, SOCK_DGRAM, 0)) == -1) - return s; - if (sockbuflen != -1) - { - if (setsockopt (s, SOL_SOCKET, SO_SNDBUF, - (char *) &sockbuflen, sizeof sockbuflen) == -1) - { - fprintf (stderr, "setsockopt(SO_SNDBUF,%ld): %s\n", - sockbuflen, strerror (errno)); - } - } - return s; -} - void short_usage (progname) const char *progname; { @@ -448,7 +768,7 @@ usage (progname) \n\ Supported options:\n\ \n\ - -p UDP port to accept flows on (default %d)\n\ + -p UDP port to accept flows on (default %s)\n\ -s
Interface address to accept flows on (default any)\n\ -d debug level\n\ -b set socket buffer size (default %lu)\n\ @@ -465,7 +785,7 @@ Specifying receivers:\n\ A.B.C.D[%cport[%cfreq][%cttl]]...\n\ where:\n\ A.B.C.D is the receiver's IP address\n\ - port is the UDP port to send to (default %d)\n\ + port is the UDP port to send to (default %s)\n\ freq is the sampling rate (default 1)\n\ ttl is the outgoing packets' TTL value (default %d)\n\ \n\ diff --git a/samplicate.c b/samplicate.c index 2c4cced..44acbed 100644 --- a/samplicate.c +++ b/samplicate.c @@ -9,6 +9,7 @@ #endif #include #include +#include #ifdef HAVE_ARPA_INET_H # include #endif @@ -30,38 +31,21 @@ #ifdef HAVE_CTYPE_H # include #endif -#ifndef HAVE_INET_ATON -extern int inet_aton (const char *, struct in_addr *); -#endif #include "samplicator.h" #include "read_config.h" #include "rawsend.h" +#include "inet.h" #define PDU_SIZE 1500 static int send_pdu_to_receiver (struct receiver *, const void *, size_t, - struct sockaddr_in *); + struct sockaddr *); static int init_samplicator (struct samplicator_context *); static int samplicate (struct samplicator_context *); - -/* Work around a GCC compatibility problem with respect to the - inet_ntoa() system function */ -#undef inet_ntoa -#define inet_ntoa(x) my_inet_ntoa(&(x)) - -static const char * -my_inet_ntoa (const struct in_addr *in) -{ - unsigned a = ntohl (in->s_addr); - static char buffer[16]; - sprintf (buffer, "%d.%d.%d.%d", - (a >> 24) & 0xff, - (a >> 16) & 0xff, - (a >> 8) & 0xff, - a & 0xff); - return buffer; -} +static int make_udp_socket (long, int, int); +static int make_recv_socket (struct samplicator_context *); +static int make_send_sockets (struct samplicator_context *); int main (argc, argv) @@ -70,9 +54,9 @@ main (argc, argv) { struct samplicator_context ctx; - if (parse_args (argc, argv, &ctx) == -1) + if (parse_args (argc, (const char **) argv, &ctx) == -1) { - exit (-1); + exit (1); } if (init_samplicator (&ctx) == -1) exit (1); @@ -81,6 +65,30 @@ main (argc, argv) exit (0); } +static int +daemonize (void) +{ + pid_t pid; + + pid = fork(); + if (pid == -1) + { + fprintf (stderr, "failed to fork process\n"); + exit (1); + } + else if (pid > 0) + { /* kill the parent */ + exit (0); + } + else + { /* end interaction with shell */ + fclose (stdin); + fclose (stdout); + fclose (stderr); + } + return 0; +} + static int write_pid_file (const char *filename) { @@ -108,78 +116,124 @@ write_pid_file (const char *filename) return 0; } -/* init_samplicator: prepares receiving socket */ +/* + make_recv_socket(ctx) + + Create the socket on which samplicator receives its packets. + + There can only be one. This will be either a wildcard socket + listening on a specific port on all interfaces, or a socket bound to + a specific address (and, thus, interface). + + The creation of this socket is affected by the preferences in CTX: + + CTX->faddr_spec + This is either a null pointer, meaning that a wildcard socket + should be created, or a hostname or address literal specifying + which address to listen on. If this maps to multiple addresses, + the socket will be bound to the first of those addresses that it + can be bound to, in the order returned by getaddrinfo(). + + CTX->fport_spec + This must be a string, and specifies the port number or service + name on which the socket will listen. + + CTX->ipv4_only + If this is non-zero, the socket will be an IPv4 socket. An error + will be signaled if faddr_spec doesn't map to an IPv4 address. + + CTX->ipv6_only + If non zero, only IPv6 addresses will be considered. + + If ipv4_only and ipv6_only are both zero, and faddr_spec is also + null, then the receive socket will be an IPv6 socket bound to a + specific port on all interfaces. This socket will be able to receive + packets over both IPv6 and IPv4. + + CTX->sockbuflen + If this is non-zero, the function will try to set the socket's + receiver buffer size to this many bytes. If setting the socket + buffer fails, a warning will be printed, but the socket will still + be created. The idea here is that a socket with an incorrect + buffer size is more useful than no socket at all, although some + people may differ. + + RETURN VALUE + + If a socket could be created and bound, this function will return + zero. If this was not possible, the function will produce an error + message and return -1. + */ static int -init_samplicator (ctx) +make_recv_socket (ctx) struct samplicator_context *ctx; { - struct sockaddr_in local_address; - - /* setup to receive flows */ - bzero (&local_address, sizeof local_address); - local_address.sin_family = AF_INET; - local_address.sin_addr.s_addr = ctx->faddr.s_addr; - local_address.sin_port = htons (ctx->fport); + struct addrinfo hints, *res; + int result; - if ((ctx->fsockfd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) + init_hints_from_preferences (&hints, ctx); + if ((result = getaddrinfo (ctx->faddr_spec, ctx->fport_spec, &hints, &res)) != 0) { - fprintf (stderr, "socket(): %s\n", strerror (errno)); + fprintf (stderr, "Failed to resolve IP address/port (%s:%s): %s\n", + ctx->faddr_spec, ctx->fport_spec, gai_strerror (result)); return -1; } - if (setsockopt (ctx->fsockfd, SOL_SOCKET, SO_RCVBUF, - (char *) &ctx->sockbuflen, sizeof ctx->sockbuflen) == -1) + for (; res; res = res->ai_next) { - fprintf (stderr, "setsockopt(SO_RCVBUF,%ld): %s\n", - ctx->sockbuflen, strerror (errno)); - } - if (bind (ctx->fsockfd, - (struct sockaddr*)&local_address, sizeof local_address) < 0) - { - fprintf (stderr, "bind(): %s\n", strerror (errno)); - return -1; + if ((ctx->fsockfd = socket (res->ai_family, SOCK_DGRAM, 0)) < 0) + { + fprintf (stderr, "socket(): %s\n", strerror (errno)); + break; + } + if (setsockopt (ctx->fsockfd, SOL_SOCKET, SO_RCVBUF, + (char *) &ctx->sockbuflen, sizeof ctx->sockbuflen) == -1) + { + fprintf (stderr, "Warning: setsockopt(SO_RCVBUF,%ld) failed: %s\n", + ctx->sockbuflen, strerror (errno)); + } + if (bind (ctx->fsockfd, + (struct sockaddr*)res->ai_addr, res->ai_addrlen) < 0) + { + fprintf (stderr, "bind(): %s\n", strerror (errno)); + break; + } + ctx->fsockaddrlen = res->ai_addrlen; + return 0; } - return 0; + return -1; } +/* init_samplicator: prepares receiving socket */ static int -samplicate (ctx) +init_samplicator (ctx) struct samplicator_context *ctx; { - unsigned char fpdu[PDU_SIZE]; - struct sockaddr_in remote_address; struct source_context *sctx; - pid_t pid; - int i, n; - socklen_t len; + int i; + + if (make_recv_socket (ctx) != 0) + { + return -1; + } /* check is there actually at least one configured data receiver */ for (i = 0, sctx = ctx->sources; sctx != NULL; sctx = sctx->next) - if(sctx->nreceivers > 0) i += sctx->nreceivers; + { + i += sctx->nreceivers; + } if (i == 0) { fprintf(stderr, "You have to specify at least one receiver, exiting\n"); - exit(1); + return -1; } - if (ctx->fork == 1) + if (make_send_sockets (ctx) != 0) { - pid = fork(); - if (pid == -1) - { - fprintf (stderr, "failed to fork process\n"); - exit (1); - } - else if (pid > 0) - { /* kill the parent */ - exit (0); - } - else - { /* end interaction with shell */ - fclose(stdin); - fclose(stdout); - fclose(stderr); - } + return -1; } + + if (ctx->fork == 1) + daemonize (); if (ctx->pid_file != 0) { if (write_pid_file (ctx->pid_file) != 0) @@ -187,15 +241,100 @@ samplicate (ctx) return -1; } } + return 0; +} + +static int +match_addr_p (struct sockaddr *input_generic, + struct sockaddr *addr_generic, + struct sockaddr *mask_generic) +{ +#define SPECIALIZE(VAR, STRUCT) \ + struct STRUCT *VAR = (struct STRUCT *) VAR ## _generic + + if (addr_generic->sa_family == AF_INET) + { + SPECIALIZE (addr, sockaddr_in); + SPECIALIZE (mask, sockaddr_in); + if (addr->sin_addr.s_addr == 0) + return 1; + if (input_generic->sa_family == AF_INET) + { + SPECIALIZE (input, sockaddr_in); + if ((input->sin_addr.s_addr & mask->sin_addr.s_addr) == addr->sin_addr.s_addr) + return 1; + return 0; + } + else if (input_generic->sa_family == AF_INET6) + { + SPECIALIZE (input, sockaddr_in6); + if (IN6_IS_ADDR_V4MAPPED (&input->sin6_addr)) + { + abort (); /* TODO */ + } + else + { + return 0; + } + } + else + abort (); /* Unexpected address family */ + } + else + { + SPECIALIZE (addr, sockaddr_in6); + SPECIALIZE (mask, sockaddr_in6); + + if (IN6_IS_ADDR_UNSPECIFIED (&mask->sin6_addr)) + { + return 1; + } + else if (input_generic->sa_family == AF_INET) + { + abort (); + } + else if (input_generic->sa_family == AF_INET6) + { + SPECIALIZE (input, sockaddr_in6); + unsigned k; + for (k = 0; k < 16; ++k) + { + if ((input->sin6_addr.s6_addr[k] & mask->sin6_addr.s6_addr[k]) + != addr->sin6_addr.s6_addr[k]) + { + return 0; + } + return 1; + } + abort (); + } + else + abort (); /* Unexpected address family */ + } +#undef SPECIALIZE +} + +static int +samplicate (ctx) + struct samplicator_context *ctx; +{ + unsigned char fpdu[PDU_SIZE]; + struct sockaddr_storage remote_address; + struct source_context *sctx; + unsigned i; + int n; + socklen_t addrlen; + char host[INET6_ADDRSTRLEN]; + char serv[6]; while (1) { - len = sizeof remote_address; + addrlen = sizeof remote_address; if ((n = recvfrom (ctx->fsockfd, (char*)fpdu, sizeof (fpdu), 0, - (struct sockaddr*) &remote_address, &len)) == -1) + (struct sockaddr *) &remote_address, &addrlen)) == -1) { - fprintf(stderr, "recvfrom(): %s\n", strerror(errno)); + fprintf (stderr, "recvfrom(): %s\n", strerror(errno)); exit (1); } if (n > PDU_SIZE) @@ -204,59 +343,111 @@ samplicate (ctx) n-PDU_SIZE); n = PDU_SIZE; } - if (len != sizeof remote_address) + if (addrlen != ctx->fsockaddrlen) { fprintf (stderr, "recvfrom() return address length %lu - expected %lu\n", - (unsigned long) len, (unsigned long) sizeof remote_address); + (unsigned long) addrlen, (unsigned long) ctx->fsockaddrlen); exit (1); } if (ctx->debug) { - fprintf (stderr, "received %d bytes from %s:%d\n", - n, - inet_ntoa (remote_address.sin_addr), - (int) ntohs (remote_address.sin_port)); + if (getnameinfo ((struct sockaddr *) &remote_address, addrlen, + host, INET6_ADDRSTRLEN, + serv, 6, + NI_NUMERICHOST|NI_NUMERICSERV) == -1) + { + strcpy (host, "???"); + strcpy (serv, "?????"); + } + fprintf (stderr, "received %d bytes from %s:%s\n", n, host, serv); } - - for(sctx = ctx->sources; sctx != NULL; sctx = sctx->next) + for (sctx = ctx->sources; sctx != NULL; sctx = sctx->next) { - if ((((struct sockaddr_in *) &sctx->source)->sin_addr.s_addr == 0) - || ((remote_address.sin_addr.s_addr & ((struct sockaddr_in *) &sctx->mask)->sin_addr.s_addr) - == ((struct sockaddr_in *) &sctx->source)->sin_addr.s_addr)) - for (i = 0; i < sctx->nreceivers; ++i) - { - if (sctx->receivers[i].freqcount == 0) - { - if (send_pdu_to_receiver (& (sctx->receivers[i]), fpdu, n, &remote_address) - == -1) - { - fprintf (stderr, "sending datagram to %s:%d failed: %s\n", - inet_ntoa (((struct sockaddr_in *) &sctx->receivers[i].addr)->sin_addr), - (int) ntohs (((struct sockaddr_in *) &sctx->receivers[i].addr)->sin_port), - strerror (errno)); - } - else if (ctx->debug) - { - fprintf (stderr, " sent to %s:%d\n", - inet_ntoa (((struct sockaddr_in *) &sctx->receivers[i].addr)->sin_addr), - (int) ntohs (((struct sockaddr_in *) &sctx->receivers[i].addr)->sin_port)); - } - sctx->receivers[i].freqcount = sctx->receivers[i].freq-1; - } - else - { - --sctx->receivers[i].freqcount; - } - if (sctx->tx_delay) - usleep (sctx->tx_delay); - } + if (match_addr_p ((struct sockaddr *) &remote_address, + (struct sockaddr *) &sctx->source, + (struct sockaddr *) &sctx->mask)) + { + sctx->matched_packets += 1; + sctx->matched_octets += n; + + for (i = 0; i < sctx->nreceivers; ++i) + { + struct receiver *receiver = &(sctx->receivers[i]); + + if (receiver->freqcount == 0) + { + if (send_pdu_to_receiver (receiver, fpdu, n, (struct sockaddr *) &remote_address) + == -1) + { + receiver->out_errors += 1; + if (getnameinfo ((struct sockaddr *) &receiver->addr, + receiver->addrlen, + host, INET6_ADDRSTRLEN, + serv, 6, + NI_NUMERICHOST|NI_NUMERICSERV) + == -1) + { + strcpy (host, "???"); + strcpy (serv, "?????"); + } + fprintf (stderr, "sending datagram to %s:%s failed: %s\n", + host, serv, strerror (errno)); + } + else + { + receiver->out_packets += 1; + receiver->out_octets += n; + + if (ctx->debug) + { + if (getnameinfo ((struct sockaddr *) &receiver->addr, + receiver->addrlen, + host, INET6_ADDRSTRLEN, + serv, 6, + NI_NUMERICHOST|NI_NUMERICSERV) + == -1) + { + strcpy (host, "???"); + strcpy (serv, "?????"); + } + fprintf (stderr, " sent to %s:%s\n", host, serv); + } + } + receiver->freqcount = receiver->freq-1; + } + else + { + receiver->freqcount -= 1; + } + if (sctx->tx_delay) + usleep (sctx->tx_delay); + } + } else { if (ctx->debug) { - fprintf (stderr, "Not matching %s/", inet_ntoa(((struct sockaddr_in *) &sctx->source)->sin_addr)); - fprintf (stderr, "%s\n", inet_ntoa(((struct sockaddr_in *) &sctx->mask)->sin_addr)); + if (getnameinfo ((struct sockaddr *) &sctx->source, + sctx->addrlen, + host, INET6_ADDRSTRLEN, + 0, 0, + NI_NUMERICHOST|NI_NUMERICSERV) + == -1) + { + strcpy (host, "???"); + } + fprintf (stderr, "Not matching %s/", host); + if (getnameinfo ((struct sockaddr *) &sctx->mask, + sctx->addrlen, + host, INET6_ADDRSTRLEN, + 0, 0, + NI_NUMERICHOST|NI_NUMERICSERV) + == -1) + { + strcpy (host, "???"); + } + fprintf (stderr, "%s\n", host); } } } @@ -268,7 +459,7 @@ send_pdu_to_receiver (receiver, fpdu, length, source_addr) struct receiver * receiver; const void * fpdu; size_t length; - struct sockaddr_in * source_addr; + struct sockaddr * source_addr; { if (receiver->flags & pf_SPOOF) { @@ -282,7 +473,80 @@ send_pdu_to_receiver (receiver, fpdu, length, source_addr) else { return sendto (receiver->fd, (char*) fpdu, length, 0, - (struct sockaddr*) &receiver->addr, - sizeof (struct sockaddr_in)); + (struct sockaddr*) &receiver->addr, receiver->addrlen); + } +} + +static int +make_cooked_udp_socket (long sockbuflen, int af) +{ + int s; + if ((s = socket (af == AF_INET ? PF_INET : PF_INET6, SOCK_DGRAM, 0)) == -1) + return s; + if (sockbuflen != -1) + { + if (setsockopt (s, SOL_SOCKET, SO_SNDBUF, + (char *) &sockbuflen, sizeof sockbuflen) == -1) + { + fprintf (stderr, "setsockopt(SO_SNDBUF,%ld): %s\n", + sockbuflen, strerror (errno)); + } } + return s; +} + +static int +make_udp_socket (long sockbuflen, int raw, int af) +{ + return raw + ? make_raw_udp_socket (sockbuflen, af) + : make_cooked_udp_socket (sockbuflen, af); +} + +static int +make_send_sockets (struct samplicator_context *ctx) +{ + /* Array of four sockets: + + First index: cooked(0)/raw(1) + Second index: IPv4(0)/IPv6(1) + + At a maximum, we need one socket of each kind. These sockets can + be used by multiple receivers of the same type. + */ + int socks[2][2] = { { -1, -1 }, { -1, -1 } }; + + struct source_context *sctx; + unsigned i; + + for (sctx = ctx->sources; sctx != 0; sctx = sctx->next) + { + for (i = 0; i < sctx->nreceivers; ++i) + { + struct receiver *receiver = &sctx->receivers[i]; + int af = receiver->addr.ss_family; + int af_index = af == AF_INET ? 0 : 1; + int spoof_p = receiver->flags & pf_SPOOF; + + if (socks[spoof_p][af_index] == -1) + { + if ((socks[spoof_p][af_index] = make_udp_socket (ctx->sockbuflen, spoof_p, af)) < 0) + { + if (spoof_p && errno == EPERM) + { + fprintf (stderr, "Not enough privilege for -S option---try again as root.\n"); + return -1; + } + else + { + fprintf (stderr, "Error creating%s socket: %s\n", + spoof_p ? " raw" : "", strerror (errno)); + } + return -1; + } + } + receiver->fd = socks[spoof_p][af_index]; + } + } + return 0; } diff --git a/samplicator.h b/samplicator.h index 3a2ebac..f83ce08 100644 --- a/samplicator.h +++ b/samplicator.h @@ -15,16 +15,26 @@ enum receiver_flags }; struct samplicator_context { - struct source_context *sources; - struct in_addr faddr; - int fport; + struct source_context *sources; + const char *faddr_spec; + struct sockaddr_storage faddr; + const char *fport_spec; long sockbuflen; int debug; int fork; + int ipv4_only; + int ipv6_only; const char *pid_file; - enum receiver_flags defaultflags; + enum receiver_flags default_receiver_flags; int fsockfd; + socklen_t fsockaddrlen; + + const char *config_file_name; + int config_file_lineno; + + /* statistics */ + uint32_t unmatched_packets; }; struct receiver { @@ -36,17 +46,26 @@ struct receiver { int freqcount; int ttl; enum receiver_flags flags; + + /* statistics */ + uint32_t out_packets; + uint32_t out_errors; + uint64_t out_octets; }; struct source_context { struct source_context *next; struct sockaddr_storage source; struct sockaddr_storage mask; + socklen_t addrlen; struct receiver *receivers; unsigned nreceivers; unsigned tx_delay; - int - debug; + int debug; + + /* statistics */ + uint32_t matched_packets; + uint64_t matched_octets; }; #endif /* not _SAMPLICATOR_H_ */