Whoops, I included sys/types in the wrong file.
[darkstat.git] / acct.c
1 /* darkstat 3
2  * copyright (c) 2001-2011 Emil Mikulic.
3  *
4  * acct.c: traffic accounting
5  *
6  * Permission to use, copy, modify, and distribute this file for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 #include "acct.h"
20 #include "decode.h"
21 #include "conv.h"
22 #include "daylog.h"
23 #include "err.h"
24 #include "hosts_db.h"
25 #include "localip.h"
26 #include "now.h"
27 #include "opt.h"
28
29 #include <arpa/inet.h> /* for inet_aton() */
30 #define __FAVOR_BSD
31 #include <netinet/tcp.h>
32 #include <sys/socket.h>
33 #include <assert.h>
34 #include <ctype.h> /* for isdigit */
35 #include <netdb.h> /* for gai_strerror */
36 #include <stdlib.h> /* for free */
37 #include <string.h> /* for memcpy */
38
39 uint64_t acct_total_packets = 0, acct_total_bytes = 0;
40
41 static int using_localnet4 = 0, using_localnet6 = 0;
42 static struct addr localnet4, localmask4, localnet6, localmask6;
43
44 /* Parse the net/mask specification into two IPs or die trying. */
45 void
46 acct_init_localnet(const char *spec)
47 {
48    char **tokens;
49    unsigned int num_tokens;
50    int isnum, j, ret;
51    int pfxlen, octets, remainder;
52    struct addr localnet, localmask;
53
54    tokens = split('/', spec, &num_tokens);
55    if (num_tokens != 2)
56       errx(1, "expecting network/netmask, got \"%s\"", spec);
57
58    if ((ret = str_to_addr(tokens[0], &localnet)) != 0)
59       errx(1, "couldn't parse \"%s\": %s", tokens[0], gai_strerror(ret));
60
61    /* Detect a purely numeric argument.  */
62    isnum = 0;
63    {
64       const char *p = tokens[1];
65       while (*p != '\0') {
66          if (isdigit(*p)) {
67             isnum = 1;
68             ++p;
69             continue;
70          } else {
71             isnum = 0;
72             break;
73          }
74       }
75    }
76
77    if (!isnum) {
78       if ((ret = str_to_addr(tokens[1], &localmask)) != 0)
79          errx(1, "couldn't parse \"%s\": %s", tokens[1], gai_strerror(ret));
80       if (localmask.family != localnet.family)
81          errx(1, "family mismatch between net and mask");
82    } else {
83       uint8_t frac, *p;
84
85       localmask.family = localnet.family;
86
87       /* Compute the prefix length.  */
88       pfxlen = (int)strtonum(tokens[1], 1,
89          (localnet.family == IPv6) ? 128 : 32, NULL);
90
91       if (pfxlen == 0)
92          errx(1, "invalid network prefix length \"%s\"", tokens[1]);
93
94       /* Construct the network mask.  */
95       octets = pfxlen / 8;
96       remainder = pfxlen % 8;
97       p = (localnet.family == IPv6) ? (localmask.ip.v6.s6_addr)
98                                     : ((uint8_t *) &(localmask.ip.v4));
99
100       if (localnet.family == IPv6)
101          memset(p, 0, 16);
102       else
103          memset(p, 0, 4);
104
105       for (j = 0; j < octets; ++j)
106          p[j] = 0xff;
107
108       frac = (uint8_t)(0xff << (8 - remainder));
109       if (frac)
110          p[j] = frac;   /* Have contribution for next position.  */
111    }
112
113    free(tokens[0]);
114    free(tokens[1]);
115    free(tokens);
116
117    /* Register the correct netmask and calculate the correct net.  */
118    addr_mask(&localnet, &localmask);
119    if (localnet.family == IPv6) {
120       using_localnet6 = 1;
121       localnet6 = localnet;
122       localmask6 = localmask;
123    } else {
124       using_localnet4 = 1;
125       localnet4 = localnet;
126       localmask4 = localmask;
127    }
128
129    verbosef("local network address: %s", addr_to_str(&localnet));
130    verbosef("   local network mask: %s", addr_to_str(&localmask));
131 }
132
133 static int
134 addr_is_local(const struct addr * const a)
135 {
136    if (a->family == IPv4) {
137       if (using_localnet4) {
138          if (addr_inside(a, &localnet4, &localmask4))
139             return 1;
140       } else {
141          if (addr_equal(a, &localip4))
142             return 1;
143       }
144    } else {
145       assert(a->family == IPv6);
146       if (using_localnet6) {
147          if (addr_inside(a, &localnet6, &localmask6))
148             return 1;
149       } else {
150          if (addr_equal(a, &localip6))
151             return 1;
152       }
153    }
154    return 0;
155 }
156
157 /* Account for the given packet summary. */
158 void
159 acct_for(const struct pktsummary * const sm)
160 {
161    struct bucket *hs = NULL, *hd = NULL;
162    struct bucket *ps, *pd;
163    int dir_in, dir_out;
164
165 #if 0 /* WANT_CHATTY? */
166    printf("%15s > ", addr_to_str(&sm->src));
167    printf("%15s ", addr_to_str(&sm->dst));
168    printf("len %4d proto %2d", sm->len, sm->proto);
169
170    if (sm->proto == IPPROTO_TCP || sm->proto == IPPROTO_UDP)
171       printf(" port %5d : %5d", sm->src_port, sm->dst_port);
172    if (sm->proto == IPPROTO_TCP)
173       printf(" %s%s%s%s%s%s",
174          (sm->tcp_flags & TH_FIN)?"F":"",
175          (sm->tcp_flags & TH_SYN)?"S":"",
176          (sm->tcp_flags & TH_RST)?"R":"",
177          (sm->tcp_flags & TH_PUSH)?"P":"",
178          (sm->tcp_flags & TH_ACK)?"A":"",
179          (sm->tcp_flags & TH_URG)?"U":""
180       );
181    printf("\n");
182 #endif
183
184    /* Totals. */
185    acct_total_packets++;
186    acct_total_bytes += sm->len;
187
188    /* Graphs. */
189    dir_out = addr_is_local(&(sm->src));
190    dir_in  = addr_is_local(&(sm->dst));
191
192    /* Traffic staying within the network isn't counted. */
193    if (dir_in == 1 && dir_out == 1)
194       dir_in = dir_out = 0;
195
196    if (dir_out) {
197       daylog_acct((uint64_t)sm->len, GRAPH_OUT);
198       graph_acct((uint64_t)sm->len, GRAPH_OUT);
199    }
200    if (dir_in) {
201       daylog_acct((uint64_t)sm->len, GRAPH_IN);
202       graph_acct((uint64_t)sm->len, GRAPH_IN);
203    }
204
205    if (opt_hosts_max == 0) return; /* skip per-host accounting */
206
207    /* Hosts. */
208    hosts_db_reduce();
209    hs = host_get(&(sm->src));
210    hs->out   += sm->len;
211    hs->total += sm->len;
212    memcpy(hs->u.host.mac_addr, sm->src_mac, sizeof(sm->src_mac));
213    hs->u.host.last_seen = now;
214
215    hd = host_get(&(sm->dst));
216    hd->in    += sm->len;
217    hd->total += sm->len;
218    memcpy(hd->u.host.mac_addr, sm->dst_mac, sizeof(sm->dst_mac));
219    hd->u.host.last_seen = now;
220
221    /* Protocols. */
222    if (sm->proto != IPPROTO_INVALID) {
223       ps = host_get_ip_proto(hs, sm->proto);
224       ps->out   += sm->len;
225       ps->total += sm->len;
226
227       pd = host_get_ip_proto(hd, sm->proto);
228       pd->in    += sm->len;
229       pd->total += sm->len;
230    }
231
232    if (opt_ports_max == 0) return; /* skip ports accounting */
233
234    /* Ports. */
235    switch (sm->proto) {
236    case IPPROTO_TCP:
237       if (sm->src_port <= opt_highest_port) {
238          ps = host_get_port_tcp(hs, sm->src_port);
239          ps->out   += sm->len;
240          ps->total += sm->len;
241       }
242
243       if (sm->dst_port <= opt_highest_port) {
244          pd = host_get_port_tcp(hd, sm->dst_port);
245          pd->in    += sm->len;
246          pd->total += sm->len;
247          if (sm->tcp_flags == TH_SYN)
248             pd->u.port_tcp.syn++;
249       }
250       break;
251
252    case IPPROTO_UDP:
253       if (sm->src_port <= opt_highest_port) {
254          ps = host_get_port_udp(hs, sm->src_port);
255          ps->out   += sm->len;
256          ps->total += sm->len;
257       }
258
259       if (sm->dst_port <= opt_highest_port) {
260          pd = host_get_port_udp(hd, sm->dst_port);
261          pd->in    += sm->len;
262          pd->total += sm->len;
263       }
264       break;
265
266    case IPPROTO_ICMP:
267    case IPPROTO_ICMPV6:
268    case IPPROTO_AH:
269    case IPPROTO_ESP:
270    case IPPROTO_OSPF:
271       /* known protocol, don't complain about it */
272       break;
273
274    default:
275       verbosef("unknown IP proto (%04x)", sm->proto);
276    }
277 }
278
279 /* vim:set ts=3 sw=3 tw=78 expandtab: */