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