Implement str_to_addr() and unit test.
[darkstat] / 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 "conv.h"
22 #include "daylog.h"
23 #include "err.h"
24 #include "hosts_db.h"
25 #include "localip.h"
26 #include "now.h"
27
28 #include <arpa/inet.h> /* for inet_aton() */
29 #define __FAVOR_BSD
30 #include <netinet/tcp.h>
31 #include <sys/socket.h>
32 #include <stdlib.h> /* for free */
33 #include <string.h> /* for memcpy */
34 #include <ctype.h> /* isdigit() */
35
36 uint64_t total_packets = 0, total_bytes = 0;
37
38 static int using_localnet = 0;
39 static int using_localnet6 = 0;
40 static in_addr_t localnet, localmask;
41 static struct in6_addr localnet6, localmask6;
42
43 /* Parse the net/mask specification into two IPs or die trying. */
44 void
45 acct_init_localnet(const char *spec)
46 {
47 char **tokens, *p;
48 int num_tokens, isnum, j;
49 int build_ipv6; /* Zero for IPv4, one for IPv6. */
50 int pfxlen, octets, remainder;
51 struct in_addr addr;
52 struct in6_addr addr6;
53
54 tokens = split('/', spec, &num_tokens);
55 if (num_tokens != 2)
56 errx(1, "expecting network/netmask, got \"%s\"", spec);
57
58 /* Presence of a colon distinguishes address families. */
59 if (strchr(tokens[0], ':')) {
60 build_ipv6 = 1;
61 if (inet_pton(AF_INET6, tokens[0], &addr6) != 1)
62 errx(1, "invalid IPv6 network address \"%s\"", tokens[0]);
63 memcpy(&localnet6, &addr6, sizeof(localnet6));
64 } else {
65 build_ipv6 = 0;
66 if (inet_pton(AF_INET, tokens[0], &addr) != 1)
67 errx(1, "invalid network address \"%s\"", tokens[0]);
68 localnet = addr.s_addr;
69 }
70
71 /* Detect a purely numeric argument. */
72 isnum = 0;
73 p = tokens[1];
74 while (*p != '\0') {
75 if (isdigit(*p)) {
76 isnum = 1;
77 ++p;
78 continue;
79 } else {
80 isnum = 0;
81 break;
82 }
83 }
84
85 if (!isnum) {
86 if (build_ipv6) {
87 if (inet_pton(AF_INET6, tokens[1], &addr6) != 1)
88 errx(1, "invalid IPv6 network mask \"%s\"", tokens[1]);
89 memcpy(&localmask6, &addr6, sizeof(localmask6));
90 } else {
91 if (inet_pton(AF_INET, tokens[1], &addr) != 1)
92 errx(1, "invalid network mask \"%s\"", tokens[1]);
93 localmask = addr.s_addr;
94 }
95 } else {
96 uint8_t frac, *p;
97
98 /* Compute the prefix length. */
99 pfxlen = strtonum(tokens[1], 1, build_ipv6 ? 128 : 32, NULL);
100 if (pfxlen == 0)
101 errx(1, "invalid network prefix length \"%s\"", tokens[1]);
102
103 /* Construct the network mask. */
104 octets = pfxlen / 8;
105 remainder = pfxlen % 8;
106 p = build_ipv6 ? (uint8_t *) localmask6.s6_addr : (uint8_t *) &localmask;
107
108 if (build_ipv6)
109 memset(&localmask6, 0, sizeof(localmask6));
110 else
111 memset(&localmask, 0, sizeof(localmask));
112
113 for (j = 0; j < octets; ++j)
114 p[j] = 0xff;
115
116 frac = 0xff << (8 - remainder);
117 if (frac)
118 p[j] = frac; /* Have contribution for next position. */
119 }
120
121 /* Register the correct netmask and calculate the correct net. */
122 if (build_ipv6) {
123 using_localnet6 = 1;
124 for (j = 0; j < 16; ++j)
125 localnet6.s6_addr[j] &= localmask6.s6_addr[j];
126 } else {
127 using_localnet = 1;
128 localnet &= localmask;
129 }
130
131 free(tokens[0]);
132 free(tokens[1]);
133 free(tokens);
134
135 if (build_ipv6) {
136 verbosef("local network address: %s", ip_to_str_af(&localnet6, AF_INET6));
137 verbosef(" local network mask: %s", ip_to_str_af(&localmask6, AF_INET6));
138 } else {
139 verbosef("local network address: %s", ip_to_str_af(&localnet, AF_INET));
140 verbosef(" local network mask: %s", ip_to_str_af(&localmask, AF_INET));
141 }
142
143 }
144
145 /* Account for the given packet summary. */
146 void
147 acct_for(const pktsummary *sm)
148 {
149 struct bucket *hs = NULL, *hd = NULL;
150 struct bucket *ps, *pd;
151 struct addr46 ipaddr;
152 struct in6_addr scribble;
153 int dir_in, dir_out, j;
154
155 #if 0 /* WANT_CHATTY? */
156 printf("%15s > ", ip_to_str_af(&sm->src_ip, AF_INET));
157 printf("%15s ", ip_to_str_af(&sm->dest_ip, AF_INET));
158 printf("len %4d proto %2d", sm->len, sm->proto);
159
160 if (sm->proto == IPPROTO_TCP || sm->proto == IPPROTO_UDP)
161 printf(" port %5d : %5d", sm->src_port, sm->dest_port);
162 if (sm->proto == IPPROTO_TCP)
163 printf(" %s%s%s%s%s%s",
164 (sm->tcp_flags & TH_FIN)?"F":"",
165 (sm->tcp_flags & TH_SYN)?"S":"",
166 (sm->tcp_flags & TH_RST)?"R":"",
167 (sm->tcp_flags & TH_PUSH)?"P":"",
168 (sm->tcp_flags & TH_ACK)?"A":"",
169 (sm->tcp_flags & TH_URG)?"U":""
170 );
171 printf("\n");
172 #endif
173
174 /* Totals. */
175 total_packets++;
176 total_bytes += sm->len;
177
178 /* Graphs. */
179 dir_in = dir_out = 0;
180
181 if (sm->af == AF_INET) {
182 if (using_localnet) {
183 if ((sm->src_ip.s_addr & localmask) == localnet)
184 dir_out = 1;
185 if ((sm->dest_ip.s_addr & localmask) == localnet)
186 dir_in = 1;
187 if (dir_in == 1 && dir_out == 1)
188 /* Traffic staying within the network isn't counted. */
189 dir_in = dir_out = 0;
190 } else {
191 if (memcmp(&sm->src_ip, &localip, sizeof(localip)) == 0)
192 dir_out = 1;
193 if (memcmp(&sm->dest_ip, &localip, sizeof(localip)) == 0)
194 dir_in = 1;
195 }
196 } else if (sm->af == AF_INET6) {
197 if (using_localnet6) {
198 for (j = 0; j < 16; ++j)
199 scribble.s6_addr[j] = sm->src_ip6.s6_addr[j] & localmask6.s6_addr[j];
200 if (memcmp(&scribble, &localnet6, sizeof(scribble)) == 0)
201 dir_out = 1;
202 else {
203 for (j = 0; j < 16; ++j)
204 scribble.s6_addr[j] = sm->dest_ip6.s6_addr[j] & localmask6.s6_addr[j];
205 if (memcmp(&scribble, &localnet6, sizeof(scribble)) == 0)
206 dir_in = 1;
207 }
208 } else {
209 if (memcmp(&sm->src_ip6, &localip6, sizeof(localip6)) == 0)
210 dir_out = 1;
211 if (memcmp(&sm->dest_ip6, &localip6, sizeof(localip6)) == 0)
212 dir_in = 1;
213 }
214 }
215
216 if (dir_out) {
217 daylog_acct((uint64_t)sm->len, GRAPH_OUT);
218 graph_acct((uint64_t)sm->len, GRAPH_OUT);
219 }
220 if (dir_in) {
221 daylog_acct((uint64_t)sm->len, GRAPH_IN);
222 graph_acct((uint64_t)sm->len, GRAPH_IN);
223 }
224
225 if (hosts_max == 0) return; /* skip per-host accounting */
226
227 /* Hosts. */
228 ipaddr.af = sm->af;
229 switch (ipaddr.af) {
230 case AF_INET6:
231 memcpy(&ipaddr.addr.ip6, &sm->src_ip6, sizeof(ipaddr.addr.ip6));
232 break;
233 case AF_INET:
234 default:
235 memcpy(&ipaddr.addr.ip, &sm->src_ip, sizeof(ipaddr.addr.ip));
236 break;
237 }
238 hs = host_get(&ipaddr);
239 hs->out += sm->len;
240 hs->total += sm->len;
241 memcpy(hs->u.host.mac_addr, sm->src_mac, sizeof(sm->src_mac));
242 hs->u.host.last_seen = now;
243
244 switch (ipaddr.af) {
245 case AF_INET6:
246 memcpy(&ipaddr.addr.ip6, &sm->dest_ip6, sizeof(ipaddr.addr.ip6));
247 break;
248 case AF_INET:
249 default:
250 memcpy(&ipaddr.addr.ip, &sm->dest_ip, sizeof(ipaddr.addr.ip));
251 break;
252 }
253 hd = host_get(&ipaddr); /* this can invalidate hs! */
254 hd->in += sm->len;
255 hd->total += sm->len;
256 memcpy(hd->u.host.mac_addr, sm->dst_mac, sizeof(sm->dst_mac));
257 hd->u.host.last_seen = now;
258
259 /* Protocols. */
260 switch (ipaddr.af) {
261 case AF_INET6:
262 memcpy(&ipaddr.addr.ip6, &sm->src_ip6, sizeof(ipaddr.addr.ip6));
263 break;
264 case AF_INET:
265 default:
266 memcpy(&ipaddr.addr.ip, &sm->src_ip, sizeof(ipaddr.addr.ip));
267 break;
268 }
269 hs = host_find(&ipaddr);
270 if (hs != NULL) {
271 ps = host_get_ip_proto(hs, sm->proto);
272 ps->out += sm->len;
273 ps->total += sm->len;
274 }
275
276 pd = host_get_ip_proto(hd, sm->proto);
277 pd->in += sm->len;
278 pd->total += sm->len;
279
280 if (ports_max == 0) return; /* skip ports accounting */
281
282 /* Ports. */
283 switch (sm->proto)
284 {
285 case IPPROTO_TCP:
286 if ((sm->src_port <= highest_port) && (hs != NULL))
287 {
288 ps = host_get_port_tcp(hs, sm->src_port);
289 ps->out += sm->len;
290 ps->total += sm->len;
291 }
292
293 if (sm->dest_port <= highest_port)
294 {
295 pd = host_get_port_tcp(hd, sm->dest_port);
296 pd->in += sm->len;
297 pd->total += sm->len;
298 if (sm->tcp_flags == TH_SYN)
299 pd->u.port_tcp.syn++;
300 }
301 break;
302
303 case IPPROTO_UDP:
304 if ((sm->src_port <= highest_port) && (hs != NULL))
305 {
306 ps = host_get_port_udp(hs, sm->src_port);
307 ps->out += sm->len;
308 ps->total += sm->len;
309 }
310
311 if (sm->dest_port <= highest_port)
312 {
313 pd = host_get_port_udp(hd, sm->dest_port);
314 pd->in += sm->len;
315 pd->total += sm->len;
316 }
317 break;
318
319 case IPPROTO_ICMP:
320 case IPPROTO_ICMPV6:
321 case IPPROTO_AH:
322 case IPPROTO_ESP:
323 case IPPROTO_OSPF:
324 /* known protocol, don't complain about it */
325 break;
326
327 default:
328 verbosef("unknown IP proto (%04x)", sm->proto);
329 }
330 }
331
332 /* vim:set ts=3 sw=3 tw=78 expandtab: */