Implement full netmasking for IPv4 and IPv6.
[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 (sm->af == AF_INET6) return; /* Still no continuation for IPv6! */
226
227 if (hosts_max == 0) return; /* skip per-host accounting */
228
229 /* Hosts. */
230 ipaddr.af = sm->af; /* TODO */
231 ipaddr.addr.ip = sm->src_ip; /* TODO */
232 hs = host_get(&ipaddr);
233 hs->out += sm->len;
234 hs->total += sm->len;
235 memcpy(hs->u.host.mac_addr, sm->src_mac, sizeof(sm->src_mac));
236 hs->u.host.last_seen = now;
237
238 ipaddr.addr.ip = sm->dest_ip; /* TODO */
239 hd = host_get(&ipaddr); /* this can invalidate hs! */
240 hd->in += sm->len;
241 hd->total += sm->len;
242 memcpy(hd->u.host.mac_addr, sm->dst_mac, sizeof(sm->dst_mac));
243 hd->u.host.last_seen = now;
244
245 /* Protocols. */
246 ipaddr.addr.ip = sm->src_ip; /* TODO */
247 hs = host_find(&ipaddr);
248 if (hs != NULL) {
249 ps = host_get_ip_proto(hs, sm->proto);
250 ps->out += sm->len;
251 ps->total += sm->len;
252 }
253
254 pd = host_get_ip_proto(hd, sm->proto);
255 pd->in += sm->len;
256 pd->total += sm->len;
257
258 if (ports_max == 0) return; /* skip ports accounting */
259
260 /* Ports. */
261 switch (sm->proto)
262 {
263 case IPPROTO_TCP:
264 if ((sm->src_port <= highest_port) && (hs != NULL))
265 {
266 ps = host_get_port_tcp(hs, sm->src_port);
267 ps->out += sm->len;
268 ps->total += sm->len;
269 }
270
271 if (sm->dest_port <= highest_port)
272 {
273 pd = host_get_port_tcp(hd, sm->dest_port);
274 pd->in += sm->len;
275 pd->total += sm->len;
276 if (sm->tcp_flags == TH_SYN)
277 pd->u.port_tcp.syn++;
278 }
279 break;
280
281 case IPPROTO_UDP:
282 if ((sm->src_port <= highest_port) && (hs != NULL))
283 {
284 ps = host_get_port_udp(hs, sm->src_port);
285 ps->out += sm->len;
286 ps->total += sm->len;
287 }
288
289 if (sm->dest_port <= highest_port)
290 {
291 pd = host_get_port_udp(hd, sm->dest_port);
292 pd->in += sm->len;
293 pd->total += sm->len;
294 }
295 break;
296
297 case IPPROTO_ICMP:
298 case IPPROTO_ICMPV6:
299 /* known protocol, don't complain about it */
300 break;
301
302 default:
303 verbosef("unknown IP proto (%04x)", sm->proto);
304 }
305 }
306
307 /* vim:set ts=3 sw=3 tw=78 expandtab: */