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