/* darkstat 3
- * copyright (c) 2001-2008 Emil Mikulic.
+ * copyright (c) 2001-2011 Emil Mikulic.
*
* hosts_db.c: database of hosts, ports, protocols.
*
* GNU General Public License version 2. (see COPYING.GPL)
*/
-#include "darkstat.h"
+#include "cdefs.h"
#include "conv.h"
#include "decode.h"
#include "dns.h"
#include "html.h"
#include "ncache.h"
#include "now.h"
+#include "opt.h"
+#include "str.h"
-#include <arpa/inet.h> /* inet_aton() */
+#include <netdb.h> /* struct addrinfo */
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h> /* memset(), strcmp() */
#include <unistd.h>
-int show_mac_addrs = 0;
-extern const char *interface;
+int hosts_db_show_macs = 0;
/* FIXME: specify somewhere more sane/tunable */
#define MAX_ENTRIES 30 /* in an HTML table rendered from a hashtable */
* src/sys/netinet/tcp_hostcache.c 1.1
*/
inline static uint32_t
-ipv4_hash(const uint32_t ip)
+ipv4_hash(const struct addr *const a)
{
+ uint32_t ip = a->ip.v4;
return ( (ip) ^ ((ip) >> 7) ^ ((ip) >> 17) );
}
+#ifndef s6_addr32
+# ifdef sun
+/*
+ * http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/netinet/in.h#130
+ */
+# define s6_addr32 _S6_un._S6_u32
+# else
+/* Covers OpenBSD and FreeBSD. The macro __USE_GNU has
+ * taken care of GNU/Linux and GNU/kfreebsd. */
+# define s6_addr32 __u6_addr.__u6_addr32
+# endif
+#endif
+
+/*
+ * This is the IPv6 hash function used by FreeBSD in the same file as above,
+ * svn rev 122922.
+ */
+inline static uint32_t
+ipv6_hash(const struct addr *const a)
+{
+ const struct in6_addr *const ip6 = &(a->ip.v6);
+ return ( ip6->s6_addr32[0] ^ ip6->s6_addr32[1] ^
+ ip6->s6_addr32[2] ^ ip6->s6_addr32[3] );
+}
+
/* ---------------------------------------------------------------------------
* hash_func collection
*/
-#define CASTKEY(type) (*((const type *)key))
-
static uint32_t
hash_func_host(const struct hashtable *h _unused_, const void *key)
{
- return (ipv4_hash(CASTKEY(in_addr_t)));
+ const struct addr *a = key;
+ if (a->family == IPv4)
+ return (ipv4_hash(a));
+ else {
+ assert(a->family == IPv6);
+ return (ipv6_hash(a));
+ }
}
+#define CASTKEY(type) (*((const type *)key))
+
static uint32_t
hash_func_short(const struct hashtable *h, const void *key)
{
static const void *
key_func_host(const struct bucket *b)
{
- return &(b->u.host.ip);
+ return &(b->u.host.addr);
}
static const void *
static int
find_func_host(const struct bucket *b, const void *key)
{
- return (b->u.host.ip == CASTKEY(in_addr_t));
+ return (addr_equal(key, &(b->u.host.addr)));
}
static int
struct bucket *next; \
uint64_t in, out, total; \
union { struct type t; } u; } _custom_bucket; \
- struct bucket *name_bucket = xmalloc(sizeof(_custom_bucket)); \
+ struct bucket *name_bucket = xcalloc(1, sizeof(_custom_bucket)); \
struct type *name_content = &(name_bucket->u.type); \
name_bucket->next = NULL; \
name_bucket->in = name_bucket->out = name_bucket->total = 0;
make_func_host(const void *key)
{
MAKE_BUCKET(b, h, host);
- h->ip = CASTKEY(in_addr_t);
+ h->addr = CASTKEY(struct addr);
h->dns = NULL;
- h->last_seen = now;
+ h->lastseen = 0;
memset(&h->mac_addr, 0, sizeof(h->mac_addr));
h->ports_tcp = NULL;
h->ports_udp = NULL;
"<tr>\n"
" <th>IP</th>\n"
" <th>Hostname</th>\n");
- if (show_mac_addrs) str_append(buf,
+ if (hosts_db_show_macs) str_append(buf,
" <th>MAC Address</th>\n");
str_append(buf,
" <th><a href=\"?sort=in\">In</a></th>\n"
" <th><a href=\"?sort=out\">Out</a></th>\n"
- " <th><a href=\"?sort=total\">Total</a></th>\n"
- " <th>Last seen</th>\n"
+ " <th><a href=\"?sort=total\">Total</a></th>\n");
+ if (opt_want_lastseen) str_append(buf,
+ " <th><a href=\"?sort=lastseen\">Last seen</a></th>\n");
+ str_append(buf,
"</tr>\n");
}
format_row_host(struct str *buf, const struct bucket *b,
const char *css_class)
{
- const char *ip = ip_to_str( b->u.host.ip );
- struct str *lastseen = NULL;
- time_t last_t;
+ const char *ip = addr_to_str(&(b->u.host.addr));
str_appendf(buf,
"<tr class=\"%s\">\n"
- " <td><a href=\"%s/\">%s</a></td>\n"
+ " <td><a href=\"./%s/\">%s</a></td>\n"
" <td>%s</td>\n",
css_class,
ip, ip,
(b->u.host.dns == NULL) ? "" : b->u.host.dns);
- if (show_mac_addrs)
+ if (hosts_db_show_macs)
str_appendf(buf,
" <td><tt>%x:%x:%x:%x:%x:%x</tt></td>\n",
b->u.host.mac_addr[0],
b->u.host.mac_addr[4],
b->u.host.mac_addr[5]);
- last_t = b->u.host.last_seen;
- if (now >= last_t)
- lastseen = length_of_time(now - last_t);
-
str_appendf(buf,
" <td class=\"num\">%'qu</td>\n"
" <td class=\"num\">%'qu</td>\n"
- " <td class=\"num\">%'qu</td>\n"
- " <td class=\"num\">",
+ " <td class=\"num\">%'qu</td>\n",
b->in, b->out, b->total);
- if (lastseen == NULL)
- str_append(buf, "(clock error)");
- else {
- str_appendstr(buf, lastseen);
- str_free(lastseen);
+ if (opt_want_lastseen) {
+ time_t last_t = b->u.host.lastseen;
+ struct str *last_str = NULL;
+
+ if ((now >= last_t) && (last_t > 0))
+ last_str = length_of_time(now - last_t);
+
+ str_append(buf,
+ " <td class=\"num\">");
+ if (last_str == NULL) {
+ if (last_t == 0)
+ str_append(buf, "(never)");
+ else
+ str_append(buf, "(clock error)");
+ } else {
+ str_appendstr(buf, last_str);
+ str_free(last_str);
+ }
+ str_append(buf,
+ "</td>");
}
str_appendf(buf,
- "</td>\n"
"</tr>\n");
/* Only resolve hosts "on demand" */
if (b->u.host.dns == NULL)
- dns_queue(b->u.host.ip);
+ dns_queue(&(b->u.host.addr));
}
static void
hosts_db_init(void)
{
assert(hosts_db == NULL);
- hosts_db = hashtable_make(HOST_BITS, hosts_max, hosts_keep,
+ hosts_db = hashtable_make(HOST_BITS, opt_hosts_max, opt_hosts_keep,
hash_func_host, free_func_host, key_func_host, find_func_host,
make_func_host, format_cols_host, format_row_host);
}
return (NULL);
}
+typedef enum { NO_REDUCE = 0, ALLOW_REDUCE = 1 } reduce_bool;
/* Search for a key. If it's not there, make and insert a bucket for it. */
static struct bucket *
-hashtable_find_or_insert(struct hashtable *h, const void *key)
+hashtable_find_or_insert(struct hashtable *h, const void *key,
+ const reduce_bool allow_reduce)
{
struct bucket *b = hashtable_search(h, key);
if (b == NULL) {
/* Not found, so insert after checking occupancy. */
- /*assert(h->count <= h->count_max);*/
- if (h->count >= h->count_max) hashtable_reduce(h);
+ if (allow_reduce && (h->count >= h->count_max))
+ hashtable_reduce(h);
b = h->make_func(key);
hashtable_insert(h, b);
}
* Return existing host or insert a new one.
*/
struct bucket *
-host_get(const in_addr_t ip)
+host_get(const struct addr *const a)
{
- return (hashtable_find_or_insert(hosts_db, &ip));
+ return (hashtable_find_or_insert(hosts_db, a, NO_REDUCE));
}
/* ---------------------------------------------------------------------------
* Find host, returns NULL if not in DB.
*/
struct bucket *
-host_find(const in_addr_t ip)
+host_find(const struct addr *const a)
{
- return (hashtable_search(hosts_db, &ip));
+ return (hashtable_search(hosts_db, a));
}
/* ---------------------------------------------------------------------------
static struct bucket *
host_search(const char *ipstr)
{
- struct in_addr addr;
+ struct addr a;
+ struct addrinfo hints, *ai;
- if (inet_aton(ipstr, &addr) != 1)
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_NUMERICHOST;
+
+ if (getaddrinfo(ipstr, NULL, &hints, &ai))
return (NULL); /* invalid addr */
- return (hashtable_search(hosts_db, &(addr.s_addr)));
+
+ if (ai->ai_family == AF_INET) {
+ a.family = IPv4;
+ a.ip.v4 = ((const struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr;
+ }
+ else if (ai->ai_family == AF_INET6) {
+ a.family = IPv6;
+ memcpy(&(a.ip.v6),
+ ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr.s6_addr,
+ sizeof(a.ip.v6));
+ } else {
+ freeaddrinfo(ai);
+ return (NULL); /* unknown family */
+ }
+ freeaddrinfo(ai);
+
+ verbosef("search(%s) turned into %s", ipstr, addr_to_str(&a));
+ return (hashtable_search(hosts_db, &a));
}
/* ---------------------------------------------------------------------------
assert(ht->count_keep < ht->count);
/* Fill table with pointers to buckets in hashtable. */
- table = xmalloc(sizeof(*table) * ht->count);
+ table = xcalloc(ht->count, sizeof(*table));
for (pos=0, i=0; i<ht->size; i++) {
struct bucket *b = ht->table[i];
while (b != NULL) {
hashtable_rehash(ht, ht->bits); /* is this needed? */
}
+/* Reduce hosts_db if needed. */
+void hosts_db_reduce(void)
+{
+ if (hosts_db->count >= hosts_db->count_max)
+ hashtable_reduce(hosts_db);
+}
+
/* ---------------------------------------------------------------------------
* Reset hosts_db to empty.
*/
struct host *h = &host->u.host;
assert(h != NULL);
if (h->ports_tcp == NULL)
- h->ports_tcp = hashtable_make(PORT_BITS, ports_max, ports_keep,
+ h->ports_tcp = hashtable_make(PORT_BITS, opt_ports_max, opt_ports_keep,
hash_func_short, free_func_simple, key_func_port_tcp,
find_func_port_tcp, make_func_port_tcp,
format_cols_port_tcp, format_row_port_tcp);
- return (hashtable_find_or_insert(h->ports_tcp, &port));
+ return (hashtable_find_or_insert(h->ports_tcp, &port, ALLOW_REDUCE));
}
/* ---------------------------------------------------------------------------
struct host *h = &host->u.host;
assert(h != NULL);
if (h->ports_udp == NULL)
- h->ports_udp = hashtable_make(PORT_BITS, ports_max, ports_keep,
+ h->ports_udp = hashtable_make(PORT_BITS, opt_ports_max, opt_ports_keep,
hash_func_short, free_func_simple, key_func_port_udp,
find_func_port_udp, make_func_port_udp,
format_cols_port_udp, format_row_port_udp);
- return (hashtable_find_or_insert(h->ports_udp, &port));
+ return (hashtable_find_or_insert(h->ports_udp, &port, ALLOW_REDUCE));
}
/* ---------------------------------------------------------------------------
hash_func_byte, free_func_simple, key_func_ip_proto,
find_func_ip_proto, make_func_ip_proto,
format_cols_ip_proto, format_row_ip_proto);
- return (hashtable_find_or_insert(h->ip_protos, &proto));
+ return (hashtable_find_or_insert(h->ip_protos, &proto, ALLOW_REDUCE));
}
static struct str *html_hosts_main(const char *qs);
struct str *
html_hosts(const char *uri, const char *query)
{
- int i, num_elems;
+ unsigned int i, num_elems;
char **elem = split('/', uri, &num_elems);
struct str *buf = NULL;
* Format hashtable into HTML.
*/
static void
-format_table(struct str *buf, struct hashtable *ht, int start,
+format_table(struct str *buf, struct hashtable *ht, unsigned int start,
const enum sort_dir sort, const int full)
{
const struct bucket **table;
- uint32_t i, pos, end;
+ unsigned int i, pos, end;
int alt = 0;
if ((ht == NULL) || (ht->count == 0)) {
}
/* Fill table with pointers to buckets in hashtable. */
- table = xmalloc(sizeof(*table) * ht->count);
+ table = xcalloc(ht->count, sizeof(*table));
for (pos=0, i=0; i<ht->size; i++) {
struct bucket *b = ht->table[i];
while (b != NULL) {
start = 0;
end = ht->count;
} else
- end = min(ht->count, (uint32_t)start+MAX_ENTRIES);
+ end = MIN(ht->count, (uint32_t)start+MAX_ENTRIES);
- str_appendf(buf, "(%u-%u of %u)<br/>\n", start+1, end, ht->count);
+ str_appendf(buf, "(%u-%u of %u)<br>\n", start+1, end, ht->count);
qsort_buckets(table, ht->count, start, end, sort);
ht->format_cols_func(buf);
else if (strcmp(qs_sort, "total") == 0) sort = TOTAL;
else if (strcmp(qs_sort, "in") == 0) sort = IN;
else if (strcmp(qs_sort, "out") == 0) sort = OUT;
+ else if (strcmp(qs_sort, "lastseen") == 0) sort = LASTSEEN;
else {
str_append(buf, "Error: invalid value for \"sort\".\n");
goto done;
#define NEXT "next page >>>"
#define FULL "full table"
- str_append(buf, html_header_1);
- str_appendf(buf, " <title>darkstat3: Hosts (%s)</title>\n", interface);
- str_append(buf, html_header_2);
- str_appendf(buf, "<h2 class=\"pageheader\">Hosts (%s)</h2>\n", interface);
+ html_open(buf, "Hosts", /*path_depth=*/1, /*want_graph_js=*/0);
format_table(buf, hosts_db, start, sort, full);
/* <prev | full | stats | next> */
sortstr = qs_sort;
if (sortstr == NULL) sortstr = "total";
if (start > 0) {
- int prev = max(start - MAX_ENTRIES, 0);
+ int prev = start - MAX_ENTRIES;
+ if (prev < 0)
+ prev = 0;
str_appendf(buf, "<a href=\"?start=%d&sort=%s\">" PREV "</a>",
prev, sortstr);
} else
else
str_append(buf, " | " NEXT);
- str_append(buf, "<br/>\n");
- str_append(buf, html_footer);
+ str_append(buf, "<br>\n");
+
+ html_close(buf);
done:
if (qs_start != NULL) free(qs_start);
if (qs_sort != NULL) free(qs_sort);
struct bucket *h;
struct str *buf, *ls_len;
char ls_when[100];
+ const char *canonical;
time_t ls;
h = host_search(ip);
if (h == NULL)
return (NULL); /* no such host */
+ canonical = addr_to_str(&(h->u.host.addr));
+
/* Overview. */
buf = str_make();
- str_append(buf, html_header_1);
- str_appendf(buf, " <title>%s</title>\n", ip);
- str_append(buf, html_header_2);
+ html_open(buf, ip, /*path_depth=*/2, /*want_graph_js=*/0);
+ if (strcmp(ip, canonical) != 0)
+ str_appendf(buf, "(canonically <b>%s</b>)\n", canonical);
str_appendf(buf,
- "<h2>%s</h2>\n"
"<p>\n"
- "<b>Hostname:</b> %s<br/>\n",
- ip,
+ "<b>Hostname:</b> %s<br>\n",
(h->u.host.dns == NULL)?"(resolving...)":h->u.host.dns);
/* Resolve host "on demand" */
if (h->u.host.dns == NULL)
- dns_queue(h->u.host.ip);
+ dns_queue(&(h->u.host.addr));
- if (show_mac_addrs)
- str_appendf(buf,
- "<b>MAC Address:</b> "
- "<tt>%x:%x:%x:%x:%x:%x</tt><br/>\n",
- h->u.host.mac_addr[0],
- h->u.host.mac_addr[1],
- h->u.host.mac_addr[2],
- h->u.host.mac_addr[3],
- h->u.host.mac_addr[4],
- h->u.host.mac_addr[5]);
+ if (hosts_db_show_macs)
+ str_appendf(buf,
+ "<b>MAC Address:</b> "
+ "<tt>%x:%x:%x:%x:%x:%x</tt><br>\n",
+ h->u.host.mac_addr[0],
+ h->u.host.mac_addr[1],
+ h->u.host.mac_addr[2],
+ h->u.host.mac_addr[3],
+ h->u.host.mac_addr[4],
+ h->u.host.mac_addr[5]);
str_append(buf,
"</p>\n"
"<p>\n"
"<b>Last seen:</b> ");
- ls = h->u.host.last_seen;
+ ls = h->u.host.lastseen;
if (strftime(ls_when, sizeof(ls_when),
"%Y-%m-%d %H:%M:%S %Z%z", localtime(&ls)) != 0)
str_append(buf, ls_when);
- if (h->u.host.last_seen <= now) {
- ls_len = length_of_time(now - h->u.host.last_seen);
+ if (h->u.host.lastseen <= now) {
+ ls_len = length_of_time(now - h->u.host.lastseen);
str_append(buf, " (");
str_appendstr(buf, ls_len);
str_free(ls_len);
str_appendf(buf,
"</p>\n"
"<p>\n"
- " <b>In:</b> %'qu<br/>\n"
- " <b>Out:</b> %'qu<br/>\n"
- " <b>Total:</b> %'qu<br/>\n"
+ " <b>In:</b> %'qu<br>\n"
+ " <b>Out:</b> %'qu<br>\n"
+ " <b>Total:</b> %'qu<br>\n"
"</p>\n",
h->in, h->out, h->total);
str_append(buf, "<h3>IP protocols</h3>\n");
format_table(buf, h->u.host.ip_protos, 0,TOTAL,0);
- str_append(buf, html_footer);
+ html_close(buf);
return (buf);
}
/* ---------------------------------------------------------------------------
* Database import and export code:
* Initially written and contributed by Ben Stewart.
- * copyright (c) 2007 Ben Stewart, Emil Mikulic.
+ * copyright (c) 2007-2011 Ben Stewart, Emil Mikulic.
*/
static int hosts_db_export_ip(const struct hashtable *h, const int fd);
static int hosts_db_export_tcp(const struct hashtable *h, const int fd);
static const unsigned char
export_tag_host_ver1[] = {'H', 'S', 'T', 0x01},
- export_tag_host_ver2[] = {'H', 'S', 'T', 0x02};
+ export_tag_host_ver2[] = {'H', 'S', 'T', 0x02},
+ export_tag_host_ver3[] = {'H', 'S', 'T', 0x03};
/* ---------------------------------------------------------------------------
* Load a host's ip_proto table from a file.
hosts_db_import_host(const int fd)
{
struct bucket *host;
- in_addr_t addr;
+ struct addr a;
uint8_t hostname_len;
uint64_t in, out;
unsigned int pos = xtell(fd);
int ver = 0;
if (!readn(fd, hdr, sizeof(hdr))) return 0;
- if (memcmp(hdr, export_tag_host_ver2, sizeof(hdr)) == 0)
+ if (memcmp(hdr, export_tag_host_ver3, sizeof(hdr)) == 0)
+ ver = 3;
+ else if (memcmp(hdr, export_tag_host_ver2, sizeof(hdr)) == 0)
ver = 2;
else if (memcmp(hdr, export_tag_host_ver1, sizeof(hdr)) == 0)
ver = 1;
return 0;
}
- if (!readaddr(fd, &addr)) return 0;
- verbosef("at file pos %u, importing host %s", pos, ip_to_str(addr));
- host = host_get(addr);
- assert(host->u.host.ip == addr); /* make fn? */
+ if (ver == 3) {
+ if (!readaddr(fd, &a))
+ return 0;
+ } else {
+ assert((ver == 1) || (ver == 2));
+ if (!readaddr_ipv4(fd, &a))
+ return 0;
+ }
+ verbosef("at file pos %u, importing host %s", pos, addr_to_str(&a));
+ host = host_get(&a);
+ assert(addr_equal(&(host->u.host.addr), &a));
if (ver > 1) {
uint64_t t;
if (!read64(fd, &t)) return 0;
- host->u.host.last_seen = (time_t)t;
+ host->u.host.lastseen = (time_t)t;
}
assert(sizeof(host->u.host.mac_addr) == 6);
for (i = 0; i<hosts_db->size; i++)
for (b = hosts_db->table[i]; b != NULL; b = b->next) {
/* For each host: */
- if (!writen(fd, export_tag_host_ver2, sizeof(export_tag_host_ver2)))
+ if (!writen(fd, export_tag_host_ver3, sizeof(export_tag_host_ver3)))
return 0;
- if (!writeaddr(fd, b->u.host.ip)) return 0;
+ if (!writeaddr(fd, &(b->u.host.addr))) return 0;
- if (!write64(fd, (uint64_t)(b->u.host.last_seen))) return 0;
+ if (!write64(fd, (uint64_t)(b->u.host.lastseen))) return 0;
assert(sizeof(b->u.host.mac_addr) == 6);
if (!writen(fd, b->u.host.mac_addr, sizeof(b->u.host.mac_addr)))