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