You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ma...@apache.org on 2022/09/23 01:01:56 UTC

[trafficserver] branch master updated: Support reverse lookups from the host file. (#9043)

This is an automated email from the ASF dual-hosted git repository.

maskit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 4c0600dc4 Support reverse lookups from the host file. (#9043)
4c0600dc4 is described below

commit 4c0600dc4f7e7222dead9e84a6b3af897930c44b
Author: Chris McFarlen <ch...@mcfarlen.us>
AuthorDate: Thu Sep 22 20:01:50 2022 -0500

    Support reverse lookups from the host file. (#9043)
    
    * Support reverse lookups from the host file.
    
    Refactor host file code to make it more testable and unify lookups
    Add unit test for HostFile
    
    * More refactoring
    
    * link libinkevent
    
    * also link tscpputil
    
    * use reverse map in intermediate parse data to use the file order for a stable view of the reverse map
    
    Co-authored-by: Chris McFarlen <cm...@apple.com>
---
 iocore/hostdb/HostDB.cc           | 186 ++++----------------------------------
 iocore/hostdb/HostDBInfo.cc       |  84 +++++++++++++++++
 iocore/hostdb/HostFile.cc         | 150 ++++++++++++++++++++++++++++++
 iocore/hostdb/HostFile.h          |  51 +++++++++++
 iocore/hostdb/Makefile.am         |  23 ++++-
 iocore/hostdb/P_HostDBProcessor.h |  25 +++--
 iocore/hostdb/test_HostFile.cc    | 164 +++++++++++++++++++++++++++++++++
 7 files changed, 504 insertions(+), 179 deletions(-)

diff --git a/iocore/hostdb/HostDB.cc b/iocore/hostdb/HostDB.cc
index 7481d85a5..79c55bff2 100644
--- a/iocore/hostdb/HostDB.cc
+++ b/iocore/hostdb/HostDB.cc
@@ -29,6 +29,7 @@
 #include "tscore/ts_file.h"
 #include "tscore/ink_apidefs.h"
 #include "tscore/bwf_std_format.h"
+#include "HostFile.h"
 
 #include <utility>
 #include <vector>
@@ -74,27 +75,6 @@ ClassAllocator<HostDBContinuation> hostDBContAllocator("hostDBContAllocator");
 
 namespace
 {
-/** Assign raw storage to an @c IpAddr
- *
- * @param ip Destination.
- * @param af IP family.
- * @param ptr Raw data for an address of family @a af.
- */
-void
-ip_addr_set(IpAddr &ip,     ///< Target storage.
-            uint8_t af,     ///< Address format.
-            void const *ptr ///< Raw address data
-)
-{
-  if (AF_INET6 == af) {
-    ip = *static_cast<in6_addr const *>(ptr);
-  } else if (AF_INET == af) {
-    ip = *static_cast<in_addr_t const *>(ptr);
-  } else {
-    ip.invalidate();
-  }
-}
-
 unsigned int
 HOSTDB_CLIENT_IP_HASH(sockaddr const *lhs, IpAddr const &rhs)
 {
@@ -222,41 +202,7 @@ HostDB_Config_Init()
 
 HostDBCache hostDB;
 
-void ParseHostFile(ts::file::path const &path, ts_seconds interval);
-
-auto
-HostDBInfo::assign(sa_family_t af, void const *addr) -> self_type &
-{
-  type = HostDBType::ADDR;
-  ip_addr_set(data.ip, af, addr);
-  return *this;
-}
-
-auto
-HostDBInfo::assign(IpAddr const &addr) -> self_type &
-{
-  type    = HostDBType::ADDR;
-  data.ip = addr;
-  return *this;
-}
-
-auto
-HostDBInfo::assign(SRV const *srv, char const *name) -> self_type &
-{
-  type                  = HostDBType::SRV;
-  data.srv.srv_weight   = srv->weight;
-  data.srv.srv_priority = srv->priority;
-  data.srv.srv_port     = srv->port;
-  data.srv.key          = srv->key;
-  data.srv.srv_offset   = reinterpret_cast<char const *>(this) - name;
-  return *this;
-}
-
-char const *
-HostDBInfo::srvname() const
-{
-  return data.srv.srv_offset ? reinterpret_cast<char const *>(this) + data.srv.srv_offset : nullptr;
-}
+void UpdateHostsFile(ts::file::path const &path, ts_seconds interval);
 
 static inline bool
 is_addr_valid(uint8_t af, ///< Address family (format of data)
@@ -363,8 +309,6 @@ HostDBHash::refresh()
   ctx.finalize(hash);
 }
 
-HostDBHash::HostDBHash() {}
-
 HostDBHash::~HostDBHash()
 {
   if (pSD) {
@@ -386,7 +330,7 @@ HostDBCache::is_pending_dns_for_hash(const CryptoHash &hash)
   return false;
 }
 
-std::shared_ptr<HostFileMap>
+std::shared_ptr<HostFile>
 HostDBCache::acquire_host_file()
 {
   std::shared_lock lock(host_file_mutex);
@@ -1467,21 +1411,16 @@ HostDBContinuation::do_dns()
       hostdb_cont_free(this);
       return;
     }
+  }
 
-    // If looking for an IPv4 or IPv6 address, check the host file.
-    if (hash.db_mark == HOSTDB_MARK_IPV6 || hash.db_mark == HOSTDB_MARK_IPV4) {
-      if (auto static_hosts = hostDB.acquire_host_file(); static_hosts) {
-        if (auto spot = static_hosts->find(hash.host_name); spot != static_hosts->end()) {
-          HostDBRecord::Handle r = (hash.db_mark == HOSTDB_MARK_IPV4) ? spot->second.record_4 : spot->second.record_6;
-          // Set the TTL based on how often we stat() the host file
-          if (r && action.continuation) {
-            r = lookup_done(hash.host_name, hostdb_hostfile_check_interval, nullptr, r);
-            reply_to_cont(action.continuation, r.get());
-            hostdb_cont_free(this);
-            return;
-          }
-        }
-      }
+  // Check if this can be fulfilled by the host file
+  if (auto static_hosts = hostDB.acquire_host_file(); static_hosts) {
+    if (auto r = static_hosts->lookup(hash); r && action.continuation) {
+      // Set the TTL based on how often we stat() the host file
+      r = lookup_done(hash.host_name, static_hosts->ttl, nullptr, r);
+      reply_to_cont(action.continuation, r.get());
+      hostdb_cont_free(this);
+      return;
     }
   }
 
@@ -1566,7 +1505,7 @@ HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS
     }
     if (update_p) {
       Debug("hostdb", "%s", ts::bwprint(dbg, R"(Updating from host file "{}")", hostdb_hostfile_path).c_str());
-      ParseHostFile(hostdb_hostfile_path, hostdb_hostfile_check_interval);
+      UpdateHostsFile(hostdb_hostfile_path, hostdb_hostfile_check_interval);
     }
   }
 
@@ -1994,13 +1933,6 @@ ink_hostdb_init(ts::ModuleVersion v)
   ts_host_res_global_init();
 }
 
-/// Pair of IP address and host name from a host file.
-struct HostFilePair {
-  using self = HostFilePair;
-  IpAddr ip;
-  const char *name;
-};
-
 struct HostDBFileContinuation : public Continuation {
   using self = HostDBFileContinuation;
   using Keys = std::vector<CryptoHash>;
@@ -2035,106 +1967,24 @@ HostDBFileContinuation::destroy()
 // globals.
 std::atomic<bool> HostDBFileUpdateActive{false};
 
-/* Container for temporarily holding data from the host file. For each FQDN there is a vector of IPv4
- * and IPv6 addresses. These are used to generate the HostDBRecord instances that are stored persistently.
- */
-using HostAddrMap = std::unordered_map<std::string_view, std::tuple<std::vector<IpAddr>, std::vector<IpAddr>>>;
-
-namespace
-{
-constexpr unsigned IPV4_IDX = 0;
-constexpr unsigned IPV6_IDX = 1;
-} // namespace
-
-static void
-ParseHostLine(TextView line, HostAddrMap &map)
-{
-  // Elements should be the address then a list of host names.
-  TextView addr_text = line.take_prefix_if(&isspace);
-  IpAddr addr;
-
-  // Don't use RecHttpLoadIp because the address *must* be literal.
-  if (TS_SUCCESS != addr.load(addr_text)) {
-    return;
-  }
-
-  while (!line.ltrim_if(&isspace).empty()) {
-    TextView name = line.take_prefix_if(&isspace);
-    if (addr.isIp6()) {
-      std::get<IPV6_IDX>(map[name]).push_back(addr);
-    } else if (addr.isIp4()) {
-      std::get<IPV4_IDX>(map[name]).push_back(addr);
-    }
-  }
-}
-
 void
-ParseHostFile(ts::file::path const &path, ts_seconds hostdb_hostfile_check_interval_parse)
+UpdateHostsFile(ts::file::path const &path, ts_seconds interval)
 {
-  std::shared_ptr<HostFileMap> map;
-
   // Test and set for update in progress.
   bool flag = false;
   if (!HostDBFileUpdateActive.compare_exchange_strong(flag, true)) {
     Debug("hostdb", "Skipped load of host file because update already in progress");
     return;
   }
-  Debug_bw("hostdb", R"(Loading host file "{}")", path);
-
-  if (!path.empty()) {
-    std::error_code ec;
-    std::string content = ts::file::load(path, ec);
-    if (!ec) {
-      HostAddrMap addr_map;
-      TextView text{content};
-      while (text) {
-        auto line = text.take_prefix_at('\n').ltrim_if(&isspace);
-        if (line.empty() || '#' == *line) {
-          continue;
-        }
-        ParseHostLine(line, addr_map);
-      }
-      // @a map should be loaded with all of the data, create the records.
-      map = std::make_shared<HostFileMap>();
-      // Common loading function for creating a record from the address vector.
-      auto loader = [](TextView key, std::vector<IpAddr> const &v) -> HostDBRecord::Handle {
-        HostDBRecord::Handle record{HostDBRecord::alloc(key, v.size())};
-        record->af_family = v.front().family(); // @a v is presumed family homogenous
-        auto rr_info      = record->rr_info();
-        auto spot         = v.begin();
-        for (auto &item : rr_info) {
-          item.assign(*spot++);
-        }
-        return record;
-      };
-      // Walk the temporary map and create the corresponding records for the persistent map.
-      for (auto const &[key, value] : addr_map) {
-        // Bit of subtlety to be able to search records with a view and not a string - the key
-        // must point at stable memory for the name, which is available in the record itself.
-        // Therefore the lookup for adding the record must be done using a view based in the record.
-        // It doesn't matter if it's the IPv4 or IPv6 record that's used, both are stable and equal
-        // to each other.
-        // IPv4
-        if (auto const &v = std::get<IPV4_IDX>(value); v.size() > 0) {
-          auto r                          = loader(key, v);
-          (*map)[r->name_view()].record_4 = r;
-        }
-        // IPv6
-        if (auto const &v = std::get<IPV6_IDX>(value); v.size() > 0) {
-          auto r                          = loader(key, v);
-          (*map)[r->name_view()].record_6 = r;
-        }
-      }
 
-      hostdb_hostfile_update_timestamp = hostdb_current_timestamp;
-    }
-  }
+  std::shared_ptr<HostFile> hf = ParseHostFile(path, interval);
 
   // Swap the pointer
-  if (map) {
+  if (hf) {
     std::unique_lock lock(hostDB.host_file_mutex);
-    hostDB.host_file = map;
+    hostDB.host_file = hf;
   }
+  hostdb_hostfile_update_timestamp = hostdb_current_timestamp;
   // Mark this one as completed, so we can allow another update to happen
   HostDBFileUpdateActive = false;
 }
diff --git a/iocore/hostdb/HostDBInfo.cc b/iocore/hostdb/HostDBInfo.cc
new file mode 100644
index 000000000..4a4388ff6
--- /dev/null
+++ b/iocore/hostdb/HostDBInfo.cc
@@ -0,0 +1,84 @@
+/** @file
+
+  HostDBInfo implementation
+
+  @section license License
+
+    Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <sys/socket.h>
+
+#include "I_HostDBProcessor.h"
+
+namespace
+{
+/** Assign raw storage to an @c IpAddr
+ *
+ * @param ip Destination.
+ * @param af IP family.
+ * @param ptr Raw data for an address of family @a af.
+ */
+void
+ip_addr_set(IpAddr &ip,     ///< Target storage.
+            uint8_t af,     ///< Address format.
+            void const *ptr ///< Raw address data
+)
+{
+  if (AF_INET6 == af) {
+    ip = *static_cast<in6_addr const *>(ptr);
+  } else if (AF_INET == af) {
+    ip = *static_cast<in_addr_t const *>(ptr);
+  } else {
+    ip.invalidate();
+  }
+}
+} // namespace
+
+auto
+HostDBInfo::assign(sa_family_t af, void const *addr) -> self_type &
+{
+  type = HostDBType::ADDR;
+  ip_addr_set(data.ip, af, addr);
+  return *this;
+}
+
+auto
+HostDBInfo::assign(IpAddr const &addr) -> self_type &
+{
+  type    = HostDBType::ADDR;
+  data.ip = addr;
+  return *this;
+}
+
+auto
+HostDBInfo::assign(SRV const *srv, char const *name) -> self_type &
+{
+  type                  = HostDBType::SRV;
+  data.srv.srv_weight   = srv->weight;
+  data.srv.srv_priority = srv->priority;
+  data.srv.srv_port     = srv->port;
+  data.srv.key          = srv->key;
+  data.srv.srv_offset   = reinterpret_cast<char const *>(this) - name;
+  return *this;
+}
+
+char const *
+HostDBInfo::srvname() const
+{
+  return data.srv.srv_offset ? reinterpret_cast<char const *>(this) + data.srv.srv_offset : nullptr;
+}
diff --git a/iocore/hostdb/HostFile.cc b/iocore/hostdb/HostFile.cc
new file mode 100644
index 000000000..42af0c26f
--- /dev/null
+++ b/iocore/hostdb/HostFile.cc
@@ -0,0 +1,150 @@
+/** @file
+
+  HostFile class for processing a hosts file
+
+  @section license License
+
+    Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "HostFile.h"
+#include "P_HostDBProcessor.h"
+
+/* Container for temporarily holding data from the host file. For each FQDN there is a vector of IPv4
+ * and IPv6 addresses. These are used to generate the HostDBRecord instances that are stored persistently.
+ */
+using HostAddrMap = std::unordered_map<std::string_view, std::tuple<std::vector<IpAddr>, std::vector<IpAddr>>>;
+using AddrHostMap = std::unordered_map<IpAddr, std::string_view, IpAddr::Hasher>;
+
+namespace
+{
+constexpr unsigned IPV4_IDX = 0;
+constexpr unsigned IPV6_IDX = 1;
+} // namespace
+
+static void
+ParseHostLine(ts::TextView line, HostAddrMap &map, AddrHostMap &rmap)
+{
+  // Elements should be the address then a list of host names.
+  ts::TextView addr_text = line.take_prefix_if(&isspace);
+  IpAddr addr;
+
+  // Don't use RecHttpLoadIp because the address *must* be literal.
+  if (TS_SUCCESS != addr.load(addr_text)) {
+    return;
+  }
+
+  while (!line.ltrim_if(&isspace).empty()) {
+    ts::TextView name = line.take_prefix_if(&isspace);
+    if (addr.isIp6()) {
+      std::get<IPV6_IDX>(map[name]).push_back(addr);
+    } else if (addr.isIp4()) {
+      std::get<IPV4_IDX>(map[name]).push_back(addr);
+    }
+    // use insert here so only the first mapping in the file is used to provide a stable view between reloads
+    rmap.insert(std::pair(addr, name));
+  }
+}
+
+HostDBRecord::Handle
+HostFile::lookup(const HostDBHash &hash)
+{
+  HostDBRecord::Handle result;
+
+  // If looking for an IPv4 or IPv6 address, check the host file.
+  if (hash.db_mark == HOSTDB_MARK_IPV6 || hash.db_mark == HOSTDB_MARK_IPV4) {
+    if (auto spot = forward.find(hash.host_name); spot != forward.end()) {
+      result = (hash.db_mark == HOSTDB_MARK_IPV4) ? spot->second.record_4 : spot->second.record_6;
+    }
+  } else if (hash.db_mark != HOSTDB_MARK_SRV) {
+    if (auto spot = reverse.find(hash.ip); spot != reverse.end()) {
+      result = spot->second;
+    }
+  }
+
+  return result;
+}
+
+std::shared_ptr<HostFile>
+ParseHostFile(ts::file::path const &path, ts_seconds interval)
+{
+  std::shared_ptr<HostFile> hf;
+
+  Debug_bw("hostdb", R"(Loading host file "{}")", path);
+
+  if (!path.empty()) {
+    std::error_code ec;
+    std::string content = ts::file::load(path, ec);
+    if (!ec) {
+      HostAddrMap addr_map;
+      AddrHostMap host_map;
+      ts::TextView text{content};
+      while (text) {
+        auto line = text.take_prefix_at('\n').ltrim_if(&isspace);
+        if (line.empty() || '#' == *line) {
+          continue;
+        }
+        ParseHostLine(line, addr_map, host_map);
+      }
+      // @a map should be loaded with all of the data, create the records.
+      hf = std::make_shared<HostFile>(interval);
+      // Common loading function for creating a record from the address vector.
+      auto loader = [](ts::TextView key, std::vector<IpAddr> const &v) -> HostDBRecord::Handle {
+        HostDBRecord::Handle record{HostDBRecord::alloc(key, v.size())};
+        record->af_family = v.front().family(); // @a v is presumed family homogenous
+        auto rr_info      = record->rr_info();
+        auto spot         = v.begin();
+        for (auto &item : rr_info) {
+          item.assign(*spot++);
+        }
+        return record;
+      };
+      // Walk the temporary map and create the corresponding records for the persistent map.
+      for (auto const &[key, value] : addr_map) {
+        // Bit of subtlety to be able to search records with a view and not a string - the key
+        // must point at stable memory for the name, which is available in the record itself.
+        // Therefore the lookup for adding the record must be done using a view based in the record.
+        // It doesn't matter if it's the IPv4 or IPv6 record that's used, both are stable and equal
+        // to each other.
+        // IPv4
+        if (auto const &v = std::get<IPV4_IDX>(value); v.size() > 0) {
+          auto r                               = loader(key, v);
+          hf->forward[r->name_view()].record_4 = r;
+        }
+        // IPv6
+        if (auto const &v = std::get<IPV6_IDX>(value); v.size() > 0) {
+          auto r                               = loader(key, v);
+          hf->forward[r->name_view()].record_6 = r;
+        }
+      }
+
+      // Walk the temporary reverse map and create the reverse index.
+      for (auto const &[ip, host] : host_map) {
+        auto lup = hf->forward.find(host);
+        if (lup != hf->forward.end()) {
+          if (ip.isIp4()) {
+            hf->reverse[ip] = lup->second.record_4;
+          } else {
+            hf->reverse[ip] = lup->second.record_6;
+          }
+        }
+      }
+    }
+  }
+
+  return hf;
+}
diff --git a/iocore/hostdb/HostFile.h b/iocore/hostdb/HostFile.h
new file mode 100644
index 000000000..48a614f08
--- /dev/null
+++ b/iocore/hostdb/HostFile.h
@@ -0,0 +1,51 @@
+/** @file
+
+  HostFile class for processing a hosts file
+
+  @section license License
+
+    Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include "I_HostDBProcessor.h"
+#include "tscpp/util/TextView.h"
+#include "tscore/ts_file.h"
+
+#include <memory>
+#include <unordered_map>
+
+struct HostFileRecord {
+  HostDBRecord::Handle record_4;
+  HostDBRecord::Handle record_6;
+};
+
+struct HostFile {
+  using HostFileForwardMap = std::unordered_map<ts::TextView, HostFileRecord, std::hash<std::string_view>>;
+  using HostFileReverseMap = std::unordered_map<IpAddr, HostDBRecord::Handle, IpAddr::Hasher>;
+
+  HostFile(ts_seconds ttl) : ttl(ttl) {}
+
+  HostDBRecord::Handle lookup(const HostDBHash &hash);
+
+  ts_seconds ttl;
+  HostFileForwardMap forward;
+  HostFileReverseMap reverse;
+};
+
+std::shared_ptr<HostFile> ParseHostFile(ts::file::path const &path, ts_seconds interval);
diff --git a/iocore/hostdb/Makefile.am b/iocore/hostdb/Makefile.am
index e0391d759..681c0988d 100644
--- a/iocore/hostdb/Makefile.am
+++ b/iocore/hostdb/Makefile.am
@@ -40,10 +40,13 @@ libinkhostdb_a_SOURCES = \
 	P_HostDBProcessor.h \
 	P_RefCountCache.h \
 	P_RefCountCacheSerializer.h \
-	RefCountCache.cc
+	RefCountCache.cc \
+	HostFile.h \
+	HostFile.cc \
+	HostDBInfo.cc
 
 TESTS = $(check_PROGRAMS)
-check_PROGRAMS = test_RefCountCache
+check_PROGRAMS = test_RefCountCache test_HostFile
 
 test_RefCountCache_SOURCES = \
 	test_RefCountCache.cc
@@ -82,6 +85,22 @@ test_RefCountCache_LDFLAGS = $(test_LD_FLAGS)
 
 test_RefCountCache_LDADD = $(test_LD_ADD)
 
+test_HostFile_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(abs_top_srcdir)/tests/include \
+	-I$(abs_top_srcdir)/proxy/http/remap
+
+test_HostFile_LDADD = \
+	$(top_builddir)/src/tscore/libtscore.la \
+	$(top_builddir)/src/tscpp/util/libtscpputil.la \
+	$(top_builddir)/iocore/eventsystem/libinkevent.a
+
+test_HostFile_SOURCES = \
+	test_HostFile.cc \
+	HostFile.h \
+	HostFile.cc \
+	HostDBInfo.cc
+
 include $(top_srcdir)/build/tidy.mk
 
 clang-tidy-local: $(DIST_SOURCES)
diff --git a/iocore/hostdb/P_HostDBProcessor.h b/iocore/hostdb/P_HostDBProcessor.h
index a373a08a5..4549496cd 100644
--- a/iocore/hostdb/P_HostDBProcessor.h
+++ b/iocore/hostdb/P_HostDBProcessor.h
@@ -28,9 +28,12 @@
 #pragma once
 
 #include <shared_mutex>
+#include <unordered_map>
 
 #include "I_HostDBProcessor.h"
+#include "P_RefCountCache.h"
 #include "tscore/TsBuffer.h"
+#include "tscore/ts_file.h"
 
 //
 // Data
@@ -152,12 +155,7 @@ extern RecRawStatBlock *hostdb_rsb;
 
 #define HOSTDB_DECREMENT_THREAD_DYN_STAT(_s, _t) RecIncrRawStatSum(hostdb_rsb, _t, (int)_s, -1);
 
-struct HostFileRecord {
-  HostDBRecord::Handle record_4;
-  HostDBRecord::Handle record_6;
-};
-
-using HostFileMap = std::unordered_map<ts::TextView, HostFileRecord, std::hash<std::string_view>>;
+struct HostFile;
 
 //
 // HostDBCache (Private)
@@ -165,7 +163,7 @@ using HostFileMap = std::unordered_map<ts::TextView, HostFileRecord, std::hash<s
 struct HostDBCache {
   int start(int flags = 0);
   // Map to contain all of the host file overrides, initialize it to empty
-  std::shared_ptr<HostFileMap> host_file;
+  std::shared_ptr<HostFile> host_file;
   std::shared_mutex host_file_mutex;
 
   // TODO: make ATS call a close() method or something on shutdown (it does nothing of the sort today)
@@ -178,13 +176,16 @@ struct HostDBCache {
   HostDBCache();
   bool is_pending_dns_for_hash(const CryptoHash &hash);
 
-  std::shared_ptr<HostFileMap> acquire_host_file();
+  std::shared_ptr<HostFile> acquire_host_file();
 };
 
 //
 // Types
 //
 
+struct DNSServer;
+struct SplitDNS;
+
 /** Container for a hash and its dependent data.
     This handles both the host name and raw address cases.
 */
@@ -204,7 +205,7 @@ struct HostDBHash {
   HostDBMark db_mark    = HOSTDB_MARK_GENERIC; ///< Mark / type of record.
 
   /// Default constructor.
-  HostDBHash();
+  HostDBHash() = default;
   /// Destructor.
   ~HostDBHash();
   /// Recompute and update the hash.
@@ -274,6 +275,12 @@ struct HostDBContinuation : public Continuation {
     return hash.db_mark == HOSTDB_MARK_SRV;
   }
 
+  bool
+  is_reverse()
+  {
+    return !is_byname() && !is_srv();
+  }
+
   Ptr<HostDBRecord>
   lookup_done(const char *query_name, ts_seconds answer_ttl, SRVHosts *s = nullptr, Ptr<HostDBRecord> record = Ptr<HostDBRecord>{})
   {
diff --git a/iocore/hostdb/test_HostFile.cc b/iocore/hostdb/test_HostFile.cc
new file mode 100644
index 000000000..4e1d61783
--- /dev/null
+++ b/iocore/hostdb/test_HostFile.cc
@@ -0,0 +1,164 @@
+/** @file
+
+  Test HostFile
+
+  @section license License
+
+    Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
+
+#include "HostFile.h"
+#include "P_HostDBProcessor.h"
+
+const std::string_view hosts_data = "127.0.0.1 localhost\n::1 localhost\n1.2.3.4  host1\n4.3.2.1 host2 host3\n"_sv;
+
+void
+spit(const ts::file::path &p, std::string_view data)
+{
+  std::ofstream f(p.c_str(), std::ios::trunc);
+  f.write(data.data(), data.size());
+  f.close();
+}
+
+TEST_CASE("HostFile", "[hostdb]")
+{
+  auto tmp = ts::file::temp_directory_path();
+  ts::LocalBufferWriter<1024> w;
+  w.print("{}/localhost.{}", tmp, ::getpid());
+
+  auto hostfilepath = ts::file::path(w.view());
+
+  spit(hostfilepath, hosts_data);
+
+  auto hf = ParseHostFile(hostfilepath, ts_seconds(3600));
+
+  REQUIRE(hf);
+
+  SECTION("reverse lookup localhost v4")
+  {
+    HostDBHash h;
+    h.ip.load("127.0.0.1");
+    h.db_mark = HOSTDB_MARK_GENERIC;
+
+    auto result = hf->lookup(h);
+
+    REQUIRE(result);
+    REQUIRE(result->name_view() == "localhost");
+  }
+
+  SECTION("reverse lookup localhost v6")
+  {
+    HostDBHash h;
+    h.ip.load("::1");
+    h.db_mark = HOSTDB_MARK_GENERIC;
+
+    auto result = hf->lookup(h);
+
+    REQUIRE(result);
+    REQUIRE(result->name_view() == "localhost");
+  }
+
+  SECTION("reverse lookup host v4")
+  {
+    HostDBHash h;
+    h.ip.load("4.3.2.1");
+    h.db_mark = HOSTDB_MARK_GENERIC;
+
+    auto result = hf->lookup(h);
+
+    REQUIRE(result);
+    REQUIRE(result->name_view() == "host2");
+  }
+
+  SECTION("forward lookup localhost v4")
+  {
+    HostDBHash h;
+    h.host_name = "localhost"_sv;
+    h.db_mark   = HOSTDB_MARK_IPV4;
+
+    auto result = hf->lookup(h);
+
+    REQUIRE(result);
+    REQUIRE(result->name_view() == h.host_name);
+  }
+
+  SECTION("forward lookup localhost v6")
+  {
+    HostDBHash h;
+    h.host_name = "localhost"_sv;
+    h.db_mark   = HOSTDB_MARK_IPV6;
+
+    auto result = hf->lookup(h);
+
+    REQUIRE(result);
+    REQUIRE(result->name_view() == h.host_name);
+  }
+
+  SECTION("forward lookup host v6")
+  {
+    HostDBHash h;
+    h.host_name = "host1"_sv;
+    h.db_mark   = HOSTDB_MARK_IPV4;
+
+    auto result = hf->lookup(h);
+
+    REQUIRE(result);
+    REQUIRE(result->name_view() == h.host_name);
+  }
+}
+
+// NOTE(cmcfarlen): need this destructor defined so we don't have to link in the entire project for this test
+HostDBHash::~HostDBHash() {}
+
+#include "tscore/Scalar.h"
+
+HostDBRecord *
+HostDBRecord::alloc(ts::TextView query_name, unsigned int rr_count, size_t srv_name_size)
+{
+  const ts::Scalar<8> qn_size = ts::round_up(query_name.size() + 1);
+  const ts::Scalar<8> r_size  = ts::round_up(sizeof(self_type) + qn_size + rr_count * sizeof(HostDBInfo) + srv_name_size);
+  auto ptr                    = malloc(r_size);
+  memset(ptr, 0, r_size);
+  auto self = static_cast<self_type *>(ptr);
+  new (self) self_type();
+  self->_iobuffer_index = 0;
+  self->_record_size    = r_size;
+
+  Debug("hostdb", "allocating %ld bytes for %.*s with %d RR records at [%p]", r_size.value(), int(query_name.size()),
+        query_name.data(), rr_count, self);
+
+  // where in our block of memory we are
+  int offset = sizeof(self_type);
+  memcpy(self->apply_offset<void>(offset), query_name);
+  offset += qn_size;
+  self->rr_offset = offset;
+  self->rr_count  = rr_count;
+  // Construct the info instances to a valid state.
+  for (auto &info : self->rr_info()) {
+    new (&info) std::remove_reference_t<decltype(info)>;
+  }
+
+  return self;
+}
+
+void
+HostDBRecord::free()
+{
+}