protobuf
protozero
providername
+proxymapping
proxyprotocol
proxyprotocolvalues
pseudonymize
#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
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);
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) {
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);
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);
}
}
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) {
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;
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;
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++;
}
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++;
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)) {
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);
}
}
}
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();
}
};
-void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads)
+void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads, ProxyMapping& proxyMapping)
{
LuaConfigItems lci;
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));
bool logQueries{true};
bool logResponses{true};
bool taggedOnly{false};
+ bool logMappedFrom{false};
};
struct FrameStreamExportConfig
ResolveDeferred
};
+using ProxyMapping = NetmaskTree<ComboAddress, Netmask>;
+
class LuaConfigItems
{
public:
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);
}
}
+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;
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"};
}
sortlist
ztc
additionals
+ proxymapping
In addition, :func:`pdnslog` together with ``pdns.loglevels`` is also supported in the Lua configuration file.
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
--- /dev/null
+.. _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.
+
+
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;
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);
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,
}
// 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);
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);
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;
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()) {
{
}
+ // 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;
d_socket = sock;
}
+ // get a string repesentation of the client address, including proxy info if applicable
string getRemote() const
{
if (d_source == d_remote) {
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;
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;
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,
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);
/* 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;
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;
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) {
++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++;
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++;
{
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) {
}
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 {
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;
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};
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.
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.
--- /dev/null
+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)
+
+