Quiet format string warnings in graph_db.
[darkstat] / hosts_db.c
index 4228d82..7ff4761 100644 (file)
@@ -1,5 +1,5 @@
 /* darkstat 3
- * copyright (c) 2001-2009 Emil Mikulic.
+ * copyright (c) 2001-2011 Emil Mikulic.
  *
  * hosts_db.c: database of hosts, ports, protocols.
  *
@@ -7,7 +7,7 @@
  * 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 <stdlib.h>
 #include <string.h> /* memset(), strcmp() */
+#include <time.h>
 #include <unistd.h>
 
-extern int want_lastseen;
-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 */
@@ -101,15 +102,23 @@ coprime(const uint32_t u)
  * 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
+#  define s6_addr32 __u6_addr.__u6_addr32
+# endif
 #endif
 
 /*
@@ -117,22 +126,30 @@ ipv4_hash(const uint32_t ip)
  * svn rev 122922.
  */
 inline static uint32_t
-ipv6_hash(const struct in6_addr *const ip6)
+ipv6_hash(const struct addr *const a)
 {
-   return ( ip6->s6_addr32[0] ^ ip6->s6_addr32[1] ^ ip6->s6_addr32[2] ^ ip6->s6_addr32[3] );
+   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)
 {
@@ -152,7 +169,7 @@ hash_func_byte(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 *
@@ -180,7 +197,7 @@ key_func_ip_proto(const struct bucket *b)
 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
@@ -218,9 +235,9 @@ static struct bucket *
 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->last_seen_mono = 0;
    memset(&h->mac_addr, 0, sizeof(h->mac_addr));
    h->ports_tcp = NULL;
    h->ports_udp = NULL;
@@ -285,14 +302,14 @@ format_cols_host(struct str *buf)
       "<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");
-   if (want_lastseen) str_append(buf,
-      " <th>Last seen</th>\n");
+   if (opt_want_lastseen) str_append(buf,
+      " <th><a href=\"?sort=lastseen\">Last seen</a></th>\n");
    str_append(buf,
       "</tr>\n");
 }
@@ -301,17 +318,17 @@ static void
 format_row_host(struct str *buf, const struct bucket *b,
    const char *css_class)
 {
-   const char *ip = ip_to_str( b->u.host.ip );
+   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],
@@ -325,33 +342,37 @@ format_row_host(struct str *buf, const struct bucket *b,
       " <td class=\"num\">%'qu</td>\n"
       " <td class=\"num\">%'qu</td>\n"
       " <td class=\"num\">%'qu</td>\n",
-      b->in, b->out, b->total);
-
-   if (want_lastseen) {
-      time_t last_t = b->u.host.last_seen;
-      struct str *lastseen = NULL;
-
-      if (now >= last_t)
-         lastseen = length_of_time(now - last_t);
-
-      str_append(buf,
-         " <td class=\"num\">");
-      if (lastseen == NULL)
-         str_append(buf, "(clock error)");
-      else {
-         str_appendstr(buf, lastseen);
-         str_free(lastseen);
+      (qu)b->in,
+      (qu)b->out,
+      (qu)b->total);
+
+   if (opt_want_lastseen) {
+      time_t last = b->u.host.last_seen_mono;
+      struct str *last_str = NULL;
+
+      if ((now_mono() >= last) && (last > 0))
+         last_str = length_of_time(now_mono() - last);
+
+      str_append(buf, " <td class=\"num\">");
+      if (last_str == NULL) {
+         if (last == 0)
+            str_append(buf, "(never)");
+         else
+            str_appendf(buf, "(clock error: now = %qu, last = %qu)",
+                        (qu)now_mono(),
+                        (qu)last);
+      } else {
+         str_appendstr(buf, last_str);
+         str_free(last_str);
       }
-      str_append(buf,
-         "</td>");
+      str_append(buf, "</td>");
    }
 
-   str_appendf(buf,
-      "</tr>\n");
+   str_appendf(buf, "</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
@@ -386,7 +407,12 @@ format_row_port_tcp(struct str *buf, const struct bucket *b,
       " <td class=\"num\">%'qu</td>\n"
       "</tr>\n",
       css_class,
-      p->port, getservtcp(p->port), b->in, b->out, b->total, p->syn
+      p->port,
+      getservtcp(p->port),
+      (qu)b->in,
+      (qu)b->out,
+      (qu)b->total,
+      (qu)p->syn
    );
 }
 
@@ -420,7 +446,11 @@ format_row_port_udp(struct str *buf, const struct bucket *b,
       " <td class=\"num\">%'qu</td>\n"
       "</tr>\n",
       css_class,
-      p->port, getservudp(p->port), b->in, b->out, b->total
+      p->port,
+      getservudp(p->port),
+      (qu)b->in,
+      (qu)b->out,
+      (qu)b->total
    );
 }
 
@@ -454,8 +484,11 @@ format_row_ip_proto(struct str *buf, const struct bucket *b,
       " <td class=\"num\">%'qu</td>\n"
       "</tr>\n",
       css_class,
-      p->proto, getproto(p->proto),
-      b->in, b->out, b->total
+      p->proto,
+      getproto(p->proto),
+      (qu)b->in,
+      (qu)b->out,
+      (qu)b->total
    );
 }
 
@@ -504,7 +537,7 @@ 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);
 }
@@ -586,16 +619,18 @@ hashtable_search(struct hashtable *h, const void *key)
    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);
    }
@@ -631,18 +666,18 @@ hashtable_free(struct hashtable *h)
  * 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));
 }
 
 /* ---------------------------------------------------------------------------
@@ -651,11 +686,33 @@ host_find(const in_addr_t ip)
 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));
 }
 
 /* ---------------------------------------------------------------------------
@@ -711,6 +768,13 @@ hashtable_reduce(struct hashtable *ht)
    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.
  */
@@ -764,11 +828,11 @@ host_get_port_tcp(struct bucket *host, const uint16_t port)
    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));
 }
 
 /* ---------------------------------------------------------------------------
@@ -780,11 +844,11 @@ host_get_port_udp(struct bucket *host, const uint16_t port)
    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));
 }
 
 /* ---------------------------------------------------------------------------
@@ -801,7 +865,7 @@ host_get_ip_proto(struct bucket *host, const uint8_t proto)
          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);
@@ -813,7 +877,7 @@ static struct str *html_hosts_detail(const char *ip);
 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;
 
@@ -838,11 +902,11 @@ html_hosts(const char *uri, const char *query)
  * 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)) {
@@ -866,9 +930,9 @@ format_table(struct str *buf, struct hashtable *ht, int start,
       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);
 
@@ -906,6 +970,7 @@ html_hosts_main(const char *qs)
    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;
@@ -931,17 +996,16 @@ html_hosts_main(const char *qs)
 #define NEXT "next page &gt;&gt;&gt;"
 #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
@@ -959,8 +1023,9 @@ html_hosts_main(const char *qs)
    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);
@@ -973,57 +1038,56 @@ done:
 /* ---------------------------------------------------------------------------
  * Web interface: detailed view of a single host.
  */
-static struct str *
-html_hosts_detail(const char *ip)
-{
+static struct str *html_hosts_detail(const char *ip) {
    struct bucket *h;
    struct str *buf, *ls_len;
    char ls_when[100];
-   time_t ls;
+   const char *canonical;
+   time_t last_real;
 
    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;
+   last_real = mono_to_real(h->u.host.last_seen_mono);
    if (strftime(ls_when, sizeof(ls_when),
-      "%Y-%m-%d %H:%M:%S %Z%z", localtime(&ls)) != 0)
+      "%Y-%m-%d %H:%M:%S %Z%z", localtime(&last_real)) != 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.last_seen_mono <= now_mono()) {
+      ls_len = length_of_time(now_mono() - h->u.host.last_seen_mono);
       str_append(buf, " (");
       str_appendstr(buf, ls_len);
       str_free(ls_len);
@@ -1035,11 +1099,13 @@ html_hosts_detail(const char *ip)
    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);
+      (qu)h->in,
+      (qu)h->out,
+      (qu)h->total);
 
    str_append(buf, "<h3>TCP ports</h3>\n");
    format_table(buf, h->u.host.ports_tcp, 0,TOTAL,0);
@@ -1050,14 +1116,14 @@ html_hosts_detail(const char *ip)
    str_append(buf, "<h3>IP protocols</h3>\n");
    format_table(buf, h->u.host.ip_protos, 0,TOTAL,0);
 
-   str_append(buf, html_footer);
-   return (buf);
+   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);
@@ -1070,7 +1136,8 @@ static const char
 
 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.
@@ -1175,7 +1242,7 @@ static int
 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);
@@ -1183,7 +1250,9 @@ hosts_db_import_host(const int 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;
@@ -1193,15 +1262,22 @@ hosts_db_import_host(const int fd)
       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.last_seen_mono = real_to_mono(t);
    }
 
    assert(sizeof(host->u.host.mac_addr) == 6);
@@ -1271,12 +1347,14 @@ int hosts_db_export(const int fd)
    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)mono_to_real(b->u.host.last_seen_mono)))
+         return 0;
 
       assert(sizeof(b->u.host.mac_addr) == 6);
       if (!writen(fd, b->u.host.mac_addr, sizeof(b->u.host.mac_addr)))