/* darkstat 3
- * copyright (c) 2001-2008 Emil Mikulic.
+ * copyright (c) 2001-2011 Emil Mikulic.
*
* acct.c: traffic accounting
*
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include "darkstat.h"
#include "acct.h"
+#include "decode.h"
#include "conv.h"
#include "daylog.h"
#include "err.h"
#include "hosts_db.h"
#include "localip.h"
#include "now.h"
+#include "opt.h"
-#include <arpa/inet.h> /* for inet_aton() */
#define __FAVOR_BSD
#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <assert.h>
+#include <ctype.h> /* for isdigit */
+#include <netdb.h> /* for gai_strerror */
#include <stdlib.h> /* for free */
#include <string.h> /* for memcpy */
-uint64_t total_packets = 0, total_bytes = 0;
+uint64_t acct_total_packets = 0, acct_total_bytes = 0;
-static int using_localnet = 0;
-static in_addr_t localnet, localmask;
+static int using_localnet4 = 0, using_localnet6 = 0;
+static struct addr localnet4, localmask4, localnet6, localmask6;
/* Parse the net/mask specification into two IPs or die trying. */
void
acct_init_localnet(const char *spec)
{
char **tokens;
- int num_tokens;
- struct in_addr addr;
+ unsigned int num_tokens;
+ int isnum, j, ret;
+ int pfxlen, octets, remainder;
+ struct addr localnet, localmask;
tokens = split('/', spec, &num_tokens);
if (num_tokens != 2)
errx(1, "expecting network/netmask, got \"%s\"", spec);
- if (inet_aton(tokens[0], &addr) != 1)
- errx(1, "invalid network address \"%s\"", tokens[0]);
- localnet = addr.s_addr;
+ if ((ret = str_to_addr(tokens[0], &localnet)) != 0)
+ errx(1, "couldn't parse \"%s\": %s", tokens[0], gai_strerror(ret));
- if (inet_aton(tokens[1], &addr) != 1)
- errx(1, "invalid network mask \"%s\"", tokens[1]);
- localmask = addr.s_addr;
- /* FIXME: improve so we can accept masks like /24 for 255.255.255.0 */
+ /* Detect a purely numeric argument. */
+ isnum = 0;
+ {
+ const char *p = tokens[1];
+ while (*p != '\0') {
+ if (isdigit(*p)) {
+ isnum = 1;
+ ++p;
+ continue;
+ } else {
+ isnum = 0;
+ break;
+ }
+ }
+ }
+
+ if (!isnum) {
+ if ((ret = str_to_addr(tokens[1], &localmask)) != 0)
+ errx(1, "couldn't parse \"%s\": %s", tokens[1], gai_strerror(ret));
+ if (localmask.family != localnet.family)
+ errx(1, "family mismatch between net and mask");
+ } else {
+ uint8_t frac, *p;
+ char *endptr;
+
+ localmask.family = localnet.family;
+
+ /* Compute the prefix length. */
+ pfxlen = (unsigned int)strtol(tokens[1], &endptr, 10);
+
+ if ((pfxlen < 0) ||
+ ((localnet.family == IPv6) && (pfxlen > 128)) ||
+ ((localnet.family == IPv4) && (pfxlen > 32)) ||
+ (tokens[1][0] == '\0') ||
+ (*endptr != '\0'))
+ errx(1, "invalid network prefix length \"%s\"", tokens[1]);
+
+ /* Construct the network mask. */
+ octets = pfxlen / 8;
+ remainder = pfxlen % 8;
+ p = (localnet.family == IPv6) ? (localmask.ip.v6.s6_addr)
+ : ((uint8_t *) &(localmask.ip.v4));
+
+ if (localnet.family == IPv6)
+ memset(p, 0, 16);
+ else
+ memset(p, 0, 4);
+
+ for (j = 0; j < octets; ++j)
+ p[j] = 0xff;
+
+ frac = (uint8_t)(0xff << (8 - remainder));
+ if (frac)
+ p[j] = frac; /* Have contribution for next position. */
+ }
- using_localnet = 1;
free(tokens[0]);
free(tokens[1]);
free(tokens);
- verbosef("local network address: %s", ip_to_str(localnet));
- verbosef(" local network mask: %s", ip_to_str(localmask));
+ /* Register the correct netmask and calculate the correct net. */
+ addr_mask(&localnet, &localmask);
+ if (localnet.family == IPv6) {
+ using_localnet6 = 1;
+ localnet6 = localnet;
+ localmask6 = localmask;
+ } else {
+ using_localnet4 = 1;
+ localnet4 = localnet;
+ localmask4 = localmask;
+ }
+
+ verbosef("local network address: %s", addr_to_str(&localnet));
+ verbosef(" local network mask: %s", addr_to_str(&localmask));
+}
- if ((localnet & localmask) != localnet)
- errx(1, "this is an invalid combination of address and mask!\n"
- "it cannot match any address!");
+static int
+addr_is_local(const struct addr * const a)
+{
+ if (a->family == IPv4) {
+ if (using_localnet4) {
+ if (addr_inside(a, &localnet4, &localmask4))
+ return 1;
+ } else {
+ if (addr_equal(a, &localip4))
+ return 1;
+ }
+ } else {
+ assert(a->family == IPv6);
+ if (using_localnet6) {
+ if (addr_inside(a, &localnet6, &localmask6))
+ return 1;
+ } else {
+ if (addr_equal(a, &localip6))
+ return 1;
+ }
+ }
+ return 0;
}
/* Account for the given packet summary. */
void
-acct_for(const pktsummary *sm)
+acct_for(const struct pktsummary * const sm)
{
struct bucket *hs = NULL, *hd = NULL;
struct bucket *ps, *pd;
int dir_in, dir_out;
#if 0 /* WANT_CHATTY? */
- printf("%15s > ", ip_to_str(sm->src_ip));
- printf("%15s ", ip_to_str(sm->dest_ip));
+ printf("%15s > ", addr_to_str(&sm->src));
+ printf("%15s ", addr_to_str(&sm->dst));
printf("len %4d proto %2d", sm->len, sm->proto);
if (sm->proto == IPPROTO_TCP || sm->proto == IPPROTO_UDP)
- printf(" port %5d : %5d", sm->src_port, sm->dest_port);
+ printf(" port %5d : %5d", sm->src_port, sm->dst_port);
if (sm->proto == IPPROTO_TCP)
printf(" %s%s%s%s%s%s",
(sm->tcp_flags & TH_FIN)?"F":"",
#endif
/* Totals. */
- total_packets++;
- total_bytes += sm->len;
-
- /* Hosts. */
- hs = host_get(sm->src_ip);
- hs->out += sm->len;
- hs->total += sm->len;
- memcpy(hs->u.host.mac_addr, sm->src_mac, sizeof(sm->src_mac));
- hs->u.host.last_seen = now;
-
- hd = host_get(sm->dest_ip); /* this can invalidate hs! */
- hd->in += sm->len;
- hd->total += sm->len;
- memcpy(hd->u.host.mac_addr, sm->dst_mac, sizeof(sm->dst_mac));
- hd->u.host.last_seen = now;
+ acct_total_packets++;
+ acct_total_bytes += sm->len;
/* Graphs. */
- dir_in = dir_out = 0;
-
- if (using_localnet) {
- if ((sm->src_ip & localmask) == localnet)
- dir_out = 1;
- if ((sm->dest_ip & localmask) == localnet)
- dir_in = 1;
- if (dir_in == 1 && dir_out == 1)
- /* Traffic staying within the network isn't counted. */
- dir_in = dir_out = 0;
- } else {
- if (sm->src_ip == localip)
- dir_out = 1;
- if (sm->dest_ip == localip)
- dir_in = 1;
- }
+ dir_out = addr_is_local(&(sm->src));
+ dir_in = addr_is_local(&(sm->dst));
+
+ /* Traffic staying within the network isn't counted. */
+ if (dir_in == 1 && dir_out == 1)
+ dir_in = dir_out = 0;
if (dir_out) {
daylog_acct((uint64_t)sm->len, GRAPH_OUT);
graph_acct((uint64_t)sm->len, GRAPH_IN);
}
+ if (opt_hosts_max == 0) return; /* skip per-host accounting */
+
+ /* Hosts. */
+ hosts_db_reduce();
+ if (!opt_want_local_only || addr_is_local(&sm->src)) {
+ hs = host_get(&(sm->src));
+ hs->out += sm->len;
+ hs->total += sm->len;
+ memcpy(hs->u.host.mac_addr, sm->src_mac, sizeof(sm->src_mac));
+ hs->u.host.lastseen = now;
+ }
+
+ if (!opt_want_local_only || addr_is_local(&sm->dst)) {
+ hd = host_get(&(sm->dst));
+ hd->in += sm->len;
+ hd->total += sm->len;
+ memcpy(hd->u.host.mac_addr, sm->dst_mac, sizeof(sm->dst_mac));
+ /*
+ * Don't update recipient's last seen time, we don't know that
+ * they received successfully.
+ */
+ }
+
/* Protocols. */
- hs = host_find(sm->src_ip);
- if (hs != NULL) {
- ps = host_get_ip_proto(hs, sm->proto);
- ps->out += sm->len;
- ps->total += sm->len;
+ if (sm->proto != IPPROTO_INVALID) {
+ if (hs) {
+ ps = host_get_ip_proto(hs, sm->proto);
+ ps->out += sm->len;
+ ps->total += sm->len;
+ }
+ if (hd) {
+ pd = host_get_ip_proto(hd, sm->proto);
+ pd->in += sm->len;
+ pd->total += sm->len;
+ }
}
- pd = host_get_ip_proto(hd, sm->proto);
- pd->in += sm->len;
- pd->total += sm->len;
+ if (opt_ports_max == 0) return; /* skip ports accounting */
/* Ports. */
- switch (sm->proto)
- {
+ switch (sm->proto) {
case IPPROTO_TCP:
- if ((sm->src_port <= highest_port) && (hs != NULL))
- {
+ if ((sm->src_port <= opt_highest_port) && hs) {
ps = host_get_port_tcp(hs, sm->src_port);
ps->out += sm->len;
ps->total += sm->len;
}
-
- if (sm->dest_port <= highest_port)
- {
- pd = host_get_port_tcp(hd, sm->dest_port);
+ if ((sm->dst_port <= opt_highest_port) && hd) {
+ pd = host_get_port_tcp(hd, sm->dst_port);
pd->in += sm->len;
pd->total += sm->len;
if (sm->tcp_flags == TH_SYN)
break;
case IPPROTO_UDP:
- if ((sm->src_port <= highest_port) && (hs != NULL))
- {
+ if ((sm->src_port <= opt_highest_port) && hs) {
ps = host_get_port_udp(hs, sm->src_port);
ps->out += sm->len;
ps->total += sm->len;
}
-
- if (sm->dest_port <= highest_port)
- {
- pd = host_get_port_udp(hd, sm->dest_port);
+ if ((sm->dst_port <= opt_highest_port) && hd) {
+ pd = host_get_port_udp(hd, sm->dst_port);
pd->in += sm->len;
pd->total += sm->len;
}
break;
case IPPROTO_ICMP:
+ case IPPROTO_ICMPV6:
+ case IPPROTO_AH:
+ case IPPROTO_ESP:
+ case IPPROTO_OSPF:
/* known protocol, don't complain about it */
break;