You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2014/12/16 03:21:47 UTC

trafficserver git commit: TS-3088: Use /etc/hosts for HostDB.

Repository: trafficserver
Updated Branches:
  refs/heads/master 04871a974 -> a2affb991


TS-3088: Use /etc/hosts for HostDB.


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/a2affb99
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/a2affb99
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/a2affb99

Branch: refs/heads/master
Commit: a2affb9916edc53aeb8b7a5d6d223146be9efdc4
Parents: 04871a9
Author: Alan M. Carroll <am...@apache.org>
Authored: Sun Sep 21 17:18:19 2014 -0500
Committer: Alan M. Carroll <am...@apache.org>
Committed: Mon Dec 15 20:21:23 2014 -0600

----------------------------------------------------------------------
 CHANGES                                         |   2 +
 .../configuration/records.config.en.rst         |  17 +
 iocore/hostdb/HostDB.cc                         | 502 ++++++++++++++++---
 iocore/hostdb/I_HostDBProcessor.h               |  16 +-
 iocore/hostdb/P_HostDBProcessor.h               |  11 +-
 lib/ts/ink_inet.cc                              |   8 +-
 lib/ts/ink_inet.h                               |  11 +-
 mgmt/RecordsConfig.cc                           |   4 +
 proxy/http/HttpSM.cc                            |   1 +
 9 files changed, 482 insertions(+), 90 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index cd2adb9..200c5ad 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache Traffic Server 5.3.0
 
+  *) [TS-3088] Enable /etc/hosts resolution.
+
   *) [TS-3240] Add the `dstaddr` hash key to the balancer plugin.
 
   *) [TS-3239] Add a new `generator` plugin.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/doc/reference/configuration/records.config.en.rst
----------------------------------------------------------------------
diff --git a/doc/reference/configuration/records.config.en.rst b/doc/reference/configuration/records.config.en.rst
index 72bb3d3..2d9bd40 100644
--- a/doc/reference/configuration/records.config.en.rst
+++ b/doc/reference/configuration/records.config.en.rst
@@ -1611,6 +1611,23 @@ uses the same origin server for the same client, for as long as the origin serve
 set to :arg:`N` the IP address is rotated if more than :arg:`N` seconds have past since the first time the
 current address was used.
 
+.. ts:cv:: CONFIG proxy.config.hostdb.host_file.path STRING /etc/hosts
+
+   Set the file path for an external host file.
+
+If this is set (non-empty) then the file is presumed to be a hosts file in the standard `host file format <http://tools.ietf.org/html/rfc1123#page-13>`_. It is read and the entries there added to the HostDB. The file is periodically checked for a more recent modification date in which case it is reloaded. The interval is set by the value :ts:cv:`proxy.config.hostdb.host_file.interval`.
+
+While not technically reloadable, the value is read every time the file is to be checked so that if changed the new
+value will be used on the next check and the file will be treated as modified.
+
+.. ts:cv:: CONFIG proxy.config.hostdb.host_file.interval INT 86400
+   :metric: seconds
+   :reloadable:
+
+   Set the file changed check timer for :ts:cv:`proxy.config.hostdb.host_file.path`.
+
+The file is checked every this many seconds to see if it has changed. If so the HostDB is updated with the new values in the file.
+
 .. ts:cv:: CONFIG proxy.config.hostdb.ip_resolve STRING NULL
 
    Set the host resolution style.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/iocore/hostdb/HostDB.cc
----------------------------------------------------------------------
diff --git a/iocore/hostdb/HostDB.cc b/iocore/hostdb/HostDB.cc
index 1e54bcd..5f066d2 100644
--- a/iocore/hostdb/HostDB.cc
+++ b/iocore/hostdb/HostDB.cc
@@ -27,6 +27,9 @@
 #include "I_Layout.h"
 #include "Show.h"
 
+#include <vector>
+#include <algorithm>
+
 // dxu: turn off all Diags.h 's function.
 //#define Debug
 //#define Warning
@@ -52,8 +55,12 @@ unsigned int hostdb_ip_stale_interval = HOST_DB_IP_STALE;
 unsigned int hostdb_ip_timeout_interval = HOST_DB_IP_TIMEOUT;
 unsigned int hostdb_ip_fail_timeout_interval = HOST_DB_IP_FAIL_TIMEOUT;
 unsigned int hostdb_serve_stale_but_revalidate = 0;
+unsigned int hostdb_hostfile_check_interval = 86400; // 1 day
+unsigned int hostdb_hostfile_update_timestamp = 0;
+unsigned int hostdb_hostfile_check_timestamp = 0;
 char hostdb_filename[PATH_NAME_MAX + 1] = DEFAULT_HOST_DB_FILENAME;
 int hostdb_size = DEFAULT_HOST_DB_SIZE;
+char hostdb_hostfile_path[PATH_NAME_MAX + 1] = "";
 int hostdb_sync_frequency = 120;
 int hostdb_srv_enabled = 0;
 int hostdb_disable_reverse_lookup = 0;
@@ -64,6 +71,8 @@ ClassAllocator<HostDBContinuation> hostDBContAllocator("hostDBContAllocator");
 
 HostDBCache hostDB;
 
+void ParseHostFile(char const* path);
+
 static  Queue <HostDBContinuation > remoteHostDBQueue[MULTI_CACHE_PARTITIONS];
 
 char *
@@ -161,31 +170,68 @@ string_for(HostDBMark mark) {
 static Action *
 register_ShowHostDB(Continuation * c, HTTPHdr * h);
 
+HostDBMD5&
+HostDBMD5::set_host(char const* name, int len)
+{
+  host_name = name;
+  host_len = len;
+#ifdef SPLIT_DNS
+  if (host_name && SplitDNSConfig::isSplitDNSEnabled()) {
+    const char *scan;
+    // I think this is checking for a hostname that is just an address.
+    for (scan = host_name ; *scan != '\0' && (ParseRules::is_digit(*scan) || '.' == *scan || ':' == *scan); ++scan)
+      ;
+    if ('\0' != *scan) {
+      // config is released in the destructor, because we must make sure values we
+      // get out of it don't evaporate while @a this is still around.
+      if (!pSD) pSD = SplitDNSConfig::acquire();
+      if (pSD) {
+        dns_server = static_cast<DNSServer*>(pSD->getDNSRecord(host_name));
+      }
+    } else {
+      dns_server = 0;
+    }
+  }
+#endif // SPLIT_DNS
+  return *this;
+}
+
 void
 HostDBMD5::refresh() {
+  MD5Context ctx;
+
   if (host_name) {
     char const* server_line = dns_server ? dns_server->x_dns_ip_line : 0;
-    make_md5(hash, host_name, host_len, port, server_line, db_mark);
+    uint8_t m = static_cast<uint8_t>(db_mark); // be sure of the type.
+
+    ctx.update(host_name, host_len);
+    ctx.update(reinterpret_cast<uint8_t*>(&port), sizeof(port));
+    ctx.update(&m, sizeof(m));
+    if (server_line) ctx.update(server_line, strlen(server_line));
   } else {
     // INK_MD5 the ip, pad on both sizes with 0's
     // so that it does not intersect the string space
     //
-    uint8_t buff[TS_IP6_SIZE+4];
+    char buff[TS_IP6_SIZE+4];
     int n = ip.isIp6() ? sizeof(in6_addr) : sizeof(in_addr_t);
     memset(buff, 0, 2);
     memcpy(buff+2, ip._addr._byte, n);
     memset(buff + 2 + n , 0, 2);
-    MD5Context().hash_immediate(hash, buff, n+4);
-//    hash.encodeBuffer(buff, n+4);
+    ctx.update(buff, n+4);
   }
+  ctx.finalize(hash);
 }
 
 HostDBMD5::HostDBMD5()
   : host_name(0), host_len(0), port(0),
-    dns_server(0), db_mark(HOSTDB_MARK_GENERIC)
+    dns_server(0), pSD(0), db_mark(HOSTDB_MARK_GENERIC)
 {
 }
 
+HostDBMD5::~HostDBMD5() {
+  if (pSD) SplitDNSConfig::release(pSD);
+}
+
 HostDBCache::HostDBCache()
 {
   tag_bits = HOST_DB_TAG_BITS;
@@ -472,6 +518,7 @@ HostDBProcessor::start(int, size_t)
   REC_EstablishStaticConfigInt32U(hostdb_ip_fail_timeout_interval, "proxy.config.hostdb.fail.timeout");
   REC_EstablishStaticConfigInt32U(hostdb_serve_stale_but_revalidate, "proxy.config.hostdb.serve_stale_for");
   REC_EstablishStaticConfigInt32(hostdb_sync_frequency, "proxy.config.cache.hostdb.sync_frequency");
+  REC_EstablishStaticConfigInt32U(hostdb_hostfile_check_interval, "proxy.config.hostdb.host_file.interval");
 
   //
   // Set up hostdb_current_interval
@@ -530,22 +577,6 @@ HostDBContinuation::refresh_MD5() {
     mutex = hostDB.lock_for_bucket((int) (fold_md5(md5.hash) % hostDB.buckets));
 }
 
-void
-make_md5(INK_MD5 & md5, const char *hostname, int len, int port, char const* pDNSServers, HostDBMark mark)
-{
-  INK_DIGEST_CTX ctx;
-  ink_code_incr_md5_init(&ctx);
-  ink_code_incr_md5_update(&ctx, hostname, len);
-  unsigned short p = port;
-  p = htons(p);
-  ink_code_incr_md5_update(&ctx, (char *) &p, 2);
-  uint8_t m = static_cast<uint8_t>(mark);
-  ink_code_incr_md5_update(&ctx, (char *) &m, sizeof(m));     /* FIXME: check this */
-  if (pDNSServers)
-    ink_code_incr_md5_update(&ctx, pDNSServers, strlen(pDNSServers));
-  ink_code_incr_md5_final((char *) &md5, &ctx);
-}
-
 static bool
 reply_to_cont(Continuation * cont, HostDBInfo * r, bool is_srv = false)
 {
@@ -616,6 +647,11 @@ db_mark_for(sockaddr const* ip) {
   return ats_is_ip6(ip) ? HOSTDB_MARK_IPV6 : HOSTDB_MARK_IPV4;
 }
 
+inline HostDBMark
+db_mark_for(IpAddr const& ip) {
+  return ip.isIp6() ? HOSTDB_MARK_IPV6 : HOSTDB_MARK_IPV4;
+}
+
 HostDBInfo *
 probe(ProxyMutex *mutex, HostDBMD5 const& md5, bool ignore_timeout)
 {
@@ -732,24 +768,10 @@ HostDBProcessor::getby(Continuation * cont,
   }
 
   // Load the MD5 data.
-  md5.host_name = hostname;
-  md5.host_len = hostname ? (len ? len : strlen(hostname)) : 0;
+  md5.set_host(hostname, hostname ? (len ? len : strlen(hostname)) : 0);
   md5.ip.assign(ip);
   md5.port = ip ? ats_ip_port_host_order(ip) : 0;
   md5.db_mark = db_mark_for(host_res_style);
-#ifdef SPLIT_DNS
-  if (hostname && SplitDNSConfig::isSplitDNSEnabled()) {
-    const char *scan = hostname;
-    // Is this a check for IPv4 address? Add check for IPv6?
-    for (; *scan != '\0' && (ParseRules::is_digit(*scan) || '.' == *scan); scan++);
-    if ('\0' != *scan) {
-      SplitDNS* pSD = SplitDNSConfig::acquire();
-      if (0 != pSD)
-        md5.dns_server = static_cast<DNSServer*>(pSD->getDNSRecord(hostname));
-      SplitDNSConfig::release(pSD);
-    }
-  }
-#endif // SPLIT_DNS
   md5.refresh();
 
   // Attempt to find the result in-line, for level 1 hits
@@ -782,7 +804,7 @@ HostDBProcessor::getby(Continuation * cont,
             reply_to_cont(cont, r);
             return ACTION_RESULT_DONE;
           }
-          md5.refresh();
+          md5.refresh(); // only on reloop, because we've changed the family.
         }
       }
     } while (loop);
@@ -938,22 +960,9 @@ HostDBProcessor::getbyname_imm(Continuation * cont, process_hostdb_info_pfn proc
     return ACTION_RESULT_DONE;
   }
 
-  md5.host_name = hostname;
-  md5.host_len = hostname ? (len ? len : strlen(hostname)) : 0;
+  md5.set_host(hostname, hostname ? (len ? len : strlen(hostname)) : 0);
   md5.port = opt.port;
   md5.db_mark = db_mark_for(opt.host_res_style);
-#ifdef SPLIT_DNS
-  if (SplitDNSConfig::isSplitDNSEnabled()) {
-    const char *scan = hostname;
-    for (; *scan != '\0' && (ParseRules::is_digit(*scan) || '.' == *scan); scan++);
-    if ('\0' != *scan) {
-      SplitDNS* pSD = SplitDNSConfig::acquire();
-      if (0 != pSD)
-        md5.dns_server = static_cast<DNSServer*>(pSD->getDNSRecord(md5.host_name));
-      SplitDNSConfig::release(pSD);
-    }
-  }
-#endif // SPLIT_DNS
   md5.refresh();
 
   // Attempt to find the result in-line, for level 1 hits
@@ -1036,7 +1045,6 @@ do_setby(HostDBInfo * r, HostDBApplicationInfo * app, const char *hostname, IpAd
   }
 }
 
-
 void
 HostDBProcessor::setby(const char *hostname, int len, sockaddr const* ip, HostDBApplicationInfo * app)
 {
@@ -1044,8 +1052,7 @@ HostDBProcessor::setby(const char *hostname, int len, sockaddr const* ip, HostDB
     return;
 
   HostDBMD5 md5;
-  md5.host_name = hostname;
-  md5.host_len = hostname ? (len ? len : strlen(hostname)) : 0;
+  md5.set_host(hostname, hostname ? (len ? len : strlen(hostname)) : 0);
   md5.ip.assign(ip);
   md5.port = ip ? ats_ip_port_host_order(ip) : 0;
   md5.db_mark = db_mark_for(ip);
@@ -1080,8 +1087,7 @@ HostDBProcessor::setby_srv(const char *hostname, int len, const char *target, Ho
       return;
 
   HostDBMD5 md5;
-  md5.host_name = hostname;
-  md5.host_len = len ? len : strlen(hostname);
+  md5.set_host(hostname, len ? len : strlen(hostname));
   md5.port = 0;
   md5.db_mark = HOSTDB_MARK_SRV;
   md5.refresh();
@@ -1156,21 +1162,10 @@ Action *
 HostDBProcessor::failed_connect_on_ip_for_name(Continuation * cont, sockaddr const* ip, const char *hostname, int len)
 {
   HostDBMD5 md5;
-  md5.host_name = hostname;
-  md5.host_len = hostname ? (len ? len : strlen(hostname)) : 0;
+  md5.set_host(hostname, hostname ? (len ? len : strlen(hostname)) : 0);
   md5.ip.assign(ip);
   md5.port = ip ? ats_ip_port_host_order(ip) : 0;
   md5.db_mark = db_mark_for(ip);
-#ifdef SPLIT_DNS
-  SplitDNS *pSD = 0;
-  if (hostname && SplitDNSConfig::isSplitDNSEnabled()) {
-    pSD = SplitDNSConfig::acquire();
-
-    if (0 != pSD)
-      md5.dns_server = static_cast<DNSServer*>(pSD->getDNSRecord(hostname));
-    SplitDNSConfig::release(pSD);
-  }
-#endif // SPLIT_DNS
   md5.refresh();
 
   ProxyMutex *mutex = hostDB.lock_for_bucket((int) (fold_md5(md5.hash) % hostDB.buckets));
@@ -1270,8 +1265,12 @@ HostDBContinuation::lookup_done(IpAddr const& ip, char const* aname, bool around
       break;
     }
     HOSTDB_SUM_DYN_STAT(hostdb_ttl_stat, ttl_seconds);
-    if (!ttl_seconds)
-      ttl_seconds = 1;          // www.barnsandnobel.com is lame
+
+    // Not sure about this - it seems wrong but I can't be sure. If we got a fail
+    // in the DNS event, 0 is passed in which we then change to 1 here. Do we need this
+    // to be non-zero to avoid an infinite timeout?
+    if (0 == ttl_seconds) ttl_seconds = 1;
+
     i = insert(ttl_seconds);
     if (is_byname()) {
       ip_text_buffer b;
@@ -1560,7 +1559,6 @@ HostDBContinuation::dnsEvent(int event, HostEnt * e)
             if (is_addr_valid(af, e->ent.h_addr_list[ii])) {
               HostDBInfo& item = rr_data->info[i];
               ip_addr_set(item.ip(), af, e->ent.h_addr_list[ii]);
-//              rr_data->info[i].ip() = *(unsigned int *) e->ent.h_addr_list[ii];
               item.full = 1;
               item.round_robin = 0;
               item.reverse_dns = 0;
@@ -1600,7 +1598,7 @@ HostDBContinuation::dnsEvent(int event, HostEnt * e)
     if (action.continuation) {
       // Check for IP family failover
       if (failed && check_for_retry(md5.db_mark, host_res_style)) {
-        this->refresh_MD5();
+        this->refresh_MD5(); // family changed if we're doing a retry.
         SET_CONTINUATION_HANDLER(this, (HostDBContHandler) & HostDBContinuation::probeEvent);
         thread->schedule_in(this, MUTEX_RETRY_DELAY);
         return EVENT_CONT;
@@ -2128,7 +2126,32 @@ put_hostinfo_ClusterFunction(ClusterHandler *ch, void *data, int /* len ATS_UNUS
 int
 HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
 {
-  hostdb_current_interval++;
+  ++hostdb_current_interval;
+
+  // hostdb_current_interval is bumped every HOST_DB_TIMEOUT_INTERVAL seconds
+  // so we need to scale that so the user config value is in seconds.
+  if (hostdb_hostfile_check_interval && // enabled
+      (hostdb_current_interval - hostdb_hostfile_check_timestamp) * (HOST_DB_TIMEOUT_INTERVAL / HRTIME_SECOND) > hostdb_hostfile_check_interval
+  ) {
+    struct stat info;
+    char path[sizeof(hostdb_hostfile_path)];
+
+    REC_ReadConfigString(path, "proxy.config.hostdb.host_file.path", PATH_NAME_MAX);
+    if (0 != strcasecmp(hostdb_hostfile_path, path)) {
+      Debug("amc", "Update host file '%s' <- '%s'", path, hostdb_hostfile_path);
+      // path to hostfile changed
+      hostdb_hostfile_update_timestamp = 0; // never updated from this file
+      memcpy(hostdb_hostfile_path, path, sizeof(hostdb_hostfile_path));
+    }
+    hostdb_hostfile_check_timestamp = hostdb_current_interval;
+    if (0 == stat(hostdb_hostfile_path, &info)) {
+      if (info.st_mtime > hostdb_hostfile_update_timestamp) {
+        ParseHostFile(hostdb_hostfile_path);
+      }
+    } else {
+      Debug("hostdb", "Failed to stat host file '%s'", hostdb_hostfile_path);
+    }
+  }
 
   return EVENT_CONT;
 }
@@ -2481,3 +2504,336 @@ ink_hostdb_init(ModuleVersion v)
 
   ts_host_res_global_init();
 }
+
+
+/// Pair of IP address and host name from a host file.
+struct HostFilePair {
+  typedef HostFilePair self;
+  IpAddr ip;
+  char const* name;
+};
+
+struct HostDBFileContinuation : public Continuation {
+  typedef HostDBFileContinuation self;
+
+  int idx; ///< Working index.
+  char const* name; ///< Host name (just for debugging)
+  INK_MD5 md5; ///< Key for entry.
+  typedef std::vector<INK_MD5> Keys;
+  Keys* keys; ///< Entries from file.
+  ats_scoped_str path; ///< Used to keep the host file name around.
+
+  HostDBFileContinuation()
+    : Continuation(0)
+    {
+    }
+
+  int insertEvent(int event, void* data);
+  int removeEvent(int event, void* data);
+
+  /// Set the current entry in the HostDB.
+  HostDBInfo* setDBEntry();
+  /// Create and schedule an update continuation.
+  static void scheduleUpdate(
+    int idx, ///< Pair index to process.
+    Keys* keys = 0 ///< Key table if any.
+    );
+  /// Create and schedule a remove continuation.
+  static void scheduleRemove(
+    int idx, ///< Index of current new key to check against.
+    Keys* keys ///< new valid keys
+    );
+  /// Finish update
+  static void finish(
+    Keys* keys ///< Valid keys from update.
+    );
+  /// Clean up this instance.
+  void destroy();
+};
+
+ClassAllocator<HostDBFileContinuation> hostDBFileContAllocator("hostDBFileContAllocator");
+
+void HostDBFileContinuation::destroy() {
+  this->~HostDBFileContinuation();
+  hostDBFileContAllocator.free(this);
+}
+
+// Host file processing globals.
+
+// We can't allow more than one update to be
+// proceeding at a time in any case so we might as well make these
+// globals.
+int HostDBFileUpdateActive = 0;
+// Contents of the host file. We keep this around because other data
+// points in to it (to minimize allocations).
+ats_scoped_str HostFileText;
+// Accumulated pairs of <IP address, host>
+std::vector<HostFilePair> HostFilePairs;
+// Entries from last update.
+HostDBFileContinuation::Keys HostFileKeys;
+/// Ordering operator for HostFilePair.
+/// We want to group first by name and then by address family.
+bool CmpHostFilePair(HostFilePair const& lhs, HostFilePair const& rhs) {
+  int zret = strcasecmp(lhs.name, rhs.name);
+  return zret < 0 || (0 == zret && lhs.ip < rhs.ip);
+}
+// Actual ordering doesn't matter as long as it's consistent.
+bool CmpMD5(INK_MD5 const& lhs, INK_MD5 const& rhs) {
+  return lhs[0] < rhs[0] || 
+    (lhs[0] == rhs[0] && lhs[1] < rhs[1])
+    ;
+}
+
+/// Finish current update.
+void
+HostDBFileContinuation::finish(Keys* keys) {
+  HostFilePairs.clear();
+  HostFileKeys.clear();
+  if (keys) {
+    std::swap(*keys, HostFileKeys); // put the new keys in place and dump the previous.
+    delete keys;
+  }
+
+  HostFileText = 0;
+  HostDBFileUpdateActive = 0;
+}
+
+HostDBInfo*
+HostDBFileContinuation::setDBEntry() {
+  HostDBInfo* r;
+  uint64_t folded_md5 = fold_md5(md5);
+
+  // remove the old one to prevent buildup
+  if (0 != (r = hostDB.lookup_block(folded_md5, 3))) {
+    hostDB.delete_block(r);
+    Debug("hostdb", "Update host file entry %s - %" PRIx64 ".%" PRIx64,
+          name, md5[0], md5[1] );
+  } else {
+    Debug("hostdb", "Add host file entry %s - %" PRIx64 ".%" PRIx64,
+          name, md5[0],md5[1] );
+  }
+
+  r = hostDB.insert_block(folded_md5, NULL, 0);
+  r->md5_high = md5[1];
+  r->ip_timeout_interval = 0; // special value - no timeout.
+  r->ip_timestamp = hostdb_current_interval;
+  keys->push_back(md5);
+  return r;
+}
+
+int
+HostDBFileContinuation::insertEvent(int, void*) {
+  int n = HostFilePairs.size();
+  HostFilePair const& first = HostFilePairs[idx];
+  int last = idx + 1;
+  HostDBInfo* r = 0;
+
+  ink_assert(idx < n);
+
+  // Get the set of addresses for this name.
+  while (last < n && 0 == strcasecmp(first.name, HostFilePairs[last].name) && first.ip.family() == HostFilePairs[last].ip.family())
+    ++last;
+  // Address set is now (idx, last].
+
+  int k = last - idx; // # of addrs for this name
+  ink_assert(k > 0);
+
+  r = this->setDBEntry();
+  r->reverse_dns = false;
+  r->is_srv = false;
+  if (k > 1) {
+    // multiple entries, need round robin
+    int s = HostDBRoundRobin::size(k, false);
+    HostDBRoundRobin* rr_data = static_cast<HostDBRoundRobin*>(hostDB.alloc(&r->app.rr.offset, s));
+    if (rr_data) {
+      int dst = 0; // index of destination RR item.
+      for (int src = idx ; src < last ; ++src, ++dst ) {
+        HostDBInfo& item = rr_data->info[dst];
+        item.data.ip.assign(HostFilePairs[src].ip);
+        item.full = 1;
+        item.round_robin = false;
+        item.reverse_dns = false;
+        item.is_srv = false;
+        item.md5_high = r->md5_high;
+        item.md5_low = r->md5_low;
+        item.md5_low_low = r->md5_low_low;
+      }
+      r->round_robin = true;
+      rr_data->good = k;
+      rr_data->current = 0;
+    }
+  } else {
+    r->data.ip.assign(HostFilePairs[idx].ip);
+    r->round_robin = false;
+  }
+
+  if (last < n) { // more entries to process
+    // We create a new continuation rather than using this one to
+    // avoid mutex issues - it is critical that the continuation use
+    // the bucket mutex to guarantee a data lock for update. It's
+    // unclear if changing the mutex to this continuation will do that
+    // properly. Because we use a class allocator we should achieve
+    // efficient allocations quickly even for large host files.
+    this->scheduleUpdate(last, keys);
+  } else {
+    std::sort(keys->begin(), keys->end(), &CmpMD5);
+//    keys->qsort(&CmpMD5);
+    // Switch to removing dead entries.
+    this->scheduleRemove(keys->size() - 1, keys);
+  }
+  // This continuation is done.
+  this->destroy();
+  return EVENT_DONE;
+}
+
+int
+HostDBFileContinuation::removeEvent(int, void*) {
+  HostDBInfo* r;
+  uint64_t folded_md5 = fold_md5(md5);
+  Debug("hostdb", "Remove host file entry %" PRIx64 ".%" PRIx64, md5[0],md5[1] );
+  if (0 != (r = hostDB.lookup_block(folded_md5, 3)))
+    hostDB.delete_block(r);
+  this->scheduleRemove(idx, keys);
+  this->destroy();
+  return EVENT_DONE;
+}
+
+void
+HostDBFileContinuation::scheduleUpdate(int idx, Keys* keys) {
+  HostDBFileContinuation* c = hostDBFileContAllocator.alloc();
+  HostFilePair& pair = HostFilePairs[idx];
+  HostDBMD5 md5;
+
+  md5.set_host(pair.name, strlen(pair.name));
+  md5.db_mark = db_mark_for(pair.ip);
+  md5.refresh();
+
+  SET_CONTINUATION_HANDLER(c, &HostDBFileContinuation::insertEvent);
+  c->idx = idx;
+  c->name = pair.name;
+  c->keys = keys ? keys : new Keys;
+  c->md5 = md5.hash;
+  c->mutex = hostDB.lock_for_bucket(fold_md5(md5.hash) % hostDB.buckets);
+  eventProcessor.schedule_imm(c, ET_CALL, EVENT_IMMEDIATE, 0);
+
+}
+
+void
+HostDBFileContinuation::scheduleRemove(int idx, Keys* keys) {
+  bool targetp = false; // Have a valid target?
+  INK_MD5 md5;
+
+  // See if we have a target.
+  if (HostFileKeys.size() > 0) {
+    md5 = HostFileKeys.back();
+    HostFileKeys.pop_back();
+    targetp = true;
+    // Move backwards through the valid items and the previous
+    // keys, removing if we have a prev key that's bigger than
+    // the current valid key (which means it's not currently valid).
+    while (idx >= 0) {
+      if (CmpMD5((*keys)[idx], md5)) { // prev is not valid, remove
+        break;
+      } else if (CmpMD5(md5, (*keys)[idx])) {
+        --idx; // prev is smaller, skip current and keep checking
+      } else if (!HostFileKeys.empty()) {
+        md5 = HostFileKeys.back();
+        HostFileKeys.pop_back(); // match, move to next potential target
+        --idx;
+      } else { // ran out of things to remove.
+        targetp = false;
+        break;
+      }
+    }
+  }
+
+  if (targetp) {
+    HostDBFileContinuation* c = hostDBFileContAllocator.alloc();
+    SET_CONTINUATION_HANDLER(c, &HostDBFileContinuation::removeEvent);
+    c->md5 = md5;
+    c->idx = idx;
+    c->keys = keys;
+    c->mutex = hostDB.lock_for_bucket(fold_md5(c->md5) % hostDB.buckets);
+    eventProcessor.schedule_imm(c, ET_CALL, EVENT_IMMEDIATE, 0);
+  } else {
+    self::finish(keys);
+  }
+}
+
+void
+ParseHostLine(char* l) {
+  Tokenizer elts(" \t");
+  int n_elts = elts.Initialize(l, SHARE_TOKS);
+  // Elements should be the address then a list of host names.
+  // Don't use RecHttpLoadIp because the address *must* be literal.
+  HostFilePair item;
+  if (n_elts > 1 && 0 == item.ip.load(elts[0]) && !item.ip.isLoopback()) {
+    for ( int i = 1 ; i < n_elts ; ++i ) {
+      item.name = elts[i];
+      HostFilePairs.push_back(item);
+    }
+  }
+}
+
+
+void
+ParseHostFile(char const* path) {
+  bool success = false;
+  // Test and set for update in progress.
+  if (0 != ink_atomic_swap(&HostDBFileUpdateActive,1)) {
+    Debug("hostdb", "Skipped load of host file because update already in progress");
+    return;
+  }
+  Debug("hostdb", "Loading host file '%s'", path);
+
+  ats_scoped_fd fd(open(path, O_RDONLY));
+  if (fd >= 0) {
+    struct stat info;
+    if (0 == fstat(fd, &info)) {
+      // +1 in case no terminating newline
+      int64_t size = info.st_size+1;
+      HostFileText = static_cast<char*>(ats_malloc(size));
+      if (HostFileText) {
+        char* base = HostFileText;
+        char* limit;
+
+        size = read(fd, HostFileText, info.st_size);
+        limit = HostFileText + size;
+        *limit = 0;
+
+        // We need to get a list of all name/addr pairs so that we can
+        // group names for round robin records. Also note that the
+        // pairs have pointer back in to the text storage for the file
+        // so we need to keep that until we're done with @a pairs.
+        while (base < limit) {
+          char *spot = strchr(base, '\n');
+
+          // terminate the line.
+          if (0 == spot) spot = limit; // no trailing EOL, grab remaining
+          else *spot = 0;
+
+          while (base < spot && isspace(*base)) ++base; // skip leading ws
+          if (*base != '#' && base < spot) // non-empty non-comment line
+            ParseHostLine(base);
+          base = spot + 1;
+        }
+
+        hostdb_hostfile_update_timestamp = hostdb_current_interval;
+        success = true;
+        if (!HostFilePairs.empty()) {
+          // Need to sort by name so multiple address hosts are
+          // contiguous.
+          std::sort(HostFilePairs.begin(), HostFilePairs.end(), &CmpHostFilePair);
+          HostDBFileContinuation::scheduleUpdate(0);
+        } else if (!HostFileKeys.empty()) {
+          HostDBFileContinuation::scheduleRemove(-1, 0);
+        } else {
+          // Nothing in new data, nothing in old data, just clean up.
+          HostDBFileContinuation::finish(0);
+        }
+      }
+    }
+  }
+  if (!success)
+    HostDBFileUpdateActive = 0;
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/iocore/hostdb/I_HostDBProcessor.h
----------------------------------------------------------------------
diff --git a/iocore/hostdb/I_HostDBProcessor.h b/iocore/hostdb/I_HostDBProcessor.h
index be48457..c26a403 100644
--- a/iocore/hostdb/I_HostDBProcessor.h
+++ b/iocore/hostdb/I_HostDBProcessor.h
@@ -176,18 +176,17 @@ struct HostDBInfo
 
   int ip_time_remaining()
   {
-    return (int) ip_timeout_interval - (int) ((hostdb_current_interval - ip_timestamp) & 0x7FFFFFFF);
+    return static_cast<int>(ip_timeout_interval) - static_cast<int>(this->ip_interval());
   }
 
   bool is_ip_stale() {
-    if (ip_timeout_interval >= 2 * hostdb_ip_stale_interval)
-      return ip_interval() >= hostdb_ip_stale_interval;
-    else
-      return false;
+    return ip_timeout_interval >= 2 * hostdb_ip_stale_interval &&
+      ip_interval() >= hostdb_ip_stale_interval
+      ;
   }
 
   bool is_ip_timeout() {
-    return ip_interval() >= ip_timeout_interval;
+    return ip_timeout_interval && ip_interval() >= ip_timeout_interval;
   }
 
   bool is_ip_fail_timeout() {
@@ -228,8 +227,9 @@ struct HostDBInfo
   } data;
 
   unsigned int ip_timestamp;
-  // limited to 0x1FFFFF (24 days)
-  unsigned int ip_timeout_interval:31;
+  // limited to HOST_DB_MAX_TTL (0x1FFFFF, 24 days)
+  // if this is 0 then no timeout.
+  unsigned int ip_timeout_interval;
 
   unsigned int full:1;
   unsigned int backed:1;        // duplicated in lower level

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/iocore/hostdb/P_HostDBProcessor.h
----------------------------------------------------------------------
diff --git a/iocore/hostdb/P_HostDBProcessor.h b/iocore/hostdb/P_HostDBProcessor.h
index f3ee41e..e67b0f8 100644
--- a/iocore/hostdb/P_HostDBProcessor.h
+++ b/iocore/hostdb/P_HostDBProcessor.h
@@ -416,17 +416,24 @@ struct HostDBMD5 {
   char const* host_name; ///< Host name.
   int host_len; ///< Length of @a _host_name
   IpAddr ip; ///< IP address.
-  int port; ///< IP port (host order).
+  in_port_t port; ///< IP port (host order).
   /// DNS server. Not strictly part of the MD5 data but
   /// it's both used by @c HostDBContinuation and provides access to
   /// MD5 data. It's just handier to store it here for both uses.
   DNSServer* dns_server;
+  SplitDNS* pSD; ///< Hold the container for @a dns_server.
   HostDBMark db_mark; ///< Mark / type of record.
 
   /// Default constructor.
   HostDBMD5();
+  /// Destructor.
+  ~HostDBMD5();
   /// Recompute and update the MD5 hash.
   void refresh();
+  /** Assign a hostname.
+      This updates the split DNS data as well.
+  */
+  self& set_host(char const* name, int len);
 };
 
 //
@@ -458,7 +465,7 @@ struct HostDBContinuation: public Continuation
   //  int namelen;
   char md5_host_name_store[MAXDNAME+1]; // used as backing store for @a md5
   char srv_target_name[MAXDNAME];
-  void *m_pDS;
+  //  void *m_pDS;
   Action *pending_action;
 
   unsigned int missing:1;

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/lib/ts/ink_inet.cc
----------------------------------------------------------------------
diff --git a/lib/ts/ink_inet.cc b/lib/ts/ink_inet.cc
index 0104981..d84fea1 100644
--- a/lib/ts/ink_inet.cc
+++ b/lib/ts/ink_inet.cc
@@ -400,11 +400,8 @@ operator == (IpAddr const& lhs, sockaddr const* rhs) {
       - -1 if @a lhs is less than @a rhs.
       - 0 if @a lhs is identical to @a rhs.
       - 1 if @a lhs is greater than @a rhs.
-
-    @internal This looks like a lot of code for an inline but I think it
-    should compile down to something reasonable.
 */
-inline int
+int
 IpAddr::cmp(self const& that) const {
   int zret = 0;
   uint16_t rtype = that._family;
@@ -433,8 +430,7 @@ IpAddr::cmp(self const& that) const {
   } else if (AF_INET == rtype || AF_INET6 == rtype) {
     // ltype is non-IP so it's less than either IP type.
     zret = -1;
-  } else {
-    // Both types are non-IP so they're equal.
+  } else { // Both types are non-IP so they're equal.
     zret = 0;
   }
 

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/lib/ts/ink_inet.h
----------------------------------------------------------------------
diff --git a/lib/ts/ink_inet.h b/lib/ts/ink_inet.h
index 388ce3c..5e4c5d4 100644
--- a/lib/ts/ink_inet.h
+++ b/lib/ts/ink_inet.h
@@ -1176,6 +1176,8 @@ struct IpAddr {
   self& invalidate() { _family = AF_UNSPEC; return *this; }
   /// Test for multicast
   bool isMulticast() const;
+  /// Test for loopback
+  bool isLoopback() const;
 
   uint16_t _family; ///< Protocol family.
   /// Address data.
@@ -1214,7 +1216,14 @@ IpAddr::isCompatibleWith(self const& that) {
 
 inline bool IpAddr::isIp4() const { return AF_INET == _family; }
 inline bool IpAddr::isIp6() const { return AF_INET6 == _family; }
-  /// Assign sockaddr storage.
+
+inline bool IpAddr::isLoopback() const {
+  return (AF_INET == _family && 0x7F == _addr._byte[0]) ||
+    (AF_INET6 == _family && IN6_IS_ADDR_LOOPBACK(&_addr._ip6))
+    ;
+}
+
+/// Assign sockaddr storage.
 inline IpAddr&
 IpAddr::assign(sockaddr const* addr) {
   if (addr) {

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/mgmt/RecordsConfig.cc
----------------------------------------------------------------------
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 80b7647..0c71255 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1037,6 +1037,10 @@ static const RecordElement RecordsConfig[] =
   //       # how often should the hostdb be synced (seconds)
   {RECT_CONFIG, "proxy.config.cache.hostdb.sync_frequency", RECD_INT, "120", RECU_DYNAMIC, RR_NULL, RECC_NULL, NULL, RECA_NULL}
   ,
+  {RECT_CONFIG, "proxy.config.hostdb.host_file.path", RECD_STRING, "/etc/hosts", RECU_DYNAMIC, RR_NULL, RECC_STR, NULL, RECA_NULL}
+  ,
+  {RECT_CONFIG, "proxy.config.hostdb.host_file.interval", RECD_INT, "86400", RECU_DYNAMIC, RR_NULL, RECC_NULL, NULL, RECA_NULL}
+  ,
 
   //##########################################################################
   //#

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a2affb99/proxy/http/HttpSM.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index 7c13ebb..e2e6080 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -4016,6 +4016,7 @@ HttpSM::do_hostdb_lookup()
     }
 
     HostDBProcessor::Options opt;
+
     opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing)
       ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS
       : HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD