]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
proxyMapping: a table based approach to let the recursor know the actual IP address... 11396/head
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 1 Mar 2022 14:32:59 +0000 (15:32 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 21 Mar 2022 10:32:09 +0000 (11:32 +0100)
14 files changed:
.github/actions/spell-check/expect.txt
pdns/pdns_recursor.cc
pdns/rec-lua-conf.cc
pdns/rec-lua-conf.hh
pdns/rec_channel_rec.cc
pdns/recursordist/docs/lua-config/index.rst
pdns/recursordist/docs/lua-config/protobuf.rst
pdns/recursordist/docs/lua-config/proxymapping.rst [new file with mode: 0644]
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-main.hh
pdns/recursordist/rec-tcp.cc
pdns/syncres.hh
regression-tests.recursor-dnssec/test_Protobuf.py
regression-tests.recursor-dnssec/test_ProxyByTable.py [new file with mode: 0644]

index 2cb4babb2bcfb950c79a6ca3987c8df6816cec48..8ea552de0d7343c420ef08cd8abb416ec378d6b6 100644 (file)
@@ -1301,6 +1301,7 @@ progid
 protobuf
 protozero
 providername
+proxymapping
 proxyprotocol
 proxyprotocolvalues
 pseudonymize
index e82b2c26d04ae5ce4345c64108385bd2509f04f7..c696a767031dec95e8da715cafdd5dcd1de26182 100644 (file)
@@ -923,7 +923,7 @@ void startDoResolve(void* p)
 #ifdef HAVE_FSTRM
     sr.setFrameStreamServers(t_frameStreamServers);
 #endif
-    sr.setQuerySource(dc->d_source, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
+    sr.setQuerySource(dc->d_mappedSource, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
     sr.setQueryReceivedOverTCP(dc->d_tcp);
 
     bool tracedQuery = false; // we could consider letting Lua know about this too
@@ -1516,11 +1516,23 @@ void startDoResolve(void* p)
         pbMessage.setQueryTime(dc->d_now.tv_sec, dc->d_now.tv_usec);
       }
       pbMessage.setMessageIdentity(dc->d_uuid);
-      pbMessage.setSocketFamily(dc->d_source.sin4.sin_family);
       pbMessage.setSocketProtocol(dc->d_tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
-      Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-      ComboAddress requestor = requestorNM.getMaskedNetwork();
-      pbMessage.setFrom(requestor);
+
+      if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
+        pbMessage.setSocketFamily(dc->d_source.sin4.sin_family);
+        Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+        ComboAddress requestor = requestorNM.getMaskedNetwork();
+        pbMessage.setFrom(requestor);
+        pbMessage.setFromPort(dc->d_source.getPort());
+      }
+      else {
+        pbMessage.setSocketFamily(dc->d_mappedSource.sin4.sin_family);
+        Netmask requestorNM(dc->d_mappedSource, dc->d_mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+        ComboAddress requestor = requestorNM.getMaskedNetwork();
+        pbMessage.setFrom(requestor);
+        pbMessage.setFromPort(dc->d_mappedSource.getPort());
+      }
+
       pbMessage.setTo(dc->d_destination);
       pbMessage.setId(dc->d_mdp.d_header.id);
 
@@ -1529,7 +1541,6 @@ void startDoResolve(void* p)
       pbMessage.setRequestorId(dq.requestorId);
       pbMessage.setDeviceId(dq.deviceId);
       pbMessage.setDeviceName(dq.deviceName);
-      pbMessage.setFromPort(dc->d_source.getPort());
       pbMessage.setToPort(dc->d_destination.getPort());
 
       for (const auto& m : dq.meta) {
@@ -1777,7 +1788,12 @@ bool expectProxyProtocol(const ComboAddress& from)
   return g_proxyProtocolACL.match(from);
 }
 
-static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, struct timeval tv, int fd, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace)
+// fromaddr: the address the query is coming from
+// destaddr: the address the query was received on
+// source: the address we assume the query is coming from, might be set by proxy protocol
+// destination: the address we assume the query was sent to, might be set by proxy protocol
+// mappedSource: the address we assume the query is coming from. Differs from source if table based mapping has been applied
+static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, const ComboAddress& mappedSource, struct timeval tv, int fd, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace)
 {
   ++(RecThreadInfo::self().numberOfDistributedQueries);
   gettimeofday(&g_now, nullptr);
@@ -1900,7 +1916,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
     RecursorPacketCache::OptPBData pbData{boost::none};
     if (t_protobufServers) {
       if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && policyTags.empty())) {
-        protobufLogQuery(luaconfsLocal, uniqueId, source, destination, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta);
+        protobufLogQuery(luaconfsLocal, uniqueId, source, destination, mappedSource, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta);
       }
     }
 
@@ -1932,7 +1948,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
         eventTrace.add(RecEventTrace::AnswerSent);
 
         if (t_protobufServers && logResponse && !(luaconfsLocal->protobufExportConfig.taggedOnly && pbData && !pbData->d_tagged)) {
-          protobufLogResponse(dh, luaconfsLocal, pbData, tv, false, source, destination, ednssubnet, uniqueId, requestorId, deviceId, deviceName, meta, eventTrace);
+          protobufLogResponse(dh, luaconfsLocal, pbData, tv, false, source, destination, mappedSource, ednssubnet, uniqueId, requestorId, deviceId, deviceName, meta, eventTrace);
         }
 
         if (eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
@@ -2014,10 +2030,11 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   dc->setSocket(fd);
   dc->d_tag = ctag;
   dc->d_qhash = qhash;
-  dc->setRemote(fromaddr);
-  dc->setSource(source);
-  dc->setLocal(destaddr);
-  dc->setDestination(destination);
+  dc->setRemote(fromaddr); // the address the query is coming from
+  dc->setSource(source); // the address we assume the query is coming from, might be set by proxy protocol
+  dc->setLocal(destaddr); // the address the query was received on
+  dc->setDestination(destination); // the address we assume the query is sent to, might be set by proxy protocol
+  dc->setMappedSource(mappedSource); // the address we assume the query is coming from. Differs from source if table-based mapping has been applied
   dc->d_tcp = false;
   dc->d_ecsFound = ecsFound;
   dc->d_ecsParsed = ecsParsed;
@@ -2052,9 +2069,9 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
   ssize_t len;
   static const size_t maxIncomingQuerySize = g_proxyProtocolACL.empty() ? 512 : (512 + g_proxyProtocolMaximumSize);
   static thread_local std::string data;
-  ComboAddress fromaddr;
-  ComboAddress source;
-  ComboAddress destination;
+  ComboAddress fromaddr; // the address the query is coming from
+  ComboAddress source; // the address we assume the query is coming from, might be set by proxy protocol
+  ComboAddress destination; // the address we assume the query was sent to, might be set by proxy protocol
   struct msghdr msgh;
   struct iovec iov;
   cmsgbuf_aligned cbuf;
@@ -2126,14 +2143,19 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       if (!proxyProto) {
         source = fromaddr;
       }
-
+      ComboAddress mappedSource = source;
+      if (t_proxyMapping) {
+        if (auto it = t_proxyMapping->lookup(source)) {
+          mappedSource = it->second;
+        }
+      }
       if (t_remotes) {
         t_remotes->push_back(fromaddr);
       }
 
-      if (t_allowFrom && !t_allowFrom->match(&source)) {
+      if (t_allowFrom && !t_allowFrom->match(&mappedSource)) {
         if (!g_quiet) {
-          g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP query from " << source.toString() << ", address not matched by allow-from" << endl;
+          g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP query from " << mappedSource.toString() << ", address not matched by allow-from" << endl;
         }
 
         g_stats.unauthorizedUDP++;
@@ -2174,9 +2196,9 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
         }
         else {
           if (dh->opcode == Opcode::Notify) {
-            if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(&source)) {
+            if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(&mappedSource)) {
               if (!g_quiet) {
-                g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP NOTIFY from " << source.toString() << ", address not matched by allow-notify-from" << endl;
+                g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP NOTIFY from " << mappedSource.toString() << ", address not matched by allow-notify-from" << endl;
               }
 
               g_stats.sourceDisallowedNotify++;
@@ -2186,7 +2208,7 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
 
           struct timeval tv = {0, 0};
           HarvestTimestamp(&msgh, &tv);
-          ComboAddress dest;
+          ComboAddress dest; // the address the query was sent to to
           dest.reset(); // this makes sure we ignore this address if not returned by recvmsg above
           auto loc = rplookup(g_listenSocketsAddresses, fd);
           if (HarvestDestinationAddress(&msgh, &dest)) {
@@ -2211,12 +2233,12 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
 
           if (RecThreadInfo::weDistributeQueries()) {
             std::string localdata = data;
-            distributeAsyncFunction(data, [localdata, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues, eventTrace]() mutable {
-              return doProcessUDPQuestion(localdata, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues, eventTrace);
+            distributeAsyncFunction(data, [localdata, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace]() mutable {
+              return doProcessUDPQuestion(localdata, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace);
             });
           }
           else {
-            doProcessUDPQuestion(data, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues, eventTrace);
+            doProcessUDPQuestion(data, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace);
           }
         }
       }
index 8f730e46422e365be386b5222b8f2c12db9a5a4b..70d2017175be526d1eafdaa440714a7cbfb43a89 100644 (file)
@@ -146,6 +146,10 @@ static void parseProtobufOptions(boost::optional<protobufOptions_t> vars, Protob
     config.logResponses = boost::get<bool>((*vars)["logResponses"]);
   }
 
+  if (vars->count("logMappedFrom")) {
+    config.logMappedFrom = boost::get<bool>((*vars)["logMappedFrom"]);
+  }
+
   if (vars->count("exportTypes")) {
     config.exportTypes.clear();
 
@@ -350,7 +354,7 @@ public:
   }
 };
 
-void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads)
+void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads, ProxyMapping& proxyMapping)
 {
   LuaConfigItems lci;
 
@@ -721,6 +725,20 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
     lci.allowAdditionalQTypes.insert_or_assign(qtype, pair(targets, mode));
   });
 
+  Lua->writeFunction("addProxyMapping", [&proxyMapping](const string& netmaskArg, const string& addressArg) {
+    try {
+      Netmask netmask(netmaskArg);
+      ComboAddress address(addressArg);
+      proxyMapping.insert_or_assign(netmask, address);
+    }
+    catch (std::exception& e) {
+      g_log << Logger::Error << "Error processing addProxyMapping: " << e.what() << endl;
+    }
+    catch (PDNSException& e) {
+      g_log << Logger::Error << "Error processing addProxyMapping: " << e.reason << endl;
+    }
+  });
+
   try {
     Lua->executeCode(ifs);
     g_luaconfs.setState(std::move(lci));
index a88ae913db94381fff444f6c4757cb7e164cb26a..482ffaa49972d04b15d0225656a620430588ec67 100644 (file)
@@ -40,6 +40,7 @@ struct ProtobufExportConfig
   bool logQueries{true};
   bool logResponses{true};
   bool taggedOnly{false};
+  bool logMappedFrom{false};
 };
 
 struct FrameStreamExportConfig
@@ -71,6 +72,8 @@ enum class AdditionalMode : uint8_t
   ResolveDeferred
 };
 
+using ProxyMapping = NetmaskTree<ComboAddress, Netmask>;
+
 class LuaConfigItems
 {
 public:
@@ -101,5 +104,5 @@ struct luaConfigDelayedThreads
   std::vector<std::tuple<std::vector<ComboAddress>, boost::optional<DNSFilterEngine::Policy>, bool, uint32_t, size_t, TSIGTriplet, size_t, ComboAddress, uint16_t, uint32_t, std::shared_ptr<SOARecordContent>, std::string>> rpzPrimaryThreads;
 };
 
-void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads);
+void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads, ProxyMapping&);
 void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads, uint64_t generation);
index b5d41b77dd5192ad695df85c7f0b8abc13d0aa45..88b2e81b694e916ee84e617c31000f0c411e4228 100644 (file)
@@ -1873,6 +1873,12 @@ static string setEventTracing(T begin, T end)
   }
 }
 
+static void* pleaseSupplantProxyMapping(std::shared_ptr<ProxyMapping> pm)
+{
+  t_proxyMapping = pm;
+  return nullptr;
+}
+
 RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const string& question, RecursorControlParser::func_t** command)
 {
   *command = nop;
@@ -2012,8 +2018,11 @@ RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const str
 
     try {
       luaConfigDelayedThreads delayedLuaThreads;
-      loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads);
+      ProxyMapping proxyMapping;
+      loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
       startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
+      std::shared_ptr<ProxyMapping> ptr = proxyMapping.empty() ? nullptr : std::make_shared<ProxyMapping>(proxyMapping);
+      broadcastFunction([=] { return pleaseSupplantProxyMapping(ptr); });
       g_log << Logger::Warning << "Reloaded Lua configuration file '" << ::arg()["lua-config-file"] << "', requested via control channel" << endl;
       return {0, "Reloaded Lua configuration file '" + ::arg()["lua-config-file"] + "'\n"};
     }
index a602f22d67d80c728104fbfb0a6b707965ac79a4..36a3ec7a2ec8b28d722a03f71c64a3c551b80a4a 100644 (file)
@@ -11,5 +11,6 @@ Since version 4.0.0, the PowerDNS Recursor supports additional configuration opt
     sortlist
     ztc
     additionals
+    proxymapping
 
 In addition, :func:`pdnslog` together with ``pdns.loglevels`` is also supported in the Lua configuration file.
index 21a222f89e78e968678c671f1a7514b4379c0322..dcb5f379442bcfb6c0b55444f3e734eee7834204 100644 (file)
@@ -37,6 +37,10 @@ Protobuf export to a server is enabled using the ``protobufServer()`` directive:
 
   The values in ``exportTypes`` can be numeric as well as strings. Symbolic names from ``pdns`` can be used, e.g.  ``exportTypes = { pdns.A, pdns.AAAA, pdns.CNAME }``
 
+  .. versionadded:: 4.7.0
+
+  * ``logMappedFrom=false``: bool - whether to log the remote address before substitution by :ref:`proxymapping` (the default) or after
+
 .. function:: protobufServer(server [[[[[[[, timeout=2], maxQueuedEntries=100], reconnectWaitTime=1], maskV4=32], maskV6=128], asyncConnect=false], taggedOnly=false])
 
   .. deprecated:: 4.2.0
diff --git a/pdns/recursordist/docs/lua-config/proxymapping.rst b/pdns/recursordist/docs/lua-config/proxymapping.rst
new file mode 100644 (file)
index 0000000..8884946
--- /dev/null
@@ -0,0 +1,51 @@
+.. _proxymapping:
+
+Table Based Proxy Mapping
+=========================
+Starting with version 4.7.0, the PowerDNS Recursor has the ability to map source IP addresses to alternative addresses, which is for example useful when some clients reach the recursor via a reverse-proxy.
+The mapped address is used internally for ACL and similar checks.
+If the :ref:`setting-proxy-protocol-from` is also used, the substitution is done on the source address specified in the proxy protocol header.
+
+Depending on context, the incoming address can be
+
+The physical address ``P``
+  the physical address the query is received on.
+The source address ``S``
+  the source address as specified in the Proxy protocol
+The mapped address ``M``
+  the source address mapped by Table Based Proxy Mapping
+
+``S equals P`` if no Proxy Protocol is used.
+
+``M equals S`` if no Table Based Proxy Mapping is used.
+
+``P`` determines if the Proxy Protocol is used (:ref:`setting-proxy-protocol-from`).
+
+``S`` is passed to Lua functions and RPZ processing
+
+``M`` is used for incoming ACL checking (:ref:`setting-allow-from`) and to determine the ECS processing (:ref:`setting-ecs-add-for`).
+
+An example use:
+
+.. code-block:: Lua
+
+  addProxyMapping("127.0.0.0/24", "203.0.113.1")
+  addProxyMapping("10.0.0.0/8", "203.0.113.2")
+
+
+The following function is available to configure table based proxy mapping.
+Reloading the Lua configuration will replace the current configuration with the new one.
+If the subnets specified in multiple :func:`addProxyMapping` calls overlap, the most specific one is used.
+By default, the address *before* mapping ``S`` is used for internal logging and ``Protobuf`` messages.
+See :func:`protobufServer` on how to tune the source address logged in ``Protobuf` messages.
+
+.. function:: addProxyMapping(subnet, ip)
+
+  .. versionadded:: 4.7.0
+
+  Specify a table based mapping for a subnet.
+
+  :param string subnet: a subnet to match
+  :param string ip: the IP address or IPaddress port combination to match the subnet to.
+
+
index cd1116d75f0338bed9aeb42ef8ca49a0bb4ad121..9018dbe2b8d8ef0d1144f96beb44bfce0b8d54bf 100644 (file)
@@ -103,6 +103,9 @@ deferredAdd_t g_deferredAdds;
    and finally the workers */
 std::vector<RecThreadInfo> RecThreadInfo::s_threadInfos;
 
+std::shared_ptr<ProxyMapping> g_proxyMapping; // new threads needs this to be setup
+thread_local std::shared_ptr<ProxyMapping> t_proxyMapping;
+
 bool RecThreadInfo::s_weDistributeQueries; // if true, 1 or more threads listen on the incoming query sockets and distribute them to workers
 unsigned int RecThreadInfo::s_numDistributorThreads;
 unsigned int RecThreadInfo::s_numWorkerThreads;
@@ -443,15 +446,23 @@ bool checkOutgoingProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal
   return true;
 }
 
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta)
+void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedRemote, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta)
 {
   if (!t_protobufServers) {
     return;
   }
 
-  Netmask requestorNM(remote, remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-  ComboAddress requestor = requestorNM.getMaskedNetwork();
-  requestor.setPort(remote.getPort());
+  ComboAddress requestor;
+  if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
+    Netmask requestorNM(remote, remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+    requestor = requestorNM.getMaskedNetwork();
+    requestor.setPort(remote.getPort());
+  }
+  else {
+    Netmask requestorNM(mappedRemote, mappedRemote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+    requestor = requestorNM.getMaskedNetwork();
+    requestor.setPort(mappedRemote.getPort());
+  }
 
   pdns::ProtoZero::RecMessage m{128, std::string::size_type(policyTags.empty() ? 0 : 64)}; // It's a guess
   m.setType(pdns::ProtoZero::Message::MessageType::DNSQueryType);
@@ -490,6 +501,7 @@ void protobufLogResponse(pdns::ProtoZero::RecMessage& message)
 void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
                          const RecursorPacketCache::OptPBData& pbData, const struct timeval& tv,
                          bool tcp, const ComboAddress& source, const ComboAddress& destination,
+                         const ComboAddress& mappedSource,
                          const EDNSSubnetOpts& ednssubnet,
                          const boost::uuids::uuid& uniqueId, const string& requestorId, const string& deviceId,
                          const string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta,
@@ -512,10 +524,19 @@ void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigI
   }
 
   // In message part
-  Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-  ComboAddress requestor = requestorNM.getMaskedNetwork();
+  if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
+    Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+    auto requestor = requestorNM.getMaskedNetwork();
+    pbMessage.setFrom(requestor);
+    pbMessage.setFromPort(source.getPort());
+  }
+  else {
+    Netmask requestorNM(mappedSource, mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+    auto requestor = requestorNM.getMaskedNetwork();
+    pbMessage.setFrom(requestor);
+    pbMessage.setFromPort(mappedSource.getPort());
+  }
   pbMessage.setMessageIdentity(uniqueId);
-  pbMessage.setFrom(requestor);
   pbMessage.setTo(destination);
   pbMessage.setSocketProtocol(tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
   pbMessage.setId(dh->id);
@@ -525,7 +546,6 @@ void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigI
   pbMessage.setRequestorId(requestorId);
   pbMessage.setDeviceId(deviceId);
   pbMessage.setDeviceName(deviceName);
-  pbMessage.setFromPort(source.getPort());
   pbMessage.setToPort(destination.getPort());
   for (const auto& m : meta) {
     pbMessage.setMeta(m.first, m.second.stringVal, m.second.intVal);
@@ -1229,7 +1249,10 @@ static int serviceMain(int argc, char* argv[])
 
   luaConfigDelayedThreads delayedLuaThreads;
   try {
-    loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads);
+    ProxyMapping proxyMapping;
+    loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
+    // Initial proxy mapping
+    g_proxyMapping = proxyMapping.empty() ? nullptr : std::make_shared<ProxyMapping>(proxyMapping);
   }
   catch (PDNSException& e) {
     g_log << Logger::Error << "Cannot load Lua configuration: " << e.reason << endl;
@@ -2024,6 +2047,7 @@ static void recursorThread()
       t_allowNotifyFor = g_initialAllowNotifyFor;
       t_udpclientsocks = std::make_unique<UDPClientSocks>();
       t_tcpClientCounts = std::make_unique<tcpClientCounts_t>();
+      t_proxyMapping = g_proxyMapping;
 
       if (threadInfo.isHandler()) {
         if (!primeHints()) {
index da1b8d82fd4b023c1ec6a694e3540b9262155fbc..89eb0a93d92c47fba21bf09719a2c59774c8fc34 100644 (file)
@@ -58,21 +58,29 @@ struct DNSComboWriter
   {
   }
 
+  // The address the query is coming from
   void setRemote(const ComboAddress& sa)
   {
     d_remote = sa;
   }
 
+  // The address we assume the query is coming from, might be set by proxy protocol
   void setSource(const ComboAddress& sa)
   {
     d_source = sa;
   }
 
+  void setMappedSource(const ComboAddress& sa)
+  {
+    d_mappedSource = sa;
+  }
+
   void setLocal(const ComboAddress& sa)
   {
     d_local = sa;
   }
 
+  // The address we assume the query is sent to, might be set by proxy protocol
   void setDestination(const ComboAddress& sa)
   {
     d_destination = sa;
@@ -83,6 +91,7 @@ struct DNSComboWriter
     d_socket = sock;
   }
 
+  // get a string repesentation of the client address, including proxy info if applicable
   string getRemote() const
   {
     if (d_source == d_remote) {
@@ -94,18 +103,12 @@ struct DNSComboWriter
   std::vector<ProxyProtocolValue> d_proxyProtocolValues;
   MOADNSParser d_mdp;
   struct timeval d_now;
-  /* Remote client, might differ from d_source
-     in case of XPF, in which case d_source holds
-     the IP of the client and d_remote of the proxy
-  */
-  ComboAddress d_remote;
-  ComboAddress d_source;
-  /* Destination address, might differ from
-     d_destination in case of XPF, in which case
-     d_destination holds the IP of the proxy and
-     d_local holds our own. */
-  ComboAddress d_local;
-  ComboAddress d_destination;
+
+  ComboAddress d_remote; // the address the query is coming from
+  ComboAddress d_source; // the address we assume the query is coming from, might be set by proxy protocol
+  ComboAddress d_local; // the address we received the query on
+  ComboAddress d_destination; // the address we assume the query is sent to, might be set by proxy protocol
+  ComboAddress d_mappedSource; // the source address after being mapped by table based proxy mapping
   RecEventTrace d_eventTrace;
   boost::uuids::uuid d_uuid;
   string d_requestorId;
@@ -224,6 +227,8 @@ extern string g_programname;
 extern string g_pidfname;
 extern RecursorControlChannel g_rcc; // only active in the handler thread
 
+extern thread_local std::shared_ptr<ProxyMapping> t_proxyMapping;
+
 #ifdef NOD_ENABLED
 extern bool g_nodEnabled;
 extern DNSName g_nodLookupDomain;
@@ -495,7 +500,7 @@ bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal);
 void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
                        bool& foundECS, EDNSSubnetOpts* ednssubnet, EDNSOptionViewMap* options,
                        bool& foundXPF, ComboAddress* xpfSource, ComboAddress* xpfDest);
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta);
+void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedSource, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta);
 bool isAllowNotifyForZone(DNSName qname);
 bool checkForCacheHit(bool qnameParsed, unsigned int tag, const string& data,
                       DNSName& qname, uint16_t& qtype, uint16_t& qclass,
@@ -506,7 +511,7 @@ void protobufLogResponse(pdns::ProtoZero::RecMessage& message);
 void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
                          const RecursorPacketCache::OptPBData& pbData, const struct timeval& tv,
                          bool tcp, const ComboAddress& source, const ComboAddress& destination,
-                         const EDNSSubnetOpts& ednssubnet,
+                         const ComboAddress& mappedSource, const EDNSSubnetOpts& ednssubnet,
                          const boost::uuids::uuid& uniqueId, const string& requestorId, const string& deviceId,
                          const string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta,
                          const RecEventTrace& eventTrace);
index 0ad1cb672109b9aa101ab497f3ea5c8cd41dd91e..406d9df933c9e408acf10e293b8aaf0136bd0e83 100644 (file)
@@ -260,9 +260,15 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       /* Now that we have retrieved the address of the client, as advertised by the proxy
          via the proxy protocol header, check that it is allowed by our ACL */
       /* note that if the proxy header used a 'LOCAL' command, the original source and destination are untouched so everything should be fine */
-      if (t_allowFrom && !t_allowFrom->match(&conn->d_source)) {
+      conn->d_mappedSource = conn->d_source;
+      if (t_proxyMapping) {
+        if (auto it = t_proxyMapping->lookup(conn->d_source)) {
+          conn->d_mappedSource = it->second;
+        }
+      }
+      if (t_allowFrom && !t_allowFrom->match(&conn->d_mappedSource)) {
         if (!g_quiet) {
-          g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << conn->d_source.toString() << ", address not matched by allow-from" << endl;
+          g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << conn->d_mappedSource.toString() << ", address not matched by allow-from" << endl;
         }
 
         ++g_stats.unauthorizedTCP;
@@ -349,15 +355,16 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       dc->d_tcpConnection = conn; // carry the torch
       dc->setSocket(conn->getFD()); // this is the only time a copy is made of the actual fd
       dc->d_tcp = true;
-      dc->setRemote(conn->d_remote);
-      dc->setSource(conn->d_source);
+      dc->setRemote(conn->d_remote); // the address the query was received from
+      dc->setSource(conn->d_source); // the address we assume the query is coming from, might be set by proxy protocol
       ComboAddress dest;
       dest.reset();
       dest.sin4.sin_family = conn->d_remote.sin4.sin_family;
       socklen_t len = dest.getSocklen();
       getsockname(conn->getFD(), (sockaddr*)&dest, &len); // if this fails, we're ok with it
-      dc->setLocal(dest);
-      dc->setDestination(conn->d_destination);
+      dc->setLocal(dest); // the address we received the query on
+      dc->setDestination(conn->d_destination); // the address we assume the query is received on, might be set by proxy protocol
+      dc->setMappedSource(conn->d_mappedSource); // the address we assume the query is coming from after table based mapping
       /* we can't move this if we want to be able to access the values in
          all queries sent over this connection */
       dc->d_proxyProtocolValues = conn->proxyProtocolValues;
@@ -447,7 +454,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
         try {
 
           if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && dc->d_policyTags.empty())) {
-            protobufLogQuery(luaconfsLocal, dc->d_uuid, dc->d_source, dc->d_destination, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName, dc->d_meta);
+            protobufLogQuery(luaconfsLocal, dc->d_uuid, dc->d_source, dc->d_destination, dc->d_mappedSource, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName, dc->d_meta);
           }
         }
         catch (const std::exception& e) {
@@ -499,9 +506,9 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
         ++g_stats.tcpqcounter;
 
         if (dc->d_mdp.d_header.opcode == Opcode::Notify) {
-          if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(dc->d_source)) {
+          if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(dc->d_mappedSource)) {
             if (!g_quiet) {
-              g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_source.toString() << ", address not matched by allow-notify-from" << endl;
+              g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_mappedSource.toString() << ", address not matched by allow-notify-from" << endl;
             }
 
             g_stats.sourceDisallowedNotify++;
@@ -510,7 +517,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
 
           if (!isAllowNotifyForZone(qname)) {
             if (!g_quiet) {
-              g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_source.toString() << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl;
+              g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_mappedSource.toString() << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl;
             }
 
             g_stats.zoneDisallowedNotify++;
@@ -547,7 +554,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
               {
                 0, 0
               };
-              protobufLogResponse(dh, luaconfsLocal, pbData, tv, true, dc->d_source, dc->d_destination, dc->d_ednssubnet, dc->d_uuid, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName, dc->d_meta, dc->d_eventTrace);
+              protobufLogResponse(dh, luaconfsLocal, pbData, tv, true, dc->d_source, dc->d_destination, dc->d_mappedSource, dc->d_ednssubnet, dc->d_uuid, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName, dc->d_meta, dc->d_eventTrace);
             }
 
             if (dc->d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
@@ -615,9 +622,15 @@ void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&)
     }
 
     bool fromProxyProtocolSource = expectProxyProtocol(addr);
-    if (t_allowFrom && !t_allowFrom->match(&addr) && !fromProxyProtocolSource) {
+    ComboAddress mappedSource = addr;
+    if (!fromProxyProtocolSource && t_proxyMapping) {
+      if (auto it = t_proxyMapping->lookup(addr)) {
+        mappedSource = it->second;
+      }
+    }
+    if (!fromProxyProtocolSource && t_allowFrom && !t_allowFrom->match(&mappedSource)) {
       if (!g_quiet)
-        g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << addr.toString() << ", address neither matched by allow-from nor proxy-protocol-from" << endl;
+        g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << mappedSource.toString() << ", address neither matched by allow-from nor proxy-protocol-from" << endl;
 
       g_stats.unauthorizedTCP++;
       try {
@@ -647,6 +660,7 @@ void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&)
     tc->d_destination.sin4.sin_family = addr.sin4.sin_family;
     socklen_t len = tc->d_destination.getSocklen();
     getsockname(tc->getFD(), reinterpret_cast<sockaddr*>(&tc->d_destination), &len); // if this fails, we're ok with it
+    tc->d_mappedSource = mappedSource;
 
     if (fromProxyProtocolSource) {
       tc->proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
index db4dc45ad7b2c438d20dbd0d21ee512f3f7f57ef..1020763673d09cccaaf6d719b60a10515647869a 100644 (file)
@@ -1140,6 +1140,7 @@ public:
   const ComboAddress d_remote;
   ComboAddress d_source;
   ComboAddress d_destination;
+  ComboAddress d_mappedSource;
   size_t queriesCount{0};
   size_t proxyProtocolGot{0};
   ssize_t proxyProtocolNeed{0};
index 92f5155102a439f8800e9f3512a17a15c80552ec..134b84b75c0c5468cd1d3ffc899a89543df374c6 100644 (file)
@@ -368,6 +368,82 @@ auth-zones=example=configs/%s/example.zone""" % _confdir
         self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
         self.checkNoRemainingMessage()
 
+class ProtobufProxyMappingTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export queries and response over protobuf with a proxyMapping
+    """
+
+    _confdir = 'ProtobufProxyMappingTest'
+    _config_template = """
+    auth-zones=example=configs/%s/example.zone
+    allow-from=3.4.5.0/24
+    """ % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("127.0.0.1/24", "3.4.5.6:99")
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1')
+        self.assertEqual(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # we have max-cache-ttl set to 15
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+        self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
+        self.checkNoRemainingMessage()
+
+class ProtobufProxyMappingLogMappedTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export queries and response over protobuf.
+    """
+
+    _confdir = 'ProtobufProxyMappingLogMappedTest'
+    _config_template = """
+    auth-zones=example=configs/%s/example.zone
+    allow-from=3.4.5.0/0"
+    """ % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("127.0.0.1/24", "3.4.5.6:99")
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logMappedFrom = true })
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '3.4.5.6')
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '3.4.5.6')
+        self.assertEqual(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # we have max-cache-ttl set to 15
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+        self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
+        self.checkNoRemainingMessage()
+
 class ProtobufProxyTest(TestRecursorProtobuf):
     """
     This test makes sure that we correctly export addresses over protobuf when the proxy protocol is used.
@@ -402,6 +478,85 @@ allow-from=127.0.0.1,6.6.6.6
         self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
         self.checkNoRemainingMessage()
 
+class ProtobufProxyWithProxyByTableTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
+    """
+
+    _confdir = 'ProtobufProxyWithProxyByTable'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+proxy-protocol-from=127.0.0.1/32
+allow-from=3.4.5.6
+""" % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("6.6.6.6/24", "3.4.5.6:99")
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQueryWithProxyProtocol(query, False, '6.6.6.6', '7.7.7.7', 666, 777)
+
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '6.6.6.6', '7.7.7.7')
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '6.6.6.6')
+        self.assertEqual(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # we have max-cache-ttl set to 15
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+        self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
+        self.checkNoRemainingMessage()
+
+class ProtobufProxyWithProxyByTableLogMappedTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
+    """
+
+    _confdir = 'ProtobufProxyWithProxyByTableLogMapped'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+proxy-protocol-from=127.0.0.1/32
+allow-from=3.4.5.6
+""" % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("6.6.6.6/24", "3.4.5.6:99")
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logMappedFrom = true })
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQueryWithProxyProtocol(query, False, '6.6.6.6', '7.7.7.7', 666, 777)
+
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '3.4.5.6', '7.7.7.7')
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '3.4.5.6')
+        self.assertEqual(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # we have max-cache-ttl set to 15
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+        self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
+        self.checkNoRemainingMessage()
+
+
 class OutgoingProtobufDefaultTest(TestRecursorProtobuf):
     """
     This test makes sure that we correctly export outgoing queries over protobuf.
diff --git a/regression-tests.recursor-dnssec/test_ProxyByTable.py b/regression-tests.recursor-dnssec/test_ProxyByTable.py
new file mode 100644 (file)
index 0000000..b804001
--- /dev/null
@@ -0,0 +1,44 @@
+import dns
+import os
+from recursortests import RecursorTest
+
+class testProxyByTable(RecursorTest):
+    """
+    This test makes sure that we correctly use the proxy-mapped address during the ACL check
+    """
+    _confdir = 'ProxyByTable'
+
+    _config_template = """dnssec=validate
+    auth-zones=authzone.example=configs/%s/authzone.zone
+    allow-from=3.4.5.0/24
+    """ % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("127.0.0.0/24", "3.4.5.6:99")
+    """
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'authzone.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN authzone.example.
+@ 3600 IN SOA {soa}
+@ 3600 IN A 192.0.2.88
+""".format(soa=cls._SOA))
+        super(testProxyByTable, cls).generateRecursorConfig(confdir)
+
+
+    def testA(self):
+        expected = dns.rrset.from_text('ns.secure.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.9'.format(prefix=self._PREFIX))
+        query = dns.message.make_query('ns.secure.example', 'A', want_dnssec=True)
+        query.flags |= dns.flags.AD
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+
+            self.assertMessageIsAuthenticated(res)
+            self.assertRRsetInAnswer(res, expected)
+            self.assertMatchingRRSIGInAnswer(res, expected)
+
+