2 * copyright (c) 2001-2011 Emil Mikulic.
4 * hosts_db.c: database of hosts, ports, protocols.
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
23 #include <arpa/inet.h> /* inet_aton() */
24 #include <netdb.h> /* struct addrinfo */
29 #include <string.h> /* memset(), strcmp() */
32 int hosts_db_show_macs
= 0;
34 /* FIXME: specify somewhere more sane/tunable */
35 #define MAX_ENTRIES 30 /* in an HTML table rendered from a hashtable */
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
*,
47 uint8_t bits
; /* size of hashtable in bits */
49 uint32_t count
, count_max
, count_keep
; /* items in table */
50 uint32_t coeff
; /* coefficient for Fibonacci hashing */
51 struct bucket
**table
;
54 uint64_t inserts
, searches
, deletions
, rehashes
;
57 hash_func_t
*hash_func
;
58 /* returns hash value of given key (passed as void*) */
60 free_func_t
*free_func
;
61 /* free of bucket payload */
64 /* returns pointer to key of bucket (to pass to hash_func) */
66 find_func_t
*find_func
;
67 /* returns true if given bucket matches key (passed as void*) */
69 make_func_t
*make_func
;
70 /* returns bucket containing new record with key (passed as void*) */
72 format_cols_func_t
*format_cols_func
;
73 /* append table columns to str */
75 format_row_func_t
*format_row_func
;
76 /* format record and append to str */
79 static void hashtable_reduce(struct hashtable
*ht
);
80 static void hashtable_free(struct hashtable
*h
);
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 */
86 /* We only use one hosts_db hashtable and this is it. */
87 static struct hashtable
*hosts_db
= NULL
;
89 /* phi^-1 (reciprocal of golden ratio) = (sqrt(5) - 1) / 2 */
90 static const double phi_1
=
91 0.61803398874989490252573887119069695472717285156250;
93 /* Co-prime of u, using phi^-1 */
94 inline static uint32_t
95 coprime(const uint32_t u
)
97 return ( (uint32_t)( (double)(u
) * phi_1
) | 1U );
101 * This is the "recommended" IPv4 hash function, as seen in FreeBSD's
102 * src/sys/netinet/tcp_hostcache.c 1.1
104 inline static uint32_t
105 ipv4_hash(const struct addr
*const a
)
107 uint32_t ip
= a
->ip
.v4
;
108 return ( (ip
) ^ ((ip
) >> 7) ^ ((ip
) >> 17) );
114 * http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/netinet/in.h#130
116 # define s6_addr32 _S6_un._S6_u32
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
125 * This is the IPv6 hash function used by FreeBSD in the same file as above,
128 inline static uint32_t
129 ipv6_hash(const struct addr
*const a
)
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] );
136 /* ---------------------------------------------------------------------------
137 * hash_func collection
140 hash_func_host(const struct hashtable
*h _unused_
, const void *key
)
142 const struct addr
*a
= key
;
143 if (a
->family
== IPv4
)
144 return (ipv4_hash(a
));
146 assert(a
->family
== IPv6
);
147 return (ipv6_hash(a
));
151 #define CASTKEY(type) (*((const type *)key))
154 hash_func_short(const struct hashtable
*h
, const void *key
)
156 return (CASTKEY(uint16_t) * h
->coeff
);
160 hash_func_byte(const struct hashtable
*h
, const void *key
)
162 return (CASTKEY(uint8_t) * h
->coeff
);
165 /* ---------------------------------------------------------------------------
166 * key_func collection
170 key_func_host(const struct bucket
*b
)
172 return &(b
->u
.host
.addr
);
176 key_func_port_tcp(const struct bucket
*b
)
178 return &(b
->u
.port_tcp
.port
);
182 key_func_port_udp(const struct bucket
*b
)
184 return &(b
->u
.port_udp
.port
);
188 key_func_ip_proto(const struct bucket
*b
)
190 return &(b
->u
.ip_proto
.proto
);
193 /* ---------------------------------------------------------------------------
194 * find_func collection
198 find_func_host(const struct bucket
*b
, const void *key
)
200 return (addr_equal(key
, &(b
->u
.host
.addr
)));
204 find_func_port_tcp(const struct bucket
*b
, const void *key
)
206 return (b
->u
.port_tcp
.port
== CASTKEY(uint16_t));
210 find_func_port_udp(const struct bucket
*b
, const void *key
)
212 return (b
->u
.port_udp
.port
== CASTKEY(uint16_t));
216 find_func_ip_proto(const struct bucket
*b
, const void *key
)
218 return (b
->u
.ip_proto
.proto
== CASTKEY(uint8_t));
221 /* ---------------------------------------------------------------------------
222 * make_func collection
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;
234 static struct bucket
*
235 make_func_host(const void *key
)
237 MAKE_BUCKET(b
, h
, host
);
238 h
->addr
= CASTKEY(struct addr
);
241 memset(&h
->mac_addr
, 0, sizeof(h
->mac_addr
));
249 free_func_host(struct bucket
*b
)
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
);
258 static struct bucket
*
259 make_func_port_tcp(const void *key
)
261 MAKE_BUCKET(b
, p
, port_tcp
);
262 p
->port
= CASTKEY(uint16_t);
267 static struct bucket
*
268 make_func_port_udp(const void *key
)
270 MAKE_BUCKET(b
, p
, port_udp
);
271 p
->port
= CASTKEY(uint16_t);
275 static struct bucket
*
276 make_func_ip_proto(const void *key
)
278 MAKE_BUCKET(b
, p
, ip_proto
);
279 p
->proto
= CASTKEY(uint8_t);
284 free_func_simple(struct bucket
*b _unused_
)
289 /* ---------------------------------------------------------------------------
290 * format_func collection (ordered by struct)
294 format_cols_host(struct str
*buf
)
296 /* FIXME: don't clobber parts of the query string
297 * specifically "full" and "start"
298 * when setting sort direction
304 " <th>Hostname</th>\n");
305 if (hosts_db_show_macs
) str_append(buf
,
306 " <th>MAC Address</th>\n");
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");
318 format_row_host(struct str
*buf
, const struct bucket
*b
,
319 const char *css_class
)
321 const char *ip
= addr_to_str(&(b
->u
.host
.addr
));
324 "<tr class=\"%s\">\n"
325 " <td><a href=\"./%s/\">%s</a></td>\n"
329 (b
->u
.host
.dns
== NULL
) ? "" : b
->u
.host
.dns
);
331 if (hosts_db_show_macs
)
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]);
342 " <td class=\"num\">%'qu</td>\n"
343 " <td class=\"num\">%'qu</td>\n"
344 " <td class=\"num\">%'qu</td>\n",
345 b
->in
, b
->out
, b
->total
);
347 if (opt_want_lastseen
) {
348 time_t last_t
= b
->u
.host
.lastseen
;
349 struct str
*last_str
= NULL
;
351 if ((now
>= last_t
) && (last_t
> 0))
352 last_str
= length_of_time(now
- last_t
);
355 " <td class=\"num\">");
356 if (last_str
== NULL
) {
358 str_append(buf
, "(never)");
360 str_append(buf
, "(clock error)");
362 str_appendstr(buf
, last_str
);
372 /* Only resolve hosts "on demand" */
373 if (b
->u
.host
.dns
== NULL
)
374 dns_queue(&(b
->u
.host
.addr
));
378 format_cols_port_tcp(struct str
*buf
)
384 " <th>Service</td>\n"
394 format_row_port_tcp(struct str
*buf
, const struct bucket
*b
,
395 const char *css_class
)
397 const struct port_tcp
*p
= &(b
->u
.port_tcp
);
400 "<tr class=\"%s\">\n"
401 " <td class=\"num\">%u</td>\n"
403 " <td class=\"num\">%'qu</td>\n"
404 " <td class=\"num\">%'qu</td>\n"
405 " <td class=\"num\">%'qu</td>\n"
406 " <td class=\"num\">%'qu</td>\n"
409 p
->port
, getservtcp(p
->port
), b
->in
, b
->out
, b
->total
, p
->syn
414 format_cols_port_udp(struct str
*buf
)
420 " <th>Service</td>\n"
429 format_row_port_udp(struct str
*buf
, const struct bucket
*b
,
430 const char *css_class
)
432 const struct port_udp
*p
= &(b
->u
.port_udp
);
435 "<tr class=\"%s\">\n"
436 " <td class=\"num\">%u</td>\n"
438 " <td class=\"num\">%'qu</td>\n"
439 " <td class=\"num\">%'qu</td>\n"
440 " <td class=\"num\">%'qu</td>\n"
443 p
->port
, getservudp(p
->port
), b
->in
, b
->out
, b
->total
448 format_cols_ip_proto(struct str
*buf
)
454 " <th>Protocol</td>\n"
463 format_row_ip_proto(struct str
*buf
, const struct bucket
*b
,
464 const char *css_class
)
466 const struct ip_proto
*p
= &(b
->u
.ip_proto
);
469 "<tr class=\"%s\">\n"
470 " <td class=\"num\">%u</td>\n"
472 " <td class=\"num\">%'qu</td>\n"
473 " <td class=\"num\">%'qu</td>\n"
474 " <td class=\"num\">%'qu</td>\n"
477 p
->proto
, getproto(p
->proto
),
478 b
->in
, b
->out
, b
->total
482 /* ---------------------------------------------------------------------------
483 * Initialise a hashtable.
485 static struct hashtable
*
486 hashtable_make(const uint8_t bits
,
487 const unsigned int count_max
,
488 const unsigned int count_keep
,
489 hash_func_t
*hash_func
,
490 free_func_t
*free_func
,
491 key_func_t
*key_func
,
492 find_func_t
*find_func
,
493 make_func_t
*make_func
,
494 format_cols_func_t
*format_cols_func
,
495 format_row_func_t
*format_row_func
)
497 struct hashtable
*hash
;
500 hash
= xmalloc(sizeof(*hash
));
502 hash
->count_max
= count_max
;
503 hash
->count_keep
= count_keep
;
504 hash
->size
= 1U << bits
;
505 hash
->mask
= hash
->size
- 1;
506 hash
->coeff
= coprime(hash
->size
);
507 hash
->hash_func
= hash_func
;
508 hash
->free_func
= free_func
;
509 hash
->key_func
= key_func
;
510 hash
->find_func
= find_func
;
511 hash
->make_func
= make_func
;
512 hash
->format_cols_func
= format_cols_func
;
513 hash
->format_row_func
= format_row_func
;
515 hash
->table
= xcalloc(hash
->size
, sizeof(*hash
->table
));
516 memset(&(hash
->stats
), 0, sizeof(hash
->stats
));
520 /* ---------------------------------------------------------------------------
521 * Initialise global hosts_db.
526 assert(hosts_db
== NULL
);
527 hosts_db
= hashtable_make(HOST_BITS
, opt_hosts_max
, opt_hosts_keep
,
528 hash_func_host
, free_func_host
, key_func_host
, find_func_host
,
529 make_func_host
, format_cols_host
, format_row_host
);
533 hashtable_rehash(struct hashtable
*h
, const uint8_t bits
)
535 struct bucket
**old_table
, **new_table
;
536 uint32_t i
, old_size
;
542 old_table
= h
->table
;
545 h
->size
= 1U << bits
;
546 h
->mask
= h
->size
- 1;
547 h
->coeff
= coprime(h
->size
);
548 new_table
= xcalloc(h
->size
, sizeof(*new_table
));
550 for (i
=0; i
<old_size
; i
++) {
551 struct bucket
*next
, *b
= old_table
[i
];
553 uint32_t pos
= h
->hash_func(h
, h
->key_func(b
)) & h
->mask
;
555 b
->next
= new_table
[pos
];
562 h
->table
= new_table
;
566 hashtable_insert(struct hashtable
*h
, struct bucket
*b
)
571 assert(b
->next
== NULL
);
573 /* Rehash on 80% occupancy */
574 if ((h
->count
> h
->size
) ||
575 ((h
->size
- h
->count
) < h
->size
/ 5))
576 hashtable_rehash(h
, h
->bits
+1);
578 pos
= h
->hash_func(h
, h
->key_func(b
)) & h
->mask
;
579 if (h
->table
[pos
] == NULL
)
582 /* Insert at top of chain. */
583 b
->next
= h
->table
[pos
];
590 /* Return bucket matching key, or NULL if no such entry. */
591 static struct bucket
*
592 hashtable_search(struct hashtable
*h
, const void *key
)
598 pos
= h
->hash_func(h
, key
) & h
->mask
;
601 if (h
->find_func(b
, key
))
609 typedef enum { NO_REDUCE
= 0, ALLOW_REDUCE
= 1 } reduce_bool
;
610 /* Search for a key. If it's not there, make and insert a bucket for it. */
611 static struct bucket
*
612 hashtable_find_or_insert(struct hashtable
*h
, const void *key
,
613 const reduce_bool allow_reduce
)
615 struct bucket
*b
= hashtable_search(h
, key
);
618 /* Not found, so insert after checking occupancy. */
619 if (allow_reduce
&& (h
->count
>= h
->count_max
))
621 b
= h
->make_func(key
);
622 hashtable_insert(h
, b
);
628 * Frees the hashtable and the buckets. The contents are assumed to be
629 * "simple" -- i.e. no "destructor" action is required beyond simply freeing
633 hashtable_free(struct hashtable
*h
)
639 for (i
=0; i
<h
->size
; i
++) {
640 struct bucket
*tmp
, *b
= h
->table
[i
];
652 /* ---------------------------------------------------------------------------
653 * Return existing host or insert a new one.
656 host_get(const struct addr
*const a
)
658 return (hashtable_find_or_insert(hosts_db
, a
, NO_REDUCE
));
661 /* ---------------------------------------------------------------------------
662 * Find host, returns NULL if not in DB.
665 host_find(const struct addr
*const a
)
667 return (hashtable_search(hosts_db
, a
));
670 /* ---------------------------------------------------------------------------
671 * Find host, returns NULL if not in DB.
673 static struct bucket
*
674 host_search(const char *ipstr
)
677 struct addrinfo hints
, *ai
;
679 memset(&hints
, 0, sizeof(hints
));
680 hints
.ai_family
= AF_UNSPEC
;
681 hints
.ai_flags
= AI_NUMERICHOST
;
683 if (getaddrinfo(ipstr
, NULL
, &hints
, &ai
))
684 return (NULL
); /* invalid addr */
686 if (ai
->ai_family
== AF_INET
) {
688 a
.ip
.v4
= ((const struct sockaddr_in
*)ai
->ai_addr
)->sin_addr
.s_addr
;
690 else if (ai
->ai_family
== AF_INET6
) {
693 ((struct sockaddr_in6
*)ai
->ai_addr
)->sin6_addr
.s6_addr
,
697 return (NULL
); /* unknown family */
701 verbosef("search(%s) turned into %s", ipstr
, addr_to_str(&a
));
702 return (hashtable_search(hosts_db
, &a
));
705 /* ---------------------------------------------------------------------------
706 * Reduce a hashtable to the top <keep> entries.
709 hashtable_reduce(struct hashtable
*ht
)
711 uint32_t i
, pos
, rmd
;
712 const struct bucket
**table
;
715 assert(ht
->count_keep
< ht
->count
);
717 /* Fill table with pointers to buckets in hashtable. */
718 table
= xcalloc(ht
->count
, sizeof(*table
));
719 for (pos
=0, i
=0; i
<ht
->size
; i
++) {
720 struct bucket
*b
= ht
->table
[i
];
726 assert(pos
== ht
->count
);
727 qsort_buckets(table
, ht
->count
, 0, ht
->count_keep
, TOTAL
);
728 cutoff
= table
[ht
->count_keep
]->total
;
731 /* Remove all elements with total <= cutoff. */
733 for (i
=0; i
<ht
->size
; i
++) {
734 struct bucket
*last
= NULL
, *next
, *b
= ht
->table
[i
];
737 if (b
->total
<= cutoff
) {
738 /* Remove this one. */
753 verbosef("hashtable_reduce: removed %u buckets, left %u",
755 hashtable_rehash(ht
, ht
->bits
); /* is this needed? */
758 /* Reduce hosts_db if needed. */
759 void hosts_db_reduce(void)
761 if (hosts_db
->count
>= hosts_db
->count_max
)
762 hashtable_reduce(hosts_db
);
765 /* ---------------------------------------------------------------------------
766 * Reset hosts_db to empty.
773 for (i
=0; i
<hosts_db
->size
; i
++) {
774 struct bucket
*next
, *b
= hosts_db
->table
[i
];
777 hosts_db
->free_func(b
);
781 hosts_db
->table
[i
] = NULL
;
783 verbosef("hosts_db reset to empty, freed %u hosts", hosts_db
->count
);
787 /* ---------------------------------------------------------------------------
788 * Deallocate hosts_db.
790 void hosts_db_free(void)
794 assert(hosts_db
!= NULL
);
795 for (i
=0; i
<hosts_db
->size
; i
++) {
796 struct bucket
*tmp
, *b
= hosts_db
->table
[i
];
800 hosts_db
->free_func(tmp
);
804 free(hosts_db
->table
);
809 /* ---------------------------------------------------------------------------
810 * Find or create a port_tcp inside a host.
813 host_get_port_tcp(struct bucket
*host
, const uint16_t port
)
815 struct host
*h
= &host
->u
.host
;
817 if (h
->ports_tcp
== NULL
)
818 h
->ports_tcp
= hashtable_make(PORT_BITS
, opt_ports_max
, opt_ports_keep
,
819 hash_func_short
, free_func_simple
, key_func_port_tcp
,
820 find_func_port_tcp
, make_func_port_tcp
,
821 format_cols_port_tcp
, format_row_port_tcp
);
822 return (hashtable_find_or_insert(h
->ports_tcp
, &port
, ALLOW_REDUCE
));
825 /* ---------------------------------------------------------------------------
826 * Find or create a port_udp inside a host.
829 host_get_port_udp(struct bucket
*host
, const uint16_t port
)
831 struct host
*h
= &host
->u
.host
;
833 if (h
->ports_udp
== NULL
)
834 h
->ports_udp
= hashtable_make(PORT_BITS
, opt_ports_max
, opt_ports_keep
,
835 hash_func_short
, free_func_simple
, key_func_port_udp
,
836 find_func_port_udp
, make_func_port_udp
,
837 format_cols_port_udp
, format_row_port_udp
);
838 return (hashtable_find_or_insert(h
->ports_udp
, &port
, ALLOW_REDUCE
));
841 /* ---------------------------------------------------------------------------
842 * Find or create an ip_proto inside a host.
845 host_get_ip_proto(struct bucket
*host
, const uint8_t proto
)
847 struct host
*h
= &host
->u
.host
;
848 static const unsigned int PROTOS_MAX
= 512, PROTOS_KEEP
= 256;
850 if (h
->ip_protos
== NULL
)
851 h
->ip_protos
= hashtable_make(PROTO_BITS
, PROTOS_MAX
, PROTOS_KEEP
,
852 hash_func_byte
, free_func_simple
, key_func_ip_proto
,
853 find_func_ip_proto
, make_func_ip_proto
,
854 format_cols_ip_proto
, format_row_ip_proto
);
855 return (hashtable_find_or_insert(h
->ip_protos
, &proto
, ALLOW_REDUCE
));
858 static struct str
*html_hosts_main(const char *qs
);
859 static struct str
*html_hosts_detail(const char *ip
);
861 /* ---------------------------------------------------------------------------
862 * Web interface: delegate the /hosts/ space.
865 html_hosts(const char *uri
, const char *query
)
867 unsigned int i
, num_elems
;
868 char **elem
= split('/', uri
, &num_elems
);
869 struct str
*buf
= NULL
;
871 assert(num_elems
>= 1);
872 assert(strcmp(elem
[0], "hosts") == 0);
876 buf
= html_hosts_main(query
);
877 else if (num_elems
== 2)
878 /* /hosts/<IP of host>/ */
879 buf
= html_hosts_detail(elem
[1]);
881 for (i
=0; i
<num_elems
; i
++)
884 return (buf
); /* FIXME: a NULL here becomes 404 Not Found, we might want
885 other codes to be possible */
888 /* ---------------------------------------------------------------------------
889 * Format hashtable into HTML.
892 format_table(struct str
*buf
, struct hashtable
*ht
, unsigned int start
,
893 const enum sort_dir sort
, const int full
)
895 const struct bucket
**table
;
896 unsigned int i
, pos
, end
;
899 if ((ht
== NULL
) || (ht
->count
== 0)) {
900 str_append(buf
, "<p>The table is empty.</p>\n");
904 /* Fill table with pointers to buckets in hashtable. */
905 table
= xcalloc(ht
->count
, sizeof(*table
));
906 for (pos
=0, i
=0; i
<ht
->size
; i
++) {
907 struct bucket
*b
= ht
->table
[i
];
913 assert(pos
== ht
->count
);
916 /* full report overrides start and end */
920 end
= MIN(ht
->count
, (uint32_t)start
+MAX_ENTRIES
);
922 str_appendf(buf
, "(%u-%u of %u)<br>\n", start
+1, end
, ht
->count
);
923 qsort_buckets(table
, ht
->count
, start
, end
, sort
);
924 ht
->format_cols_func(buf
);
926 for (i
=start
; i
<end
; i
++) {
927 ht
->format_row_func(buf
, table
[i
], alt
? "alt1" : "alt2");
928 alt
= !alt
; /* alternate class for table rows */
931 str_append(buf
, "</table>\n");
934 /* ---------------------------------------------------------------------------
935 * Web interface: sorted table of hosts.
938 html_hosts_main(const char *qs
)
940 struct str
*buf
= str_make();
941 char *qs_start
, *qs_sort
, *qs_full
, *ep
;
946 /* parse query string */
947 qs_start
= qs_get(qs
, "start");
948 qs_sort
= qs_get(qs
, "sort");
949 qs_full
= qs_get(qs
, "full");
950 if (qs_full
!= NULL
) {
956 if (qs_sort
== NULL
) sort
= TOTAL
;
957 else if (strcmp(qs_sort
, "total") == 0) sort
= TOTAL
;
958 else if (strcmp(qs_sort
, "in") == 0) sort
= IN
;
959 else if (strcmp(qs_sort
, "out") == 0) sort
= OUT
;
960 else if (strcmp(qs_sort
, "lastseen") == 0) sort
= LASTSEEN
;
962 str_append(buf
, "Error: invalid value for \"sort\".\n");
967 if (qs_start
== NULL
)
970 start
= (int)strtoul(qs_start
, &ep
, 10);
972 str_append(buf
, "Error: \"start\" is not a number.\n");
975 if ((errno
== ERANGE
) ||
976 (start
< 0) || (start
>= (int)hosts_db
->count
)) {
977 str_append(buf
, "Error: \"start\" is out of bounds.\n");
982 #define PREV "<<< prev page"
983 #define NEXT "next page >>>"
984 #define FULL "full table"
986 html_open(buf
, "Hosts", /*path_depth=*/1, /*want_graph_js=*/0);
987 format_table(buf
, hosts_db
, start
, sort
, full
);
989 /* <prev | full | stats | next> */
991 if (sortstr
== NULL
) sortstr
= "total";
993 int prev
= start
- MAX_ENTRIES
;
996 str_appendf(buf
, "<a href=\"?start=%d&sort=%s\">" PREV
"</a>",
999 str_append(buf
, PREV
);
1002 str_append(buf
, " | " FULL
);
1004 str_appendf(buf
, " | <a href=\"?full=yes&sort=%s\">" FULL
"</a>",
1007 if (start
+MAX_ENTRIES
< (int)hosts_db
->count
)
1008 str_appendf(buf
, " | <a href=\"?start=%d&sort=%s\">" NEXT
"</a>",
1009 start
+MAX_ENTRIES
, sortstr
);
1011 str_append(buf
, " | " NEXT
);
1013 str_append(buf
, "<br>\n");
1017 if (qs_start
!= NULL
) free(qs_start
);
1018 if (qs_sort
!= NULL
) free(qs_sort
);
1025 /* ---------------------------------------------------------------------------
1026 * Web interface: detailed view of a single host.
1029 html_hosts_detail(const char *ip
)
1032 struct str
*buf
, *ls_len
;
1034 const char *canonical
;
1037 h
= host_search(ip
);
1039 return (NULL
); /* no such host */
1041 canonical
= addr_to_str(&(h
->u
.host
.addr
));
1045 html_open(buf
, ip
, /*path_depth=*/2, /*want_graph_js=*/0);
1046 if (strcmp(ip
, canonical
) != 0)
1047 str_appendf(buf
, "(canonically <b>%s</b>)\n", canonical
);
1050 "<b>Hostname:</b> %s<br>\n",
1051 (h
->u
.host
.dns
== NULL
)?"(resolving...)":h
->u
.host
.dns
);
1053 /* Resolve host "on demand" */
1054 if (h
->u
.host
.dns
== NULL
)
1055 dns_queue(&(h
->u
.host
.addr
));
1057 if (hosts_db_show_macs
)
1059 "<b>MAC Address:</b> "
1060 "<tt>%x:%x:%x:%x:%x:%x</tt><br>\n",
1061 h
->u
.host
.mac_addr
[0],
1062 h
->u
.host
.mac_addr
[1],
1063 h
->u
.host
.mac_addr
[2],
1064 h
->u
.host
.mac_addr
[3],
1065 h
->u
.host
.mac_addr
[4],
1066 h
->u
.host
.mac_addr
[5]);
1071 "<b>Last seen:</b> ");
1073 ls
= h
->u
.host
.lastseen
;
1074 if (strftime(ls_when
, sizeof(ls_when
),
1075 "%Y-%m-%d %H:%M:%S %Z%z", localtime(&ls
)) != 0)
1076 str_append(buf
, ls_when
);
1078 if (h
->u
.host
.lastseen
<= now
) {
1079 ls_len
= length_of_time(now
- h
->u
.host
.lastseen
);
1080 str_append(buf
, " (");
1081 str_appendstr(buf
, ls_len
);
1083 str_append(buf
, " ago)");
1085 str_append(buf
, " (in the future, possible clock problem)");
1091 " <b>In:</b> %'qu<br>\n"
1092 " <b>Out:</b> %'qu<br>\n"
1093 " <b>Total:</b> %'qu<br>\n"
1095 h
->in
, h
->out
, h
->total
);
1097 str_append(buf
, "<h3>TCP ports</h3>\n");
1098 format_table(buf
, h
->u
.host
.ports_tcp
, 0,TOTAL
,0);
1100 str_append(buf
, "<h3>UDP ports</h3>\n");
1101 format_table(buf
, h
->u
.host
.ports_udp
, 0,TOTAL
,0);
1103 str_append(buf
, "<h3>IP protocols</h3>\n");
1104 format_table(buf
, h
->u
.host
.ip_protos
, 0,TOTAL
,0);
1110 /* ---------------------------------------------------------------------------
1111 * Database import and export code:
1112 * Initially written and contributed by Ben Stewart.
1113 * copyright (c) 2007-2011 Ben Stewart, Emil Mikulic.
1115 static int hosts_db_export_ip(const struct hashtable
*h
, const int fd
);
1116 static int hosts_db_export_tcp(const struct hashtable
*h
, const int fd
);
1117 static int hosts_db_export_udp(const struct hashtable
*h
, const int fd
);
1120 export_proto_ip
= 'P',
1121 export_proto_tcp
= 'T',
1122 export_proto_udp
= 'U';
1124 static const unsigned char
1125 export_tag_host_ver1
[] = {'H', 'S', 'T', 0x01},
1126 export_tag_host_ver2
[] = {'H', 'S', 'T', 0x02},
1127 export_tag_host_ver3
[] = {'H', 'S', 'T', 0x03};
1129 /* ---------------------------------------------------------------------------
1130 * Load a host's ip_proto table from a file.
1131 * Returns 0 on failure, 1 on success.
1134 hosts_db_import_ip(const int fd
, struct bucket
*host
)
1138 if (!expect8(fd
, export_proto_ip
)) return 0;
1139 if (!read8(fd
, &count
)) return 0;
1141 for (i
=0; i
<count
; i
++) {
1146 if (!read8(fd
, &proto
)) return 0;
1147 if (!read64(fd
, &in
)) return 0;
1148 if (!read64(fd
, &out
)) return 0;
1151 b
= host_get_ip_proto(host
, proto
);
1154 b
->total
= in
+ out
;
1155 assert(b
->u
.ip_proto
.proto
== proto
); /* should be done by make fn */
1160 /* ---------------------------------------------------------------------------
1161 * Load a host's port_tcp table from a file.
1162 * Returns 0 on failure, 1 on success.
1165 hosts_db_import_tcp(const int fd
, struct bucket
*host
)
1169 if (!expect8(fd
, export_proto_tcp
)) return 0;
1170 if (!read16(fd
, &count
)) return 0;
1172 for (i
=0; i
<count
; i
++) {
1175 uint64_t in
, out
, syn
;
1177 if (!read16(fd
, &port
)) return 0;
1178 if (!read64(fd
, &syn
)) return 0;
1179 if (!read64(fd
, &in
)) return 0;
1180 if (!read64(fd
, &out
)) return 0;
1183 b
= host_get_port_tcp(host
, port
);
1186 b
->total
= in
+ out
;
1187 assert(b
->u
.port_tcp
.port
== port
); /* done by make_func_port_tcp */
1188 b
->u
.port_tcp
.syn
= syn
;
1193 /* ---------------------------------------------------------------------------
1194 * Load a host's port_tcp table from a file.
1195 * Returns 0 on failure, 1 on success.
1198 hosts_db_import_udp(const int fd
, struct bucket
*host
)
1202 if (!expect8(fd
, export_proto_udp
)) return 0;
1203 if (!read16(fd
, &count
)) return 0;
1205 for (i
=0; i
<count
; i
++) {
1210 if (!read16(fd
, &port
)) return 0;
1211 if (!read64(fd
, &in
)) return 0;
1212 if (!read64(fd
, &out
)) return 0;
1215 b
= host_get_port_udp(host
, port
);
1218 b
->total
= in
+ out
;
1219 assert(b
->u
.port_udp
.port
== port
); /* done by make_func */
1224 /* ---------------------------------------------------------------------------
1225 * Load all hosts from a file.
1226 * Returns 0 on failure, 1 on success.
1229 hosts_db_import_host(const int fd
)
1231 struct bucket
*host
;
1233 uint8_t hostname_len
;
1235 unsigned int pos
= xtell(fd
);
1239 if (!readn(fd
, hdr
, sizeof(hdr
))) return 0;
1240 if (memcmp(hdr
, export_tag_host_ver3
, sizeof(hdr
)) == 0)
1242 else if (memcmp(hdr
, export_tag_host_ver2
, sizeof(hdr
)) == 0)
1244 else if (memcmp(hdr
, export_tag_host_ver1
, sizeof(hdr
)) == 0)
1247 warnx("bad host header: %02x%02x%02x%02x",
1248 hdr
[0], hdr
[1], hdr
[2], hdr
[3]);
1253 if (!readaddr(fd
, &a
))
1256 assert((ver
== 1) || (ver
== 2));
1257 if (!readaddr_ipv4(fd
, &a
))
1260 verbosef("at file pos %u, importing host %s", pos
, addr_to_str(&a
));
1261 host
= host_get(&a
);
1262 assert(addr_equal(&(host
->u
.host
.addr
), &a
));
1266 if (!read64(fd
, &t
)) return 0;
1267 host
->u
.host
.lastseen
= (time_t)t
;
1270 assert(sizeof(host
->u
.host
.mac_addr
) == 6);
1271 if (!readn(fd
, host
->u
.host
.mac_addr
, sizeof(host
->u
.host
.mac_addr
)))
1275 assert(host
->u
.host
.dns
== NULL
); /* make fn? */
1276 if (!read8(fd
, &hostname_len
)) return 0;
1277 if (hostname_len
> 0) {
1278 host
->u
.host
.dns
= xmalloc(hostname_len
+ 1);
1279 host
->u
.host
.dns
[0] = '\0';
1281 /* At this point, the hostname is attached to a host which is in our
1282 * hosts_db, so if we bail out due to an import error, this pointer
1283 * isn't lost and leaked, it can be cleaned up in hosts_db_{free,reset}
1286 if (!readn(fd
, host
->u
.host
.dns
, hostname_len
)) return 0;
1287 host
->u
.host
.dns
[hostname_len
] = '\0';
1290 if (!read64(fd
, &in
)) return 0;
1291 if (!read64(fd
, &out
)) return 0;
1295 host
->total
= in
+ out
;
1297 /* Host's port and proto subtables: */
1298 if (!hosts_db_import_ip(fd
, host
)) return 0;
1299 if (!hosts_db_import_tcp(fd
, host
)) return 0;
1300 if (!hosts_db_import_udp(fd
, host
)) return 0;
1304 /* ---------------------------------------------------------------------------
1305 * Database Import: Grab hosts_db from a file provided by the caller.
1307 * This function will retrieve the data sans the header. We expect the caller
1308 * to have validated the header of the hosts_db segment, and left the file
1309 * sitting at the start of the data.
1311 int hosts_db_import(const int fd
)
1313 uint32_t host_count
, i
;
1315 if (!read32(fd
, &host_count
)) return 0;
1317 for (i
=0; i
<host_count
; i
++)
1318 if (!hosts_db_import_host(fd
)) return 0;
1323 /* ---------------------------------------------------------------------------
1324 * Database Export: Dump hosts_db into a file provided by the caller.
1325 * The caller is responsible for writing out export_tag_hosts_ver1 first.
1327 int hosts_db_export(const int fd
)
1332 if (!write32(fd
, hosts_db
->count
)) return 0;
1334 for (i
= 0; i
<hosts_db
->size
; i
++)
1335 for (b
= hosts_db
->table
[i
]; b
!= NULL
; b
= b
->next
) {
1336 /* For each host: */
1337 if (!writen(fd
, export_tag_host_ver3
, sizeof(export_tag_host_ver3
)))
1340 if (!writeaddr(fd
, &(b
->u
.host
.addr
))) return 0;
1342 if (!write64(fd
, (uint64_t)(b
->u
.host
.lastseen
))) return 0;
1344 assert(sizeof(b
->u
.host
.mac_addr
) == 6);
1345 if (!writen(fd
, b
->u
.host
.mac_addr
, sizeof(b
->u
.host
.mac_addr
)))
1349 if (b
->u
.host
.dns
== NULL
) {
1350 if (!write8(fd
, 0)) return 0;
1352 int dnslen
= strlen(b
->u
.host
.dns
);
1355 warnx("found a very long hostname: \"%s\"\n"
1356 "wasn't expecting one longer than 255 chars (this one is %d)",
1357 b
->u
.host
.dns
, dnslen
);
1361 if (!write8(fd
, (uint8_t)dnslen
)) return 0;
1362 if (!writen(fd
, b
->u
.host
.dns
, dnslen
)) return 0;
1365 if (!write64(fd
, b
->in
)) return 0;
1366 if (!write64(fd
, b
->out
)) return 0;
1368 if (!hosts_db_export_ip(b
->u
.host
.ip_protos
, fd
)) return 0;
1369 if (!hosts_db_export_tcp(b
->u
.host
.ports_tcp
, fd
)) return 0;
1370 if (!hosts_db_export_udp(b
->u
.host
.ports_udp
, fd
)) return 0;
1375 /* ---------------------------------------------------------------------------
1376 * Dump the ip_proto table of a host.
1379 hosts_db_export_ip(const struct hashtable
*h
, const int fd
)
1381 uint32_t i
, written
= 0;
1385 if (!write8(fd
, export_proto_ip
)) return 0;
1387 /* If no data, write a IP Proto count of 0 and we're done. */
1389 if (!write8(fd
, 0)) return 0;
1393 assert(h
->count
< 256);
1394 if (!write8(fd
, (uint8_t)h
->count
)) return 0;
1396 for (i
= 0; i
<h
->size
; i
++)
1397 for (b
= h
->table
[i
]; b
!= NULL
; b
= b
->next
) {
1398 /* For each ip_proto bucket: */
1400 if (!write8(fd
, b
->u
.ip_proto
.proto
)) return 0;
1401 if (!write64(fd
, b
->in
)) return 0;
1402 if (!write64(fd
, b
->out
)) return 0;
1405 assert(written
== h
->count
);
1409 /* ---------------------------------------------------------------------------
1410 * Dump the port_tcp table of a host.
1413 hosts_db_export_tcp(const struct hashtable
*h
, const int fd
)
1416 uint32_t i
, written
= 0;
1419 if (!write8(fd
, export_proto_tcp
)) return 0;
1421 /* If no data, write a count of 0 and we're done. */
1423 if (!write16(fd
, 0)) return 0;
1427 assert(h
->count
< 65536);
1428 if (!write16(fd
, (uint16_t)h
->count
)) return 0;
1430 for (i
= 0; i
<h
->size
; i
++)
1431 for (b
= h
->table
[i
]; b
!= NULL
; b
= b
->next
) {
1432 if (!write16(fd
, b
->u
.port_tcp
.port
)) return 0;
1433 if (!write64(fd
, b
->u
.port_tcp
.syn
)) return 0;
1434 if (!write64(fd
, b
->in
)) return 0;
1435 if (!write64(fd
, b
->out
)) return 0;
1438 assert(written
== h
->count
);
1442 /* ---------------------------------------------------------------------------
1443 * Dump the port_udp table of a host.
1446 hosts_db_export_udp(const struct hashtable
*h
, const int fd
)
1449 uint32_t i
, written
= 0;
1452 if (!write8(fd
, export_proto_udp
)) return 0;
1454 /* If no data, write a count of 0 and we're done. */
1456 if (!write16(fd
, 0)) return 0;
1460 assert(h
->count
< 65536);
1461 if (!write16(fd
, (uint16_t)h
->count
)) return 0;
1463 for (i
= 0; i
<h
->size
; i
++)
1464 for (b
= h
->table
[i
]; b
!= NULL
; b
= b
->next
) {
1465 if (!write16(fd
, b
->u
.port_udp
.port
)) return 0;
1466 if (!write64(fd
, b
->in
)) return 0;
1467 if (!write64(fd
, b
->out
)) return 0;
1470 assert(written
== h
->count
);
1474 /* vim:set ts=3 sw=3 tw=78 expandtab: */