Remove dead stores.
[darkstat] / hosts_db.c
1 /* darkstat 3
2 * copyright (c) 2001-2009 Emil Mikulic.
3 *
4 * hosts_db.c: database of hosts, ports, protocols.
5 *
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
8 */
9
10 #include "darkstat.h"
11 #include "conv.h"
12 #include "decode.h"
13 #include "dns.h"
14 #include "err.h"
15 #include "hosts_db.h"
16 #include "db.h"
17 #include "html.h"
18 #include "ncache.h"
19 #include "now.h"
20
21 #include <arpa/inet.h> /* inet_aton() */
22 #include <assert.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h> /* memset(), strcmp() */
27 #include <unistd.h>
28
29 extern int want_lastseen;
30 int show_mac_addrs = 0;
31 extern const char *interface;
32
33 /* FIXME: specify somewhere more sane/tunable */
34 #define MAX_ENTRIES 30 /* in an HTML table rendered from a hashtable */
35
36 typedef uint32_t (hash_func_t)(const struct hashtable *, const void *);
37 typedef void (free_func_t)(struct bucket *);
38 typedef const void * (key_func_t)(const struct bucket *);
39 typedef int (find_func_t)(const struct bucket *, const void *);
40 typedef struct bucket * (make_func_t)(const void *);
41 typedef void (format_cols_func_t)(struct str *);
42 typedef void (format_row_func_t)(struct str *, const struct bucket *,
43 const char *);
44
45 struct hashtable {
46 uint8_t bits; /* size of hashtable in bits */
47 uint32_t size, mask;
48 uint32_t count, count_max, count_keep; /* items in table */
49 uint32_t coeff; /* coefficient for Fibonacci hashing */
50 struct bucket **table;
51
52 struct {
53 uint64_t inserts, searches, deletions, rehashes;
54 } stats;
55
56 hash_func_t *hash_func;
57 /* returns hash value of given key (passed as void*) */
58
59 free_func_t *free_func;
60 /* free of bucket payload */
61
62 key_func_t *key_func;
63 /* returns pointer to key of bucket (to pass to hash_func) */
64
65 find_func_t *find_func;
66 /* returns true if given bucket matches key (passed as void*) */
67
68 make_func_t *make_func;
69 /* returns bucket containing new record with key (passed as void*) */
70
71 format_cols_func_t *format_cols_func;
72 /* append table columns to str */
73
74 format_row_func_t *format_row_func;
75 /* format record and append to str */
76 };
77
78 static void hashtable_reduce(struct hashtable *ht);
79 static void hashtable_free(struct hashtable *h);
80
81 #define HOST_BITS 1 /* initial size of hosts table */
82 #define PORT_BITS 1 /* initial size of ports tables */
83 #define PROTO_BITS 1 /* initial size of proto table */
84
85 /* We only use one hosts_db hashtable and this is it. */
86 static struct hashtable *hosts_db = NULL;
87
88 /* phi^-1 (reciprocal of golden ratio) = (sqrt(5) - 1) / 2 */
89 static const double phi_1 =
90 0.61803398874989490252573887119069695472717285156250;
91
92 /* Co-prime of u, using phi^-1 */
93 inline static uint32_t
94 coprime(const uint32_t u)
95 {
96 return ( (uint32_t)( (double)(u) * phi_1 ) | 1U );
97 }
98
99 /*
100 * This is the "recommended" IPv4 hash function, as seen in FreeBSD's
101 * src/sys/netinet/tcp_hostcache.c 1.1
102 */
103 inline static uint32_t
104 ipv4_hash(const uint32_t ip)
105 {
106 return ( (ip) ^ ((ip) >> 7) ^ ((ip) >> 17) );
107 }
108
109 /* ---------------------------------------------------------------------------
110 * hash_func collection
111 */
112 #define CASTKEY(type) (*((const type *)key))
113
114 static uint32_t
115 hash_func_host(const struct hashtable *h _unused_, const void *key)
116 {
117 return (ipv4_hash(CASTKEY(in_addr_t)));
118 }
119
120 static uint32_t
121 hash_func_short(const struct hashtable *h, const void *key)
122 {
123 return (CASTKEY(uint16_t) * h->coeff);
124 }
125
126 static uint32_t
127 hash_func_byte(const struct hashtable *h, const void *key)
128 {
129 return (CASTKEY(uint8_t) * h->coeff);
130 }
131
132 /* ---------------------------------------------------------------------------
133 * key_func collection
134 */
135
136 static const void *
137 key_func_host(const struct bucket *b)
138 {
139 return &(b->u.host.ip);
140 }
141
142 static const void *
143 key_func_port_tcp(const struct bucket *b)
144 {
145 return &(b->u.port_tcp.port);
146 }
147
148 static const void *
149 key_func_port_udp(const struct bucket *b)
150 {
151 return &(b->u.port_udp.port);
152 }
153
154 static const void *
155 key_func_ip_proto(const struct bucket *b)
156 {
157 return &(b->u.ip_proto.proto);
158 }
159
160 /* ---------------------------------------------------------------------------
161 * find_func collection
162 */
163
164 static int
165 find_func_host(const struct bucket *b, const void *key)
166 {
167 return (b->u.host.ip == CASTKEY(in_addr_t));
168 }
169
170 static int
171 find_func_port_tcp(const struct bucket *b, const void *key)
172 {
173 return (b->u.port_tcp.port == CASTKEY(uint16_t));
174 }
175
176 static int
177 find_func_port_udp(const struct bucket *b, const void *key)
178 {
179 return (b->u.port_udp.port == CASTKEY(uint16_t));
180 }
181
182 static int
183 find_func_ip_proto(const struct bucket *b, const void *key)
184 {
185 return (b->u.ip_proto.proto == CASTKEY(uint8_t));
186 }
187
188 /* ---------------------------------------------------------------------------
189 * make_func collection
190 */
191
192 #define MAKE_BUCKET(name_bucket, name_content, type) struct { \
193 struct bucket *next; \
194 uint64_t in, out, total; \
195 union { struct type t; } u; } _custom_bucket; \
196 struct bucket *name_bucket = xmalloc(sizeof(_custom_bucket)); \
197 struct type *name_content = &(name_bucket->u.type); \
198 name_bucket->next = NULL; \
199 name_bucket->in = name_bucket->out = name_bucket->total = 0;
200
201 static struct bucket *
202 make_func_host(const void *key)
203 {
204 MAKE_BUCKET(b, h, host);
205 h->ip = CASTKEY(in_addr_t);
206 h->dns = NULL;
207 h->last_seen = now;
208 memset(&h->mac_addr, 0, sizeof(h->mac_addr));
209 h->ports_tcp = NULL;
210 h->ports_udp = NULL;
211 h->ip_protos = NULL;
212 return (b);
213 }
214
215 static void
216 free_func_host(struct bucket *b)
217 {
218 struct host *h = &(b->u.host);
219 if (h->dns != NULL) free(h->dns);
220 hashtable_free(h->ports_tcp);
221 hashtable_free(h->ports_udp);
222 hashtable_free(h->ip_protos);
223 }
224
225 static struct bucket *
226 make_func_port_tcp(const void *key)
227 {
228 MAKE_BUCKET(b, p, port_tcp);
229 p->port = CASTKEY(uint16_t);
230 p->syn = 0;
231 return (b);
232 }
233
234 static struct bucket *
235 make_func_port_udp(const void *key)
236 {
237 MAKE_BUCKET(b, p, port_udp);
238 p->port = CASTKEY(uint16_t);
239 return (b);
240 }
241
242 static struct bucket *
243 make_func_ip_proto(const void *key)
244 {
245 MAKE_BUCKET(b, p, ip_proto);
246 p->proto = CASTKEY(uint8_t);
247 return (b);
248 }
249
250 static void
251 free_func_simple(struct bucket *b _unused_)
252 {
253 /* nop */
254 }
255
256 /* ---------------------------------------------------------------------------
257 * format_func collection (ordered by struct)
258 */
259
260 static void
261 format_cols_host(struct str *buf)
262 {
263 /* FIXME: don't clobber parts of the query string
264 * specifically "full" and "start"
265 * when setting sort direction
266 */
267 str_append(buf,
268 "<table>\n"
269 "<tr>\n"
270 " <th>IP</th>\n"
271 " <th>Hostname</th>\n");
272 if (show_mac_addrs) str_append(buf,
273 " <th>MAC Address</th>\n");
274 str_append(buf,
275 " <th><a href=\"?sort=in\">In</a></th>\n"
276 " <th><a href=\"?sort=out\">Out</a></th>\n"
277 " <th><a href=\"?sort=total\">Total</a></th>\n");
278 if (want_lastseen) str_append(buf,
279 " <th>Last seen</th>\n");
280 str_append(buf,
281 "</tr>\n");
282 }
283
284 static void
285 format_row_host(struct str *buf, const struct bucket *b,
286 const char *css_class)
287 {
288 const char *ip = ip_to_str( b->u.host.ip );
289
290 str_appendf(buf,
291 "<tr class=\"%s\">\n"
292 " <td><a href=\"%s/\">%s</a></td>\n"
293 " <td>%s</td>\n",
294 css_class,
295 ip, ip,
296 (b->u.host.dns == NULL) ? "" : b->u.host.dns);
297
298 if (show_mac_addrs)
299 str_appendf(buf,
300 " <td><tt>%x:%x:%x:%x:%x:%x</tt></td>\n",
301 b->u.host.mac_addr[0],
302 b->u.host.mac_addr[1],
303 b->u.host.mac_addr[2],
304 b->u.host.mac_addr[3],
305 b->u.host.mac_addr[4],
306 b->u.host.mac_addr[5]);
307
308 str_appendf(buf,
309 " <td class=\"num\">%'qu</td>\n"
310 " <td class=\"num\">%'qu</td>\n"
311 " <td class=\"num\">%'qu</td>\n",
312 b->in, b->out, b->total);
313
314 if (want_lastseen) {
315 time_t last_t = b->u.host.last_seen;
316 struct str *lastseen = NULL;
317
318 if (now >= last_t)
319 lastseen = length_of_time(now - last_t);
320
321 str_append(buf,
322 " <td class=\"num\">");
323 if (lastseen == NULL)
324 str_append(buf, "(clock error)");
325 else {
326 str_appendstr(buf, lastseen);
327 str_free(lastseen);
328 }
329 str_append(buf,
330 "</td>");
331 }
332
333 str_appendf(buf,
334 "</tr>\n");
335
336 /* Only resolve hosts "on demand" */
337 if (b->u.host.dns == NULL)
338 dns_queue(b->u.host.ip);
339 }
340
341 static void
342 format_cols_port_tcp(struct str *buf)
343 {
344 str_append(buf,
345 "<table>\n"
346 "<tr>\n"
347 " <th>Port</td>\n"
348 " <th>Service</td>\n"
349 " <th>In</td>\n"
350 " <th>Out</td>\n"
351 " <th>Total</td>\n"
352 " <th>SYNs</td>\n"
353 "</tr>\n"
354 );
355 }
356
357 static void
358 format_row_port_tcp(struct str *buf, const struct bucket *b,
359 const char *css_class)
360 {
361 const struct port_tcp *p = &(b->u.port_tcp);
362
363 str_appendf(buf,
364 "<tr class=\"%s\">\n"
365 " <td class=\"num\">%u</td>\n"
366 " <td>%s</td>\n"
367 " <td class=\"num\">%'qu</td>\n"
368 " <td class=\"num\">%'qu</td>\n"
369 " <td class=\"num\">%'qu</td>\n"
370 " <td class=\"num\">%'qu</td>\n"
371 "</tr>\n",
372 css_class,
373 p->port, getservtcp(p->port), b->in, b->out, b->total, p->syn
374 );
375 }
376
377 static void
378 format_cols_port_udp(struct str *buf)
379 {
380 str_append(buf,
381 "<table>\n"
382 "<tr>\n"
383 " <th>Port</td>\n"
384 " <th>Service</td>\n"
385 " <th>In</td>\n"
386 " <th>Out</td>\n"
387 " <th>Total</td>\n"
388 "</tr>\n"
389 );
390 }
391
392 static void
393 format_row_port_udp(struct str *buf, const struct bucket *b,
394 const char *css_class)
395 {
396 const struct port_udp *p = &(b->u.port_udp);
397
398 str_appendf(buf,
399 "<tr class=\"%s\">\n"
400 " <td class=\"num\">%u</td>\n"
401 " <td>%s</td>\n"
402 " <td class=\"num\">%'qu</td>\n"
403 " <td class=\"num\">%'qu</td>\n"
404 " <td class=\"num\">%'qu</td>\n"
405 "</tr>\n",
406 css_class,
407 p->port, getservudp(p->port), b->in, b->out, b->total
408 );
409 }
410
411 static void
412 format_cols_ip_proto(struct str *buf)
413 {
414 str_append(buf,
415 "<table>\n"
416 "<tr>\n"
417 " <th>#</td>\n"
418 " <th>Protocol</td>\n"
419 " <th>In</td>\n"
420 " <th>Out</td>\n"
421 " <th>Total</td>\n"
422 "</tr>\n"
423 );
424 }
425
426 static void
427 format_row_ip_proto(struct str *buf, const struct bucket *b,
428 const char *css_class)
429 {
430 const struct ip_proto *p = &(b->u.ip_proto);
431
432 str_appendf(buf,
433 "<tr class=\"%s\">\n"
434 " <td class=\"num\">%u</td>\n"
435 " <td>%s</td>\n"
436 " <td class=\"num\">%'qu</td>\n"
437 " <td class=\"num\">%'qu</td>\n"
438 " <td class=\"num\">%'qu</td>\n"
439 "</tr>\n",
440 css_class,
441 p->proto, getproto(p->proto),
442 b->in, b->out, b->total
443 );
444 }
445
446 /* ---------------------------------------------------------------------------
447 * Initialise a hashtable.
448 */
449 static struct hashtable *
450 hashtable_make(const uint8_t bits,
451 const unsigned int count_max,
452 const unsigned int count_keep,
453 hash_func_t *hash_func,
454 free_func_t *free_func,
455 key_func_t *key_func,
456 find_func_t *find_func,
457 make_func_t *make_func,
458 format_cols_func_t *format_cols_func,
459 format_row_func_t *format_row_func)
460 {
461 struct hashtable *hash;
462 assert(bits > 0);
463
464 hash = xmalloc(sizeof(*hash));
465 hash->bits = bits;
466 hash->count_max = count_max;
467 hash->count_keep = count_keep;
468 hash->size = 1U << bits;
469 hash->mask = hash->size - 1;
470 hash->coeff = coprime(hash->size);
471 hash->hash_func = hash_func;
472 hash->free_func = free_func;
473 hash->key_func = key_func;
474 hash->find_func = find_func;
475 hash->make_func = make_func;
476 hash->format_cols_func = format_cols_func;
477 hash->format_row_func = format_row_func;
478 hash->count = 0;
479 hash->table = xcalloc(hash->size, sizeof(*hash->table));
480 memset(&(hash->stats), 0, sizeof(hash->stats));
481 return (hash);
482 }
483
484 /* ---------------------------------------------------------------------------
485 * Initialise global hosts_db.
486 */
487 void
488 hosts_db_init(void)
489 {
490 assert(hosts_db == NULL);
491 hosts_db = hashtable_make(HOST_BITS, hosts_max, hosts_keep,
492 hash_func_host, free_func_host, key_func_host, find_func_host,
493 make_func_host, format_cols_host, format_row_host);
494 }
495
496 static void
497 hashtable_rehash(struct hashtable *h, const uint8_t bits)
498 {
499 struct bucket **old_table, **new_table;
500 uint32_t i, old_size;
501 assert(h != NULL);
502 assert(bits > 0);
503
504 h->stats.rehashes++;
505 old_size = h->size;
506 old_table = h->table;
507
508 h->bits = bits;
509 h->size = 1U << bits;
510 h->mask = h->size - 1;
511 h->coeff = coprime(h->size);
512 new_table = xcalloc(h->size, sizeof(*new_table));
513
514 for (i=0; i<old_size; i++) {
515 struct bucket *next, *b = old_table[i];
516 while (b != NULL) {
517 uint32_t pos = h->hash_func(h, h->key_func(b)) & h->mask;
518 next = b->next;
519 b->next = new_table[pos];
520 new_table[pos] = b;
521 b = next;
522 }
523 }
524
525 free(h->table);
526 h->table = new_table;
527 }
528
529 static void
530 hashtable_insert(struct hashtable *h, struct bucket *b)
531 {
532 uint32_t pos;
533 assert(h != NULL);
534 assert(b != NULL);
535 assert(b->next == NULL);
536
537 /* Rehash on 80% occupancy */
538 if ((h->count > h->size) ||
539 ((h->size - h->count) < h->size / 5))
540 hashtable_rehash(h, h->bits+1);
541
542 pos = h->hash_func(h, h->key_func(b)) & h->mask;
543 if (h->table[pos] == NULL)
544 h->table[pos] = b;
545 else {
546 /* Insert at top of chain. */
547 b->next = h->table[pos];
548 h->table[pos] = b;
549 }
550 h->count++;
551 h->stats.inserts++;
552 }
553
554 /* Return bucket matching key, or NULL if no such entry. */
555 static struct bucket *
556 hashtable_search(struct hashtable *h, const void *key)
557 {
558 uint32_t pos;
559 struct bucket *b;
560
561 h->stats.searches++;
562 pos = h->hash_func(h, key) & h->mask;
563 b = h->table[pos];
564 while (b != NULL) {
565 if (h->find_func(b, key))
566 return (b);
567 else
568 b = b->next;
569 }
570 return (NULL);
571 }
572
573 /* Search for a key. If it's not there, make and insert a bucket for it. */
574 static struct bucket *
575 hashtable_find_or_insert(struct hashtable *h, const void *key)
576 {
577 struct bucket *b = hashtable_search(h, key);
578
579 if (b == NULL) {
580 /* Not found, so insert after checking occupancy. */
581 /*assert(h->count <= h->count_max);*/
582 if (h->count >= h->count_max) hashtable_reduce(h);
583 b = h->make_func(key);
584 hashtable_insert(h, b);
585 }
586 return (b);
587 }
588
589 /*
590 * Frees the hashtable and the buckets. The contents are assumed to be
591 * "simple" -- i.e. no "destructor" action is required beyond simply freeing
592 * the bucket.
593 */
594 static void
595 hashtable_free(struct hashtable *h)
596 {
597 uint32_t i;
598
599 if (h == NULL)
600 return;
601 for (i=0; i<h->size; i++) {
602 struct bucket *tmp, *b = h->table[i];
603 while (b != NULL) {
604 tmp = b;
605 b = b->next;
606 h->free_func(tmp);
607 free(tmp);
608 }
609 }
610 free(h->table);
611 free(h);
612 }
613
614 /* ---------------------------------------------------------------------------
615 * Return existing host or insert a new one.
616 */
617 struct bucket *
618 host_get(const in_addr_t ip)
619 {
620 return (hashtable_find_or_insert(hosts_db, &ip));
621 }
622
623 /* ---------------------------------------------------------------------------
624 * Find host, returns NULL if not in DB.
625 */
626 struct bucket *
627 host_find(const in_addr_t ip)
628 {
629 return (hashtable_search(hosts_db, &ip));
630 }
631
632 /* ---------------------------------------------------------------------------
633 * Find host, returns NULL if not in DB.
634 */
635 static struct bucket *
636 host_search(const char *ipstr)
637 {
638 struct in_addr addr;
639
640 if (inet_aton(ipstr, &addr) != 1)
641 return (NULL); /* invalid addr */
642 return (hashtable_search(hosts_db, &(addr.s_addr)));
643 }
644
645 /* ---------------------------------------------------------------------------
646 * Reduce a hashtable to the top <keep> entries.
647 */
648 static void
649 hashtable_reduce(struct hashtable *ht)
650 {
651 uint32_t i, pos, rmd;
652 const struct bucket **table;
653 uint64_t cutoff;
654
655 assert(ht->count_keep < ht->count);
656
657 /* Fill table with pointers to buckets in hashtable. */
658 table = xmalloc(sizeof(*table) * ht->count);
659 for (pos=0, i=0; i<ht->size; i++) {
660 struct bucket *b = ht->table[i];
661 while (b != NULL) {
662 table[pos++] = b;
663 b = b->next;
664 }
665 }
666 assert(pos == ht->count);
667 qsort_buckets(table, ht->count, 0, ht->count_keep, TOTAL);
668 cutoff = table[ht->count_keep]->total;
669 free(table);
670
671 /* Remove all elements with total <= cutoff. */
672 rmd = 0;
673 for (i=0; i<ht->size; i++) {
674 struct bucket *last = NULL, *next, *b = ht->table[i];
675 while (b != NULL) {
676 next = b->next;
677 if (b->total <= cutoff) {
678 /* Remove this one. */
679 ht->free_func(b);
680 free(b);
681 if (last == NULL)
682 ht->table[i] = next;
683 else
684 last->next = next;
685 rmd++;
686 ht->count--;
687 } else {
688 last = b;
689 }
690 b = next;
691 }
692 }
693 verbosef("hashtable_reduce: removed %u buckets, left %u",
694 rmd, ht->count);
695 hashtable_rehash(ht, ht->bits); /* is this needed? */
696 }
697
698 /* ---------------------------------------------------------------------------
699 * Reset hosts_db to empty.
700 */
701 void
702 hosts_db_reset(void)
703 {
704 unsigned int i;
705
706 for (i=0; i<hosts_db->size; i++) {
707 struct bucket *next, *b = hosts_db->table[i];
708 while (b != NULL) {
709 next = b->next;
710 hosts_db->free_func(b);
711 free(b);
712 b = next;
713 }
714 hosts_db->table[i] = NULL;
715 }
716 verbosef("hosts_db reset to empty, freed %u hosts", hosts_db->count);
717 hosts_db->count = 0;
718 }
719
720 /* ---------------------------------------------------------------------------
721 * Deallocate hosts_db.
722 */
723 void hosts_db_free(void)
724 {
725 uint32_t i;
726
727 assert(hosts_db != NULL);
728 for (i=0; i<hosts_db->size; i++) {
729 struct bucket *tmp, *b = hosts_db->table[i];
730 while (b != NULL) {
731 tmp = b;
732 b = b->next;
733 hosts_db->free_func(tmp);
734 free(tmp);
735 }
736 }
737 free(hosts_db->table);
738 free(hosts_db);
739 hosts_db = NULL;
740 }
741
742 /* ---------------------------------------------------------------------------
743 * Find or create a port_tcp inside a host.
744 */
745 struct bucket *
746 host_get_port_tcp(struct bucket *host, const uint16_t port)
747 {
748 struct host *h = &host->u.host;
749 assert(h != NULL);
750 if (h->ports_tcp == NULL)
751 h->ports_tcp = hashtable_make(PORT_BITS, ports_max, ports_keep,
752 hash_func_short, free_func_simple, key_func_port_tcp,
753 find_func_port_tcp, make_func_port_tcp,
754 format_cols_port_tcp, format_row_port_tcp);
755 return (hashtable_find_or_insert(h->ports_tcp, &port));
756 }
757
758 /* ---------------------------------------------------------------------------
759 * Find or create a port_udp inside a host.
760 */
761 struct bucket *
762 host_get_port_udp(struct bucket *host, const uint16_t port)
763 {
764 struct host *h = &host->u.host;
765 assert(h != NULL);
766 if (h->ports_udp == NULL)
767 h->ports_udp = hashtable_make(PORT_BITS, ports_max, ports_keep,
768 hash_func_short, free_func_simple, key_func_port_udp,
769 find_func_port_udp, make_func_port_udp,
770 format_cols_port_udp, format_row_port_udp);
771 return (hashtable_find_or_insert(h->ports_udp, &port));
772 }
773
774 /* ---------------------------------------------------------------------------
775 * Find or create an ip_proto inside a host.
776 */
777 struct bucket *
778 host_get_ip_proto(struct bucket *host, const uint8_t proto)
779 {
780 struct host *h = &host->u.host;
781 static const unsigned int PROTOS_MAX = 512, PROTOS_KEEP = 256;
782 assert(h != NULL);
783 if (h->ip_protos == NULL)
784 h->ip_protos = hashtable_make(PROTO_BITS, PROTOS_MAX, PROTOS_KEEP,
785 hash_func_byte, free_func_simple, key_func_ip_proto,
786 find_func_ip_proto, make_func_ip_proto,
787 format_cols_ip_proto, format_row_ip_proto);
788 return (hashtable_find_or_insert(h->ip_protos, &proto));
789 }
790
791 static struct str *html_hosts_main(const char *qs);
792 static struct str *html_hosts_detail(const char *ip);
793
794 /* ---------------------------------------------------------------------------
795 * Web interface: delegate the /hosts/ space.
796 */
797 struct str *
798 html_hosts(const char *uri, const char *query)
799 {
800 int i, num_elems;
801 char **elem = split('/', uri, &num_elems);
802 struct str *buf = NULL;
803
804 assert(num_elems >= 1);
805 assert(strcmp(elem[0], "hosts") == 0);
806
807 if (num_elems == 1)
808 /* /hosts/ */
809 buf = html_hosts_main(query);
810 else if (num_elems == 2)
811 /* /hosts/<IP of host>/ */
812 buf = html_hosts_detail(elem[1]);
813
814 for (i=0; i<num_elems; i++)
815 free(elem[i]);
816 free(elem);
817 return (buf); /* FIXME: a NULL here becomes 404 Not Found, we might want
818 other codes to be possible */
819 }
820
821 /* ---------------------------------------------------------------------------
822 * Format hashtable into HTML.
823 */
824 static void
825 format_table(struct str *buf, struct hashtable *ht, int start,
826 const enum sort_dir sort, const int full)
827 {
828 const struct bucket **table;
829 uint32_t i, pos, end;
830 int alt = 0;
831
832 if ((ht == NULL) || (ht->count == 0)) {
833 str_append(buf, "<p>The table is empty.</p>\n");
834 return;
835 }
836
837 /* Fill table with pointers to buckets in hashtable. */
838 table = xmalloc(sizeof(*table) * ht->count);
839 for (pos=0, i=0; i<ht->size; i++) {
840 struct bucket *b = ht->table[i];
841 while (b != NULL) {
842 table[pos++] = b;
843 b = b->next;
844 }
845 }
846 assert(pos == ht->count);
847
848 if (full) {
849 /* full report overrides start and end */
850 start = 0;
851 end = ht->count;
852 } else
853 end = min(ht->count, (uint32_t)start+MAX_ENTRIES);
854
855 str_appendf(buf, "(%u-%u of %u)<br/>\n", start+1, end, ht->count);
856 qsort_buckets(table, ht->count, start, end, sort);
857 ht->format_cols_func(buf);
858
859 for (i=start; i<end; i++) {
860 ht->format_row_func(buf, table[i], alt ? "alt1" : "alt2");
861 alt = !alt; /* alternate class for table rows */
862 }
863 free(table);
864 str_append(buf, "</table>\n");
865 }
866
867 /* ---------------------------------------------------------------------------
868 * Web interface: sorted table of hosts.
869 */
870 static struct str *
871 html_hosts_main(const char *qs)
872 {
873 struct str *buf = str_make();
874 char *qs_start, *qs_sort, *qs_full, *ep;
875 const char *sortstr;
876 int start, full = 0;
877 enum sort_dir sort;
878
879 /* parse query string */
880 qs_start = qs_get(qs, "start");
881 qs_sort = qs_get(qs, "sort");
882 qs_full = qs_get(qs, "full");
883 if (qs_full != NULL) {
884 full = 1;
885 free(qs_full);
886 }
887
888 /* validate sort */
889 if (qs_sort == NULL) sort = TOTAL;
890 else if (strcmp(qs_sort, "total") == 0) sort = TOTAL;
891 else if (strcmp(qs_sort, "in") == 0) sort = IN;
892 else if (strcmp(qs_sort, "out") == 0) sort = OUT;
893 else {
894 str_append(buf, "Error: invalid value for \"sort\".\n");
895 goto done;
896 }
897
898 /* parse start */
899 if (qs_start == NULL)
900 start = 0;
901 else {
902 start = (int)strtoul(qs_start, &ep, 10);
903 if (*ep != '\0') {
904 str_append(buf, "Error: \"start\" is not a number.\n");
905 goto done;
906 }
907 if ((errno == ERANGE) ||
908 (start < 0) || (start >= (int)hosts_db->count)) {
909 str_append(buf, "Error: \"start\" is out of bounds.\n");
910 goto done;
911 }
912 }
913
914 #define PREV "&lt;&lt;&lt; prev page"
915 #define NEXT "next page &gt;&gt;&gt;"
916 #define FULL "full table"
917
918 str_append(buf, html_header_1);
919 str_appendf(buf, " <title>darkstat3: Hosts (%s)</title>\n", interface);
920 str_append(buf, html_header_2);
921 str_appendf(buf, "<h2 class=\"pageheader\">Hosts (%s)</h2>\n", interface);
922 format_table(buf, hosts_db, start, sort, full);
923
924 /* <prev | full | stats | next> */
925 sortstr = qs_sort;
926 if (sortstr == NULL) sortstr = "total";
927 if (start > 0) {
928 int prev = max(start - MAX_ENTRIES, 0);
929 str_appendf(buf, "<a href=\"?start=%d&sort=%s\">" PREV "</a>",
930 prev, sortstr);
931 } else
932 str_append(buf, PREV);
933
934 if (full)
935 str_append(buf, " | " FULL);
936 else
937 str_appendf(buf, " | <a href=\"?full=yes&sort=%s\">" FULL "</a>",
938 sortstr);
939
940 if (start+MAX_ENTRIES < (int)hosts_db->count)
941 str_appendf(buf, " | <a href=\"?start=%d&sort=%s\">" NEXT "</a>",
942 start+MAX_ENTRIES, sortstr);
943 else
944 str_append(buf, " | " NEXT);
945
946 str_append(buf, "<br/>\n");
947 str_append(buf, html_footer);
948 done:
949 if (qs_start != NULL) free(qs_start);
950 if (qs_sort != NULL) free(qs_sort);
951 return buf;
952 #undef PREV
953 #undef NEXT
954 #undef FULL
955 }
956
957 /* ---------------------------------------------------------------------------
958 * Web interface: detailed view of a single host.
959 */
960 static struct str *
961 html_hosts_detail(const char *ip)
962 {
963 struct bucket *h;
964 struct str *buf, *ls_len;
965 char ls_when[100];
966 time_t ls;
967
968 h = host_search(ip);
969 if (h == NULL)
970 return (NULL); /* no such host */
971
972 /* Overview. */
973 buf = str_make();
974 str_append(buf, html_header_1);
975 str_appendf(buf, " <title>%s</title>\n", ip);
976 str_append(buf, html_header_2);
977 str_appendf(buf,
978 "<h2>%s</h2>\n"
979 "<p>\n"
980 "<b>Hostname:</b> %s<br/>\n",
981 ip,
982 (h->u.host.dns == NULL)?"(resolving...)":h->u.host.dns);
983
984 /* Resolve host "on demand" */
985 if (h->u.host.dns == NULL)
986 dns_queue(h->u.host.ip);
987
988 if (show_mac_addrs)
989 str_appendf(buf,
990 "<b>MAC Address:</b> "
991 "<tt>%x:%x:%x:%x:%x:%x</tt><br/>\n",
992 h->u.host.mac_addr[0],
993 h->u.host.mac_addr[1],
994 h->u.host.mac_addr[2],
995 h->u.host.mac_addr[3],
996 h->u.host.mac_addr[4],
997 h->u.host.mac_addr[5]);
998
999 str_append(buf,
1000 "</p>\n"
1001 "<p>\n"
1002 "<b>Last seen:</b> ");
1003
1004 ls = h->u.host.last_seen;
1005 if (strftime(ls_when, sizeof(ls_when),
1006 "%Y-%m-%d %H:%M:%S %Z%z", localtime(&ls)) != 0)
1007 str_append(buf, ls_when);
1008
1009 if (h->u.host.last_seen <= now) {
1010 ls_len = length_of_time(now - h->u.host.last_seen);
1011 str_append(buf, " (");
1012 str_appendstr(buf, ls_len);
1013 str_free(ls_len);
1014 str_append(buf, " ago)");
1015 } else {
1016 str_append(buf, " (in the future, possible clock problem)");
1017 }
1018
1019 str_appendf(buf,
1020 "</p>\n"
1021 "<p>\n"
1022 " <b>In:</b> %'qu<br/>\n"
1023 " <b>Out:</b> %'qu<br/>\n"
1024 " <b>Total:</b> %'qu<br/>\n"
1025 "</p>\n",
1026 h->in, h->out, h->total);
1027
1028 str_append(buf, "<h3>TCP ports</h3>\n");
1029 format_table(buf, h->u.host.ports_tcp, 0,TOTAL,0);
1030
1031 str_append(buf, "<h3>UDP ports</h3>\n");
1032 format_table(buf, h->u.host.ports_udp, 0,TOTAL,0);
1033
1034 str_append(buf, "<h3>IP protocols</h3>\n");
1035 format_table(buf, h->u.host.ip_protos, 0,TOTAL,0);
1036
1037 str_append(buf, html_footer);
1038 return (buf);
1039 }
1040
1041 /* ---------------------------------------------------------------------------
1042 * Database import and export code:
1043 * Initially written and contributed by Ben Stewart.
1044 * copyright (c) 2007 Ben Stewart, Emil Mikulic.
1045 */
1046 static int hosts_db_export_ip(const struct hashtable *h, const int fd);
1047 static int hosts_db_export_tcp(const struct hashtable *h, const int fd);
1048 static int hosts_db_export_udp(const struct hashtable *h, const int fd);
1049
1050 static const char
1051 export_proto_ip = 'P',
1052 export_proto_tcp = 'T',
1053 export_proto_udp = 'U';
1054
1055 static const unsigned char
1056 export_tag_host_ver1[] = {'H', 'S', 'T', 0x01},
1057 export_tag_host_ver2[] = {'H', 'S', 'T', 0x02};
1058
1059 /* ---------------------------------------------------------------------------
1060 * Load a host's ip_proto table from a file.
1061 * Returns 0 on failure, 1 on success.
1062 */
1063 static int
1064 hosts_db_import_ip(const int fd, struct bucket *host)
1065 {
1066 uint8_t count, i;
1067
1068 if (!expect8(fd, export_proto_ip)) return 0;
1069 if (!read8(fd, &count)) return 0;
1070
1071 for (i=0; i<count; i++) {
1072 struct bucket *b;
1073 uint8_t proto;
1074 uint64_t in, out;
1075
1076 if (!read8(fd, &proto)) return 0;
1077 if (!read64(fd, &in)) return 0;
1078 if (!read64(fd, &out)) return 0;
1079
1080 /* Store data */
1081 b = host_get_ip_proto(host, proto);
1082 b->in = in;
1083 b->out = out;
1084 b->total = in + out;
1085 assert(b->u.ip_proto.proto == proto); /* should be done by make fn */
1086 }
1087 return 1;
1088 }
1089
1090 /* ---------------------------------------------------------------------------
1091 * Load a host's port_tcp table from a file.
1092 * Returns 0 on failure, 1 on success.
1093 */
1094 static int
1095 hosts_db_import_tcp(const int fd, struct bucket *host)
1096 {
1097 uint16_t count, i;
1098
1099 if (!expect8(fd, export_proto_tcp)) return 0;
1100 if (!read16(fd, &count)) return 0;
1101
1102 for (i=0; i<count; i++) {
1103 struct bucket *b;
1104 uint16_t port;
1105 uint64_t in, out, syn;
1106
1107 if (!read16(fd, &port)) return 0;
1108 if (!read64(fd, &syn)) return 0;
1109 if (!read64(fd, &in)) return 0;
1110 if (!read64(fd, &out)) return 0;
1111
1112 /* Store data */
1113 b = host_get_port_tcp(host, port);
1114 b->in = in;
1115 b->out = out;
1116 b->total = in + out;
1117 assert(b->u.port_tcp.port == port); /* done by make_func_port_tcp */
1118 b->u.port_tcp.syn = syn;
1119 }
1120 return 1;
1121 }
1122
1123 /* ---------------------------------------------------------------------------
1124 * Load a host's port_tcp table from a file.
1125 * Returns 0 on failure, 1 on success.
1126 */
1127 static int
1128 hosts_db_import_udp(const int fd, struct bucket *host)
1129 {
1130 uint16_t count, i;
1131
1132 if (!expect8(fd, export_proto_udp)) return 0;
1133 if (!read16(fd, &count)) return 0;
1134
1135 for (i=0; i<count; i++) {
1136 struct bucket *b;
1137 uint16_t port;
1138 uint64_t in, out;
1139
1140 if (!read16(fd, &port)) return 0;
1141 if (!read64(fd, &in)) return 0;
1142 if (!read64(fd, &out)) return 0;
1143
1144 /* Store data */
1145 b = host_get_port_udp(host, port);
1146 b->in = in;
1147 b->out = out;
1148 b->total = in + out;
1149 assert(b->u.port_udp.port == port); /* done by make_func */
1150 }
1151 return 1;
1152 }
1153
1154 /* ---------------------------------------------------------------------------
1155 * Load all hosts from a file.
1156 * Returns 0 on failure, 1 on success.
1157 */
1158 static int
1159 hosts_db_import_host(const int fd)
1160 {
1161 struct bucket *host;
1162 in_addr_t addr;
1163 uint8_t hostname_len;
1164 uint64_t in, out;
1165 unsigned int pos = xtell(fd);
1166 char hdr[4];
1167 int ver = 0;
1168
1169 if (!readn(fd, hdr, sizeof(hdr))) return 0;
1170 if (memcmp(hdr, export_tag_host_ver2, sizeof(hdr)) == 0)
1171 ver = 2;
1172 else if (memcmp(hdr, export_tag_host_ver1, sizeof(hdr)) == 0)
1173 ver = 1;
1174 else {
1175 warnx("bad host header: %02x%02x%02x%02x",
1176 hdr[0], hdr[1], hdr[2], hdr[3]);
1177 return 0;
1178 }
1179
1180 if (!readaddr(fd, &addr)) return 0;
1181 verbosef("at file pos %u, importing host %s", pos, ip_to_str(addr));
1182 host = host_get(addr);
1183 assert(host->u.host.ip == addr); /* make fn? */
1184
1185 if (ver > 1) {
1186 uint64_t t;
1187 if (!read64(fd, &t)) return 0;
1188 host->u.host.last_seen = (time_t)t;
1189 }
1190
1191 assert(sizeof(host->u.host.mac_addr) == 6);
1192 if (!readn(fd, host->u.host.mac_addr, sizeof(host->u.host.mac_addr)))
1193 return 0;
1194
1195 /* HOSTNAME */
1196 assert(host->u.host.dns == NULL); /* make fn? */
1197 if (!read8(fd, &hostname_len)) return 0;
1198 if (hostname_len > 0) {
1199 host->u.host.dns = xmalloc(hostname_len + 1);
1200 host->u.host.dns[0] = '\0';
1201
1202 /* At this point, the hostname is attached to a host which is in our
1203 * hosts_db, so if we bail out due to an import error, this pointer
1204 * isn't lost and leaked, it can be cleaned up in hosts_db_{free,reset}
1205 */
1206
1207 if (!readn(fd, host->u.host.dns, hostname_len)) return 0;
1208 host->u.host.dns[hostname_len] = '\0';
1209 }
1210
1211 if (!read64(fd, &in)) return 0;
1212 if (!read64(fd, &out)) return 0;
1213
1214 host->in = in;
1215 host->out = out;
1216 host->total = in + out;
1217
1218 /* Host's port and proto subtables: */
1219 if (!hosts_db_import_ip(fd, host)) return 0;
1220 if (!hosts_db_import_tcp(fd, host)) return 0;
1221 if (!hosts_db_import_udp(fd, host)) return 0;
1222 return 1;
1223 }
1224
1225 /* ---------------------------------------------------------------------------
1226 * Database Import: Grab hosts_db from a file provided by the caller.
1227 *
1228 * This function will retrieve the data sans the header. We expect the caller
1229 * to have validated the header of the hosts_db segment, and left the file
1230 * sitting at the start of the data.
1231 */
1232 int hosts_db_import(const int fd)
1233 {
1234 uint32_t host_count, i;
1235
1236 if (!read32(fd, &host_count)) return 0;
1237
1238 for (i=0; i<host_count; i++)
1239 if (!hosts_db_import_host(fd)) return 0;
1240
1241 return 1;
1242 }
1243
1244 /* ---------------------------------------------------------------------------
1245 * Database Export: Dump hosts_db into a file provided by the caller.
1246 * The caller is responsible for writing out export_tag_hosts_ver1 first.
1247 */
1248 int hosts_db_export(const int fd)
1249 {
1250 uint32_t i;
1251 struct bucket *b;
1252
1253 if (!write32(fd, hosts_db->count)) return 0;
1254
1255 for (i = 0; i<hosts_db->size; i++)
1256 for (b = hosts_db->table[i]; b != NULL; b = b->next) {
1257 /* For each host: */
1258 if (!writen(fd, export_tag_host_ver2, sizeof(export_tag_host_ver2)))
1259 return 0;
1260
1261 if (!writeaddr(fd, b->u.host.ip)) return 0;
1262
1263 if (!write64(fd, (uint64_t)(b->u.host.last_seen))) return 0;
1264
1265 assert(sizeof(b->u.host.mac_addr) == 6);
1266 if (!writen(fd, b->u.host.mac_addr, sizeof(b->u.host.mac_addr)))
1267 return 0;
1268
1269 /* HOSTNAME */
1270 if (b->u.host.dns == NULL) {
1271 if (!write8(fd, 0)) return 0;
1272 } else {
1273 int dnslen = strlen(b->u.host.dns);
1274
1275 if (dnslen > 255) {
1276 warnx("found a very long hostname: \"%s\"\n"
1277 "wasn't expecting one longer than 255 chars (this one is %d)",
1278 b->u.host.dns, dnslen);
1279 dnslen = 255;
1280 }
1281
1282 if (!write8(fd, (uint8_t)dnslen)) return 0;
1283 if (!writen(fd, b->u.host.dns, dnslen)) return 0;
1284 }
1285
1286 if (!write64(fd, b->in)) return 0;
1287 if (!write64(fd, b->out)) return 0;
1288
1289 if (!hosts_db_export_ip(b->u.host.ip_protos, fd)) return 0;
1290 if (!hosts_db_export_tcp(b->u.host.ports_tcp, fd)) return 0;
1291 if (!hosts_db_export_udp(b->u.host.ports_udp, fd)) return 0;
1292 }
1293 return 1;
1294 }
1295
1296 /* ---------------------------------------------------------------------------
1297 * Dump the ip_proto table of a host.
1298 */
1299 static int
1300 hosts_db_export_ip(const struct hashtable *h, const int fd)
1301 {
1302 uint32_t i, written = 0;
1303 struct bucket *b;
1304
1305 /* IP DATA */
1306 if (!write8(fd, export_proto_ip)) return 0;
1307
1308 /* If no data, write a IP Proto count of 0 and we're done. */
1309 if (h == NULL) {
1310 if (!write8(fd, 0)) return 0;
1311 return 1;
1312 }
1313
1314 assert(h->count < 256);
1315 if (!write8(fd, (uint8_t)h->count)) return 0;
1316
1317 for (i = 0; i<h->size; i++)
1318 for (b = h->table[i]; b != NULL; b = b->next) {
1319 /* For each ip_proto bucket: */
1320
1321 if (!write8(fd, b->u.ip_proto.proto)) return 0;
1322 if (!write64(fd, b->in)) return 0;
1323 if (!write64(fd, b->out)) return 0;
1324 written++;
1325 }
1326 assert(written == h->count);
1327 return 1;
1328 }
1329
1330 /* ---------------------------------------------------------------------------
1331 * Dump the port_tcp table of a host.
1332 */
1333 static int
1334 hosts_db_export_tcp(const struct hashtable *h, const int fd)
1335 {
1336 struct bucket *b;
1337 uint32_t i, written = 0;
1338
1339 /* TCP DATA */
1340 if (!write8(fd, export_proto_tcp)) return 0;
1341
1342 /* If no data, write a count of 0 and we're done. */
1343 if (h == NULL) {
1344 if (!write16(fd, 0)) return 0;
1345 return 1;
1346 }
1347
1348 assert(h->count < 65536);
1349 if (!write16(fd, (uint16_t)h->count)) return 0;
1350
1351 for (i = 0; i<h->size; i++)
1352 for (b = h->table[i]; b != NULL; b = b->next) {
1353 if (!write16(fd, b->u.port_tcp.port)) return 0;
1354 if (!write64(fd, b->u.port_tcp.syn)) return 0;
1355 if (!write64(fd, b->in)) return 0;
1356 if (!write64(fd, b->out)) return 0;
1357 written++;
1358 }
1359 assert(written == h->count);
1360 return 1;
1361 }
1362
1363 /* ---------------------------------------------------------------------------
1364 * Dump the port_udp table of a host.
1365 */
1366 static int
1367 hosts_db_export_udp(const struct hashtable *h, const int fd)
1368 {
1369 struct bucket *b;
1370 uint32_t i, written = 0;
1371
1372 /* UDP DATA */
1373 if (!write8(fd, export_proto_udp)) return 0;
1374
1375 /* If no data, write a count of 0 and we're done. */
1376 if (h == NULL) {
1377 if (!write16(fd, 0)) return 0;
1378 return 1;
1379 }
1380
1381 assert(h->count < 65536);
1382 if (!write16(fd, (uint16_t)h->count)) return 0;
1383
1384 for (i = 0; i<h->size; i++)
1385 for (b = h->table[i]; b != NULL; b = b->next) {
1386 if (!write16(fd, b->u.port_udp.port)) return 0;
1387 if (!write64(fd, b->in)) return 0;
1388 if (!write64(fd, b->out)) return 0;
1389 written++;
1390 }
1391 assert(written == h->count);
1392 return 1;
1393 }
1394
1395 /* vim:set ts=3 sw=3 tw=78 expandtab: */