$(LIBCRYPTO_CFLAGS) \
$(LIBZMQ_CFLAGS)
+if LUA
+AM_CPPFLAGS +=$(LUA_CFLAGS)
+endif
+
AM_LDFLAGS = $(THREADFLAGS)
JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
packetcache.hh \
packethandler.cc packethandler.hh \
pdnsexception.hh \
+ proxy-protocol.cc proxy-protocol.hh \
qtype.cc qtype.hh \
query-local-address.hh query-local-address.cc \
rcpgenerator.cc \
unique_ptr<TCPNameserver> TN;
static vector<DNSDistributor*> g_distributors;
vector<std::shared_ptr<UDPNameserver> > g_udpReceivers;
+NetmaskGroup g_proxyProtocolACL;
+size_t g_proxyProtocolMaximumSize;
ArgvMap &arg()
{
::arg().setSwitch("dnsupdate","Enable/Disable DNS update (RFC2136) support. Default is no.")="no";
::arg().setSwitch("write-pid","Write a PID file")="yes";
::arg().set("allow-dnsupdate-from","A global setting to allow DNS updates from these IP ranges.")="127.0.0.0/8,::1";
+ ::arg().set("proxy-protocol-from","A Proxy Protocol header is only allowed from these subnets, and is mandatory then too.")="";
+ ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values")="512";
::arg().setSwitch("send-signed-notify", "Send TSIG secured NOTIFY if TSIG key is configured for a zone") = "yes";
::arg().set("allow-unsigned-notify", "Allow unsigned notifications for TSIG secured zones") = "yes"; //FIXME: change to 'no' later
::arg().set("allow-unsigned-supermaster", "Allow supermasters to create zones without TSIG signed NOTIFY")="yes";
bool logDNSQueries = ::arg().mustDo("log-dns-queries");
shared_ptr<UDPNameserver> NS;
std::string buffer;
- buffer.resize(DNSPacket::s_udpTruncationThreshold);
// If we have SO_REUSEPORT then create a new port for all receiver threads
// other than the first one.
}
for(;;) {
+ if (g_proxyProtocolACL.empty()) {
+ buffer.resize(DNSPacket::s_udpTruncationThreshold);
+ }
+ else {
+ buffer.resize(DNSPacket::s_udpTruncationThreshold + g_proxyProtocolMaximumSize);
+ }
+
if(!NS->receive(question, buffer)) { // receive a packet inline
continue; // packet was broken, try again
}
S.ringAccount("queries", question.qdomain, question.qtype);
S.ringAccount("remotes", question.d_remote);
if(logDNSQueries) {
- string remote;
- if(question.hasEDNSSubnet())
- remote = question.getRemote().toString() + "<-" + question.getRealRemote().toString();
- else
- remote = question.getRemote().toString();
- g_log << Logger::Notice<<"Remote "<< remote <<" wants '" << question.qdomain<<"|"<<question.qtype.toString() <<
+ g_log << Logger::Notice<<"Remote "<< question.getRemoteString() <<" wants '" << question.qdomain<<"|"<<question.qtype.toString() <<
"', do = " <<question.d_dnssecOk <<", bufsize = "<< question.getMaxReplyLen();
if(question.d_ednsRawPacketSizeLimit > 0 && question.getMaxReplyLen() != (unsigned int)question.d_ednsRawPacketSizeLimit)
g_log<<" ("<<question.d_ednsRawPacketSizeLimit<<")";
DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing");
PacketHandler::s_SVCAutohints = ::arg().mustDo("svc-autohints");
+ g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+ g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
+
PC.setTTL(::arg().asNum("cache-ttl"));
PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
QC.setMaxEntries(::arg().asNum("max-cache-entries"));
void carbonDumpThread();
extern bool g_anyToTcp;
extern bool g_8bitDNS;
+extern NetmaskGroup g_proxyProtocolACL;
+extern size_t g_proxyProtocolMaximumSize;
#ifdef HAVE_LUA_RECORDS
extern bool g_doLuaRecord;
extern bool g_LuaRecordSharedState;
return d_rawpacket;
}
+string DNSPacket::getRemoteString() const
+{
+ string ret;
+
+ ret = getRemote().toString();
+
+ if (d_inner_remote) {
+ ret += "(" + d_inner_remote->toString() + ")";
+ }
+
+ if(hasEDNSSubnet()) {
+ ret += "<-" + getRealRemote().toString();
+ }
+
+ return ret;
+}
+
+string DNSPacket::getRemoteStringWithPort() const
+{
+ string ret;
+
+ ret = getRemote().toStringWithPort();
+
+ if (d_inner_remote) {
+ ret += "(" + d_inner_remote->toStringWithPort() + ")";
+ }
+
+ if(hasEDNSSubnet()) {
+ ret += "<-" + getRealRemote().toString();
+ }
+
+ return ret;
+}
+
ComboAddress DNSPacket::getRemote() const
{
return d_remote;
}
+ComboAddress DNSPacket::getInnerRemote() const
+{
+ if (d_inner_remote)
+ return *d_inner_remote;
+ return d_remote;
+}
+
uint16_t DNSPacket::getRemotePort() const
{
return d_remote.sin4.sin_port;
r->setSocket(d_socket);
r->d_anyLocal=d_anyLocal;
r->setRemote(&d_remote);
+ r->d_inner_remote=d_inner_remote;
r->setAnswer(true); // this implies the allocation of the header
r->setA(true); // and we are authoritative
r->setRA(false); // no recursion available
d_rawpacket.assign(mesg,length);
if(length < 12) {
g_log << Logger::Debug << "Ignoring packet: too short ("<<length<<" < 12) from "
- << d_remote.toStringWithPort()<< endl;
+ << getRemoteStringWithPort();
return -1;
}
d_wantsnsid=false;
}
//! Use this to set where this packet was received from or should be sent to
-void DNSPacket::setRemote(const ComboAddress *s)
+void DNSPacket::setRemote(const ComboAddress *outer, std::optional<ComboAddress> inner)
{
- d_remote=*s;
+ d_remote=*outer;
+ if (inner) {
+ d_inner_remote=*inner;
+ }
+ else {
+ d_inner_remote.reset();
+ }
}
bool DNSPacket::hasEDNSSubnet() const
{
if(d_haveednssubnet)
return d_eso.source;
- return Netmask(d_remote);
+ return Netmask(getInnerRemote());
}
void DNSPacket::setSocket(Utility::sock_t sock)
const string& getString(); //!< for serialization - just passes the whole packet
// address & socket manipulation
- void setRemote(const ComboAddress*);
+ void setRemote(const ComboAddress*, std::optional<ComboAddress> = std::nullopt);
ComboAddress getRemote() const;
+ ComboAddress getInnerRemote() const; // for proxy protocol
Netmask getRealRemote() const;
ComboAddress getLocal() const
{
}
uint16_t getRemotePort() const;
+ string getRemoteString() const;
+ string getRemoteStringWithPort() const;
+
boost::optional<ComboAddress> d_anyLocal;
Utility::sock_t getSocket() const
}
void setSocket(Utility::sock_t sock);
-
// these manipulate 'd'
void setA(bool); //!< make this packet authoritative - manipulates 'd'
void setID(uint16_t); //!< set the DNS id of this packet - manipulates 'd'
TSIGRecordContent d_trc; //72
ComboAddress d_remote; //28
+ std::optional<ComboAddress> d_inner_remote; // for proxy protocol
TSIGHashEnum d_tsig_algo{TSIG_MD5}; //4
int d_ednsRawPacketSizeLimit{-1}; // only used for Lua record
lua.writeVariable("qname", query);
lua.writeVariable("zone", zone);
lua.writeVariable("zoneid", zoneid);
- lua.writeVariable("who", dnsp.getRemote());
+ lua.writeVariable("who", dnsp.getInnerRemote());
lua.writeVariable("dh", (dnsheader*)&dnsp.d);
lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
lua.writeVariable("tcp", dnsp.d_tcp);
}
else {
lua.writeVariable("ecswho", nullptr);
- s_lua_record_ctx->bestwho = dnsp.getRemote();
+ s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
}
lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
#include <sys/types.h>
#include "responsestats.hh"
+#include "common_startup.hh"
#include "dns.hh"
#include "dnsbackend.hh"
#include "dnspacket.hh"
#include "logger.hh"
#include "arguments.hh"
#include "statbag.hh"
+#include "proxy-protocol.hh"
#include "namespaces.hh"
packet.d_dt.setTimeval(recvtv);
}
else
- packet.d_dt.set(); // timing
+ packet.d_dt.set(); // timing
+
+ if (g_proxyProtocolACL.match(remote)) {
+ ComboAddress psource, pdestination;
+ bool proxyProto, tcp;
+ std::vector<ProxyProtocolValue> ppvalues;
+
+ buffer.resize(len);
+ ssize_t used = parseProxyHeader(buffer, proxyProto, psource, pdestination, tcp, ppvalues);
+ if (used <= 0 || (size_t) used > g_proxyProtocolMaximumSize || (len - used) > DNSPacket::s_udpTruncationThreshold) {
+ S.inc("corrupt-packets");
+ S.ringAccount("remotes-corrupt", packet.d_remote);
+ return false;
+ }
+ buffer.erase(0, used);
+ packet.d_inner_remote = psource;
+ packet.d_tcp = tcp;
+ }
+ else {
+ packet.d_inner_remote.reset();
+ }
if(packet.parse(&buffer.at(0), (size_t) len)<0) {
S.inc("corrupt-packets");
int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName& tsigkeyname)
{
ComboAddress remote = p.getRemote();
+ // this uses the outer (non-PROXY) remote on purpose
if(p.hasEDNSSubnet() && pdns::isAddressTrustedNotificationProxy(remote)) {
remote = p.getRealRemote().getNetwork();
}
+ else {
+ // but we fall back to the inner (PROXY) remote if there is no ECS forwarded by a trusted proxy
+ remote = p.getInnerRemote();
+ }
remote.setPort(53);
Resolver::res_t nsset;
if master is higher -> do stuff
*/
- g_log<<Logger::Debug<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<endl;
+ g_log<<Logger::Debug<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<endl;
if(!::arg().mustDo("secondary") && s_forwardNotify.empty()) {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" but slave support is disabled in the configuration"<<endl;
+ g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" but slave support is disabled in the configuration"<<endl;
return RCode::Refused;
}
// Sender verification
//
- if(!s_allowNotifyFrom.match((ComboAddress *) &p.d_remote ) || p.d_havetsig) {
+ if(!s_allowNotifyFrom.match(p.getInnerRemote()) || p.d_havetsig) {
if (p.d_havetsig && p.getTSIGKeyname().empty() == false) {
- g_log<<Logger::Notice<<"Received secure NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<", with TSIG key '"<<p.getTSIGKeyname()<<"'"<<endl;
+ g_log<<Logger::Notice<<"Received secure NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<", with TSIG key '"<<p.getTSIGKeyname()<<"'"<<endl;
} else {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" but the remote is not providing a TSIG key or in allow-notify-from (Refused)"<<endl;
+ g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" but the remote is not providing a TSIG key or in allow-notify-from (Refused)"<<endl;
return RCode::Refused;
}
}
if ((!::arg().mustDo("allow-unsigned-notify") && !p.d_havetsig) || p.d_havetsig) {
if (!p.d_havetsig) {
- g_log<<Logger::Warning<<"Received unsigned NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" while a TSIG key was required (Refused)"<<endl;
+ g_log<<Logger::Warning<<"Received unsigned NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" while a TSIG key was required (Refused)"<<endl;
return RCode::Refused;
}
vector<string> meta;
if (B.getDomainMetadata(p.qdomain,"AXFR-MASTER-TSIG",meta) && meta.size() > 0) {
DNSName expected{meta[0]};
if (p.getTSIGKeyname() != expected) {
- g_log<<Logger::Warning<<"Received secure NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<": expected TSIG key '"<<expected<<"', got '"<<p.getTSIGKeyname()<<"' (Refused)"<<endl;
+ g_log<<Logger::Warning<<"Received secure NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<": expected TSIG key '"<<expected<<"', got '"<<p.getTSIGKeyname()<<"' (Refused)"<<endl;
return RCode::Refused;
}
}
DomainInfo di;
if(!B.getDomainInfo(p.qdomain, di, false) || !di.backend) {
if(::arg().mustDo("autosecondary")) {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" for which we are not authoritative, trying supermaster"<<endl;
+ g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" for which we are not authoritative, trying supermaster"<<endl;
return trySuperMaster(p, p.getTSIGKeyname());
}
- g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" for which we are not authoritative (Refused)"<<endl;
+ g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" for which we are not authoritative (Refused)"<<endl;
return RCode::Refused;
}
+ // this uses the outer (non-PROXY) remote on purpose
if(pdns::isAddressTrustedNotificationProxy(p.getRemote())) {
if(di.masters.empty()) {
g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from trusted-notification-proxy "<<p.getRemote()<<", zone does not have any masters defined (Refused)"<<endl;
g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from trusted-notification-proxy "<<p.getRemote()<<endl;
}
else if(::arg().mustDo("primary") && di.kind == DomainInfo::Master) {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" but we are master (Refused)"<<endl;
+ g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" but we are master (Refused)"<<endl;
return RCode::Refused;
}
- else if(!di.isMaster(p.getRemote())) {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" which is not a master (Refused)"<<endl;
+ else if(!di.isMaster(p.getInnerRemote())) {
+ g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" which is not a master (Refused)"<<endl;
return RCode::Refused;
}
if(!s_forwardNotify.empty()) {
set<string> forwardNotify(s_forwardNotify);
for(const auto & j : forwardNotify) {
- g_log<<Logger::Notice<<"Relaying notification of domain "<<p.qdomain<<" from "<<p.getRemote()<<" to "<<j<<endl;
+ g_log<<Logger::Notice<<"Relaying notification of domain "<<p.qdomain<<" from "<<p.getRemoteString()<<" to "<<j<<endl;
Communicator.notify(p.qdomain,j);
}
}
if(::arg().mustDo("secondary")) {
- g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" - queueing check"<<endl;
+ g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" - queueing check"<<endl;
di.receivedNotify = true;
- Communicator.addSlaveCheckRequest(di, p.d_remote);
+ Communicator.addSlaveCheckRequest(di, p.getInnerRemote());
}
return 0;
}
#pragma once
-#include <iputils.hh>
+#include "iputils.hh"
struct ProxyProtocolValue
{
#include "namespaces.hh"
#include "signingpipe.hh"
#include "stubresolver.hh"
+#include "proxy-protocol.hh"
+#include "noinitvector.hh"
extern AuthPacketCache PC;
extern StatBag S;
try {
int mesgsize=65535;
boost::scoped_array<char> mesg(new char[mesgsize]);
-
+ std::optional<ComboAddress> inner_remote;
+ bool inner_tcp = false;
+
DLOG(g_log<<"TCP Connection accepted on fd "<<fd<<endl);
bool logDNSQueries= ::arg().mustDo("log-dns-queries");
+ if (g_proxyProtocolACL.match(remote)) {
+ unsigned int remainingTime = 0;
+ PacketBuffer proxyData;
+ proxyData.reserve(g_proxyProtocolMaximumSize);
+ ssize_t used;
+
+ // this for-loop ends by throwing, or by having gathered a complete proxy header
+ for (;;) {
+ used = isProxyHeaderComplete(proxyData);
+ if (used < 0) {
+ ssize_t origsize = proxyData.size();
+ proxyData.resize(origsize + -used);
+ if (maxConnectionDurationReached(d_maxConnectionDuration, start, remainingTime)) {
+ throw NetworkError("Error reading PROXYv2 header from TCP client "+remote.toString()+": maximum TCP connection duration exceeded");
+ }
+
+ try {
+ readnWithTimeout(fd, &proxyData[origsize], -used, d_idleTimeout, true, remainingTime);
+ }
+ catch(NetworkError& ae) {
+ throw NetworkError("Error reading PROXYv2 header from TCP client "+remote.toString()+": "+ae.what());
+ }
+ }
+ else if (used == 0) {
+ throw NetworkError("Error reading PROXYv2 header from TCP client "+remote.toString()+": PROXYv2 header was invalid");
+ }
+ else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+ throw NetworkError("Error reading PROXYv2 header from TCP client "+remote.toString()+": PROXYv2 header too big");
+ }
+ else { // used > 0 && used <= g_proxyProtocolMaximumSize
+ break;
+ }
+ }
+ ComboAddress psource, pdestination;
+ bool proxyProto, tcp;
+ std::vector<ProxyProtocolValue> ppvalues;
+
+ used = parseProxyHeader(proxyData, proxyProto, psource, pdestination, tcp, ppvalues);
+ if (used <= 0) {
+ throw NetworkError("Error reading PROXYv2 header from TCP client "+remote.toString()+": PROXYv2 header was invalid");
+ }
+ if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+ throw NetworkError("Error reading PROXYv2 header from TCP client "+remote.toString()+": PROXYv2 header was oversized");
+ }
+ inner_remote = psource;
+ inner_tcp = tcp;
+ }
+
for(;;) {
unsigned int remainingTime = 0;
transactions++;
packet=make_unique<DNSPacket>(true);
packet->setRemote(&remote);
packet->d_tcp=true;
+ if (inner_remote) {
+ packet->d_inner_remote = inner_remote;
+ packet->d_tcp = inner_tcp;
+ }
packet->setSocket(fd);
if(packet->parse(mesg.get(), pktlen)<0)
break;
std::unique_ptr<DNSPacket> reply;
auto cached = make_unique<DNSPacket>(false);
if(logDNSQueries) {
- string remote_text;
- if(packet->hasEDNSSubnet())
- remote_text = packet->getRemote().toString() + "<-" + packet->getRealRemote().toString();
- else
- remote_text = packet->getRemote().toString();
- g_log << Logger::Notice<<"TCP Remote "<< remote_text <<" wants '" << packet->qdomain<<"|"<<packet->qtype.toString() <<
+ g_log << Logger::Notice<<"TCP Remote "<< packet->getRemoteString() <<" wants '" << packet->qdomain<<"|"<<packet->qtype.toString() <<
"', do = " <<packet->d_dnssecOk <<", bufsize = "<< packet->getMaxReplyLen();
}
if(::arg().mustDo("disable-axfr"))
return false;
- string logPrefix=string(isAXFR ? "A" : "I")+"XFR-out zone '"+q->qdomain.toLogString()+"', client '"+q->getRemote().toStringWithPort()+"', ";
+ string logPrefix=string(isAXFR ? "A" : "I")+"XFR-out zone '"+q->qdomain.toLogString()+"', client '"+q->getInnerRemote().toStringWithPort()+"', ";
if(q->d_havetsig) { // if you have one, it must be good
TSIGRecordContent trc;
}
// cerr<<"checking allow-axfr-ips"<<endl;
- if(!(::arg()["allow-axfr-ips"].empty()) && d_ng.match( (ComboAddress *) &q->d_remote )) {
+ if(!(::arg()["allow-axfr-ips"].empty()) && d_ng.match( q->getInnerRemote() )) {
g_log<<Logger::Notice<<logPrefix<<"allowed: client IP is in allow-axfr-ips"<<endl;
return true;
}
vector<string> nsips=fns.lookup(j, s_P->getBackend());
for(const auto & nsip : nsips) {
// cerr<<"got "<<*k<<" from AUTO-NS"<<endl;
- if(nsip == q->getRemote().toString())
+ if(nsip == q->getInnerRemote().toString())
{
// cerr<<"got AUTO-NS hit"<<endl;
g_log<<Logger::Notice<<logPrefix<<"allowed: client IP is in NSset"<<endl;
else
{
Netmask nm = Netmask(i);
- if(nm.match( (ComboAddress *) &q->d_remote ))
+ if(nm.match( q->getInnerRemote() ))
{
g_log<<Logger::Notice<<logPrefix<<"allowed: client IP is in per-zone ACL"<<endl;
// cerr<<"hit!"<<endl;
extern CommunicatorClass Communicator;
- if(Communicator.justNotified(q->qdomain, q->getRemote().toString())) { // we just notified this ip
+ if(Communicator.justNotified(q->qdomain, q->getInnerRemote().toString())) { // we just notified this ip
g_log<<Logger::Notice<<logPrefix<<"allowed: client IP is from recently notified secondary"<<endl;
return true;
}
/** do the actual zone transfer. Return 0 in case of error, 1 in case of success */
int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock)
{
- string logPrefix="AXFR-out zone '"+target.toLogString()+"', client '"+q->getRemote().toStringWithPort()+"', ";
+ string logPrefix="AXFR-out zone '"+target.toLogString()+"', client '"+q->getRemoteString()+"', ";
std::unique_ptr<DNSPacket> outpacket= getFreshAXFRPacket(q);
if(q->d_dnssecOk)
int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
{
- string logPrefix="IXFR-out zone '"+q->qdomain.toLogString()+"', client '"+q->getRemote().toStringWithPort()+"', ";
+ string logPrefix="IXFR-out zone '"+q->qdomain.toLogString()+"', client '"+q->getRemoteString()+"', ";
std::unique_ptr<DNSPacket> outpacket=getFreshAXFRPacket(q);
if(q->d_dnssecOk)
private:
static void sendPacket(std::unique_ptr<DNSPacket>& p, int outsock, bool last=true);
- static int readLength(int fd, ComboAddress *remote);
static void getQuestion(int fd, char *mesg, int pktlen, const ComboAddress& remote, unsigned int totalTime);
static int doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock);
static int doIXFR(std::unique_ptr<DNSPacket>& q, int outsock);
#include <utility>
extern vector<ComboAddress> g_localaddresses;
+NetmaskGroup g_proxyProtocolACL;
+size_t g_proxyProtocolMaximumSize = 512;
BOOST_AUTO_TEST_SUITE(test_nameserver_cc)
--- /dev/null
+../regression-tests.common/proxyprotocol.py
\ No newline at end of file
--- /dev/null
+import dns
+import os
+import socket
+import struct
+import threading
+import time
+import unittest
+
+from authtests import AuthTest
+from proxyprotocol import ProxyProtocol
+
+class TestProxyProtocolLuaRecords(AuthTest):
+ _config_template = """
+launch=bind
+any-to-tcp=no
+proxy-protocol-from=127.0.0.1
+"""
+
+ _zones = {
+ 'example.org': """
+example.org. 3600 IN SOA {soa}
+example.org. 3600 IN NS ns1.example.org.
+example.org. 3600 IN NS ns2.example.org.
+ns1.example.org. 3600 IN A {prefix}.10
+ns2.example.org. 3600 IN A {prefix}.11
+
+myip.example.org. 3600 IN LUA A "who:toString()"
+ """
+ }
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestProxyProtocolLuaRecords, cls).setUpClass()
+
+ def testWhoAmI(self):
+ """
+ See if LUA who picks up the inner address from the PROXY protocol
+ """
+
+ # first test with an unproxied query - should get ignored
+ query = dns.message.make_query('myip.example.org', 'A')
+
+ res = self.sendUDPQuery(query)
+
+ self.assertEqual(res, None) # query was ignored correctly
+
+
+ # now send a proxied query
+ queryPayload = query.to_wire()
+ ppPayload = ProxyProtocol.getPayload(False, False, False, "192.0.2.1", "10.1.2.3", 12345, 53, [])
+ payload = ppPayload + queryPayload
+
+ # UDP
+ self._sock.settimeout(2.0)
+
+ try:
+ self._sock.send(payload)
+ data = self._sock.recv(4096)
+ except socket.timeout:
+ data = None
+ finally:
+ self._sock.settimeout(None)
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+
+ expected = [dns.rrset.from_text('myip.example.org.', 0, dns.rdataclass.IN, 'A', '192.0.2.1')]
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(res.answer, expected)
+
+ # TCP
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(2.0)
+ sock.connect(("127.0.0.1", self._authPort))
+
+ try:
+ sock.send(ppPayload)
+ sock.send(struct.pack("!H", len(queryPayload)))
+ sock.send(queryPayload)
+ data = sock.recv(2)
+ if data:
+ (datalen,) = struct.unpack("!H", data)
+ data = sock.recv(datalen)
+ except socket.timeout as e:
+ print("Timeout: %s" % (str(e)))
+ data = None
+ except socket.error as e:
+ print("Network error: %s" % (str(e)))
+ data = None
+ finally:
+ sock.close()
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(res.answer, expected)
+
+class TestProxyProtocolNOTIFY(AuthTest):
+ _config_template = """
+launch=bind
+any-to-tcp=no
+proxy-protocol-from=127.0.0.1
+secondary
+"""
+
+ _zones = { 'example.org': '192.0.2.1',
+ 'example.com': '192.0.2.2'
+ }
+
+ @classmethod
+ def generateAuthZone(cls, confdir, zonename, zonecontent):
+ try:
+ os.unlink(os.path.join(confdir, '%s.zone' % zonename))
+ except:
+ pass
+
+ @classmethod
+ def generateAuthNamedConf(cls, confdir, zones):
+ with open(os.path.join(confdir, 'named.conf'), 'w') as namedconf:
+ namedconf.write("""
+options {
+ directory "%s";
+};""" % confdir)
+ for zonename in zones:
+ zone = '.' if zonename == 'ROOT' else zonename
+
+ namedconf.write("""
+ zone "%s" {
+ type slave;
+ file "%s.zone";
+ masters { %s; };
+ };""" % (zone, zonename, cls._zones[zone]))
+
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestProxyProtocolNOTIFY, cls).setUpClass()
+
+ def testNOTIFY(self):
+ """
+ Check that NOTIFY is properly accepted/rejected based on the PROXY header inner address
+ """
+
+ query = dns.message.make_query('example.org', 'SOA')
+ query.set_opcode(dns.opcode.NOTIFY)
+
+ queryPayload = query.to_wire()
+
+ for task in ('192.0.2.1', dns.rcode.NOERROR), ('192.0.2.2', dns.rcode.REFUSED):
+ ip, expectedrcode = task
+
+ ppPayload = ProxyProtocol.getPayload(False, False, False, ip, "10.1.2.3", 12345, 53, [])
+ payload = ppPayload + queryPayload
+
+ self._sock.settimeout(2.0)
+
+ try:
+ self._sock.send(payload)
+ data = self._sock.recv(4096)
+ except socket.timeout:
+ data = None
+ finally:
+ self._sock.settimeout(None)
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+
+ self.assertRcodeEqual(res, expectedrcode)
+
+
+class TestProxyProtocolAXFRACL(AuthTest):
+ _config_template = """
+launch=bind
+any-to-tcp=no
+proxy-protocol-from=127.0.0.1
+allow-axfr-ips=192.0.2.53
+"""
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestProxyProtocolAXFRACL, cls).setUpClass()
+
+ def testAXFR(self):
+ """
+ Check that AXFR is properly accepted/rejected based on the PROXY header inner address
+ """
+
+ query = dns.message.make_query('example.org', 'AXFR')
+
+ queryPayload = query.to_wire()
+
+ for task in ('192.0.2.1', dns.rcode.NOTAUTH), ('127.0.0.1', dns.rcode.NOTAUTH), ('192.0.2.53', dns.rcode.NOERROR):
+ ip, expectedrcode = task
+
+ ppPayload = ProxyProtocol.getPayload(False, True, False, ip, "10.1.2.3", 12345, 53, [])
+
+ # TCP
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(2.0)
+ sock.connect(("127.0.0.1", self._authPort))
+
+ try:
+ sock.send(ppPayload)
+ sock.send(struct.pack("!H", len(queryPayload)))
+ sock.send(queryPayload)
+ data = sock.recv(2)
+ if data:
+ (datalen,) = struct.unpack("!H", data)
+ data = sock.recv(datalen)
+ except socket.timeout as e:
+ print("Timeout: %s" % (str(e)))
+ data = None
+ except socket.error as e:
+ print("Network error: %s" % (str(e)))
+ data = None
+ finally:
+ sock.close()
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+
+ self.assertRcodeEqual(res, expectedrcode)