case "$1" in
configure)
- addgroup --system pdns
- adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+ if [ -z "`getent group pdns`" ]; then
+ addgroup --system pdns
+ fi
+ if [ -z "`getent passwd pdns`" ]; then
+ adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+ fi
if [ "`stat -c '%U:%G' /etc/powerdns/recursor.conf`" = "root:root" ]; then
chown root:pdns /etc/powerdns/recursor.conf
# Make sure that pdns can read it; the default used to be 0600
--with-libcap \
--with-libsodium \
--with-protobuf=yes \
+ --enable-dnstap \
--without-net-snmp \
--disable-silent-rules \
--with-service-user=pdns \
case "$1" in
configure)
- addgroup --system pdns
- adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+ if [ -z "`getent group pdns`" ]; then
+ addgroup --system pdns
+ fi
+ if [ -z "`getent passwd pdns`" ]; then
+ adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+ fi
if [ "`stat -c '%U:%G' /etc/powerdns/recursor.conf`" = "root:root" ]; then
chown root:pdns /etc/powerdns/recursor.conf
# Make sure that pdns can read it; the default used to be 0600
libcap-dev,
libluajit-5.1-dev,
libprotobuf-dev,
+ libfstrm-dev,
libsodium-dev,
libssl-dev,
libsystemd-dev [linux-any],
case "$1" in
configure)
- addgroup --system pdns
- adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+ if [ -z "`getent group pdns`" ]; then
+ addgroup --system pdns
+ fi
+ if [ -z "`getent passwd pdns`" ]; then
+ adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+ fi
if [ "`stat -c '%U:%G' /etc/powerdns/recursor.conf`" = "root:root" ]; then
chown root:pdns /etc/powerdns/recursor.conf
# Make sure that pdns can read it; the default used to be 0600
--with-libcap \
--with-libsodium \
--with-protobuf=yes \
+ --enable-dnstap \
--without-net-snmp \
--disable-silent-rules \
--with-service-user=pdns \
@INCLUDE Dockerfile.debbuild-prepare
@IF [ ! -z "$M_authoritative" ]
-ADD builder-support/debian/authoritative/debian-stretch/ pdns-${BUILDER_VERSION}/debian/
+ADD builder-support/debian/authoritative/debian-buster/ pdns-${BUILDER_VERSION}/debian/
@ENDIF
@IF [ ! -z "$M_recursor" ]
-ADD builder-support/debian/recursor/debian-stretch/ pdns-recursor-${BUILDER_VERSION}/debian/
+ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSION}/debian/
@ENDIF
@IF [ ! -z "$M_dnsdist" ]
%endif
%if 0%{?rhel} >= 7
Requires(pre): shadow-utils
-%if 0%{?rhel} == 7
-# No fstrm in EPEL 8 (yet) https://bugzilla.redhat.com/show_bug.cgi?id=1760298
BuildRequires: fstrm-devel
-%endif
%systemd_requires
%endif
--without-protobuf \
--without-net-snmp
%endif
-%if 0%{?rhel} == 7
- --enable-dnstap \
-%endif
%if 0%{?rhel} >= 7
--with-gnutls \
--with-protobuf \
+ --enable-dnstap \
--with-lua=%{lua_implementation} \
--with-libcap \
--with-libsodium \
%if 0%{?rhel} >= 7
BuildRequires: protobuf-compiler
BuildRequires: protobuf-devel
-
-%if 0%{?rhel} == 7
-# No fstrm in EPEL 8 yet
BuildRequires: fstrm-devel
%endif
-%endif
BuildRequires: openssl-devel
BuildRequires: net-snmp-devel
make %{?_smp_mflags} LIBRARY_PATH=/usr/lib64/boost148
%else
--with-protobuf \
+ --enable-dnstap \
--with-libcap \
--with-lua=%{lua_implementation} \
--enable-systemd --with-systemd=%{_unitdir}
datestr = datestr + '.' + str(msg.timeUsec)
ipfromstr = 'N/A'
iptostr = 'N/A'
+ toportstr = ''
+ fromportstr = ''
fromvalue = getattr(msg, 'from')
if msg.socketFamily == dnsmessage_pb2.PBDNSMessage.INET:
if msg.HasField('from'):
iptostr = socket.inet_ntop(socket.AF_INET, msg.to)
else:
if msg.HasField('from'):
- ipfromstr = socket.inet_ntop(socket.AF_INET6, fromvalue)
+ ipfromstr = '[' + socket.inet_ntop(socket.AF_INET6, fromvalue) + ']'
if msg.HasField('to'):
- iptostr = socket.inet_ntop(socket.AF_INET6, msg.to)
+ iptostr = '[' + socket.inet_ntop(socket.AF_INET6, msg.to) + ']'
if msg.socketProtocol == dnsmessage_pb2.PBDNSMessage.UDP:
protostr = 'UDP'
else:
protostr = 'TCP'
+ if msg.HasField('fromPort'):
+ fromportstr = ':' + str(msg.fromPort) + ' '
+
+ if msg.HasField('toPort'):
+ toportstr = ':' + str(msg.toPort) + ' '
+
messageidstr = binascii.hexlify(bytearray(msg.messageId))
serveridstr = 'N/A'
if (msg.HasField('newlyObservedDomain')):
nod = msg.newlyObservedDomain
- print('[%s] %s of size %d: %s%s -> %s (%s), id: %d, uuid: %s%s '
+ print('[%s] %s of size %d: %s%s%s -> %s%s (%s), id: %d, uuid: %s%s '
'requestorid: %s deviceid: %s serverid: %s nod: %d' % (datestr,
typestr,
msg.inBytes,
ipfromstr,
+ fromportstr,
requestorstr,
iptostr,
+ toportstr,
protostr,
msg.id,
messageidstr,
Methods
^^^^^^^
+Methods required for different features
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+:Always required: ``initialize``, ``lookup``
+:Master operation: ``list``, ``getUpdatedMasters``, ``setNotified``
+
``initialize``
~~~~~~~~~~~~~~
``createSlaveDomain``
~~~~~~~~~~~~~~~~~~~~~
-
Creates new domain. This method is called when NOTIFY is received and
you are superslaving.
-Mandatory: No Parameters: ip, domain Optional parameters: nameserver,
-account Reply: true for success, false for failure
+ - Mandatory: No
+ - Parameters: ip, domain
+ - Optional parameters: nameserver, account
+ - Reply: true for success, false for failure
Example JSON/RPC
''''''''''''''''
- Mandatory: no
- Parameters: none
-- Reply: array of DomainInfo
+- Reply: array of DomainInfo or at least the ``id``, ``zone``, ``serial`` and ``notified_serial`` fields
Example JSON/RPC
''''''''''''''''
AXFR-serving, a lot of signing needs to happen.
Most best practices are documented in :rfc:`6781`.
+
+Some notes on TTL usage
+-----------------------
+
+In zones signed by PowerDNS (so non-presigned zones), some TTL values need to be filled in by PowerDNS.
+The TTL of RRSIG record sets is the TTL of the covered RRset.
+For CDS, CDNSKEY, DNSKEY, NSEC, NSEC3 and NSEC3PARAM, we use the SOA default TTL (the last number in the SOA record).
+Except for CDS/CDNSKEY/DNSKEY, these TTLs are chosen because `RFC 4034 <https://tools.ietf.org/html/rfc4034>`__ demands it so.
+
+If you want a 'normal' TTL (3600, 86400, etc.) for your DNSKEY but a low TTL on negative answers, set your SOA default TTL to the high number, and set the TTL on the SOA record itself to the low TTL you want for negative answers.
+Note that the NSEC/NSEC3 records proving those negatives will get the high TTL in that case, and this may affect subsequent resolution in resolvers that do aggressive NSEC caching (`RFC 8198 <https://tools.ietf.org/html/rfc8198>`__).
\ No newline at end of file
-@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019121201 10800 3600 604800 10800
+@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2020011600 10800 3600 604800 10800
@ 3600 IN NS pdns-public-ns1.powerdns.com.
@ 3600 IN NS pdns-public-ns2.powerdns.com.
recursor-4.3.0-alpha1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
recursor-4.3.0-alpha2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
recursor-4.3.0-alpha3.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-beta1.security-status 60 IN TXT "1 OK"
+recursor-4.3.0-beta1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+recursor-4.3.0-beta2.security-status 60 IN TXT "1 OK"
; Recursor Debian
recursor-3.6.2-2.debian.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/"
namespace YaHTTP {
+ template class AsyncLoader<Request>;
+
bool isspace(char c) {
return std::isspace(c) != 0;
}
class SPgSQLStatement: public SSqlStatement
{
public:
- SPgSQLStatement(const string& query, bool dolog, int nparams, SPgSQL* db, unsigned int nstatement) {
+ SPgSQLStatement(const string& query, bool dolog, int nparams, SPgSQL* db) {
d_query = query;
d_dolog = dolog;
d_parent = db;
d_res_set = NULL;
paramValues = NULL;
paramLengths = NULL;
- d_nstatement = nstatement;
d_paridx = 0;
d_residx = 0;
d_resnum = 0;
g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": " << d_query << endl;
d_dtime.set();
}
- d_res_set = PQexecPrepared(d_db(), d_stmt.c_str(), d_nparams, paramValues, paramLengths, NULL, 0);
+ d_res_set = PQexecParams(d_db(), d_query.c_str(), d_nparams, NULL, paramValues, paramLengths, NULL, 0);
ExecStatusType status = PQresultStatus(d_res_set);
if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
string errmsg(PQresultErrorMessage(d_res_set));
void releaseStatement() {
d_prepared = false;
reset();
- if (!d_stmt.empty()) {
- string cmd = string("DEALLOCATE " + d_stmt);
- PGresult *res = PQexec(d_db(), cmd.c_str());
- PQclear(res);
- d_stmt.clear();
- }
}
void prepareStatement() {
if (d_prepared) return;
- // prepare a statement; name must be unique per session (using d_nstatement to ensure this).
- this->d_stmt = string("stmt") + std::to_string(d_nstatement);
- PGresult* res = PQprepare(d_db(), d_stmt.c_str(), d_query.c_str(), d_nparams, NULL);
- ExecStatusType status = PQresultStatus(res);
- string errmsg(PQresultErrorMessage(res));
- PQclear(res);
- if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
- releaseStatement();
- throw SSqlException("Fatal error during prepare: " + d_query + string(": ") + errmsg);
- }
paramValues=NULL;
d_cur_set=d_paridx=d_residx=d_resnum=d_fnum=0;
paramLengths=NULL;
}
string d_query;
- string d_stmt;
SPgSQL *d_parent;
PGresult *d_res_set;
PGresult *d_res;
int d_resnum;
int d_fnum;
int d_cur_set;
- unsigned int d_nstatement;
};
bool SPgSQL::s_dolog;
d_db=0;
d_in_trx = false;
d_connectstr="";
- d_nstatement = 0;
if (!database.empty())
d_connectstr+="dbname="+database;
std::unique_ptr<SSqlStatement> SPgSQL::prepare(const string& query, int nparams)
{
- d_nstatement++;
- return std::unique_ptr<SSqlStatement>(new SPgSQLStatement(query, s_dolog, nparams, this, d_nstatement));
+ return std::unique_ptr<SSqlStatement>(new SPgSQLStatement(query, s_dolog, nparams, this));
}
void SPgSQL::startTransaction() {
string d_connectlogstr;
static bool s_dolog;
bool d_in_trx;
- unsigned int d_nstatement;
};
#endif /* SPGSQL_HH */
bool LMDBBackend::list(const DNSName &target, int id, bool include_disabled)
{
- d_inlist=true;
DomainInfo di;
{
auto dtxn = d_tdomains->getROTransaction();
-
if((di.id = dtxn.get<0>(target, di)))
; // cout<<"Found domain "<<target<<" on domain_id "<<di.id <<", list requested "<<id<<endl;
else {
return false;
}
}
-
+
d_rotxn = getRecordsROTransaction(di.id, d_rwtxn);
+ d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
+
compoundOrdername co;
d_matchkey = co(di.id);
- d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
+
MDBOutVal key, val;
- d_inlist = true;
-
if(d_getcursor->lower_bound(d_matchkey, key, val) || key.get<StringView>().rfind(d_matchkey, 0) != 0) {
// cout<<"Found nothing for list"<<endl;
d_getcursor.reset();
- return true;
}
-
- d_lookupqname = target;
-
+
+ d_lookupdomain = target;
return true;
}
g_log << Logger::Warning << "Got lookup for "<<qdomain<<"|"<<type.getName()<<" in zone "<< zoneId<<endl;
d_dtime.set();
}
+
DNSName hunt(qdomain);
DomainInfo di;
if(zoneId < 0) {
d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
MDBOutVal key, val;
if(type.getCode() == QType::ANY) {
- d_matchkey = co(zoneId,relqname);
+ d_matchkey = co(zoneId, relqname);
}
else {
- d_matchkey= co(zoneId,relqname, type.getCode());
+ d_matchkey= co(zoneId, relqname, type.getCode());
}
- d_inlist=false;
if(d_getcursor->lower_bound(d_matchkey, key, val) || key.get<StringView>().rfind(d_matchkey, 0) != 0) {
d_getcursor.reset();
if(d_dolog) {
g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": "<<d_dtime.udiffNoReset()<<" usec to execute"<<endl;
}
-
- d_lookuptype=type;
- d_lookupqname = qdomain;
+
d_lookupdomain = hunt;
- d_lookupdomainid = zoneId;
}
-bool LMDBBackend::get(DNSZoneRecord& rr)
-{
- if(d_inlist)
- return get_list(rr);
- else
- return get_lookup(rr);
-}
-bool LMDBBackend::get(DNSResourceRecord& rr)
-{
- // cout <<"Old-school get called"<<endl;
- DNSZoneRecord dzr;
- if(d_inlist) {
- if(!get_list(dzr))
- return false;
- }
- else {
- if(!get_lookup(dzr))
- return false;
- }
- rr.qname = dzr.dr.d_name;
- rr.ttl = dzr.dr.d_ttl;
- rr.qtype =dzr.dr.d_type;
- rr.content = dzr.dr.d_content->getZoneRepresentation(true);
- rr.domain_id = dzr.domain_id;
- rr.auth = dzr.auth;
- // cout<<"old school called for "<<rr.qname<<", "<<rr.qtype.getName()<<endl;
- return true;
-}
-
-bool LMDBBackend::getSOA(const DNSName &domain, SOAData &sd)
-{
- // cout <<"Native getSOA called"<<endl;
- lookup(QType(QType::SOA), domain, -1);
- DNSZoneRecord dzr;
- bool found=false;
- while(get(dzr)) {
- auto src = getRR<SOARecordContent>(dzr.dr);
- sd.domain_id = dzr.domain_id;
- sd.ttl = dzr.dr.d_ttl;
- sd.qname = dzr.dr.d_name;
-
- sd.nameserver = src->d_mname;
- sd.hostmaster = src->d_rname;
- sd.serial = src->d_st.serial;
- sd.refresh = src->d_st.refresh;
- sd.retry = src->d_st.retry;
- sd.expire = src->d_st.expire;
- sd.default_ttl = src->d_st.minimum;
-
- sd.db = this;
- found=true;
- }
- return found;
-}
-bool LMDBBackend::get_list(DNSZoneRecord& rr)
+bool LMDBBackend::get(DNSZoneRecord& rr)
{
for(;;) {
- if(!d_getcursor) {
+ if(!d_getcursor) {
d_rotxn.reset();
return false;
}
-
- MDBOutVal keyv, val;
+ MDBOutVal keyv, val;
d_getcursor->current(keyv, val);
+
DNSResourceRecord drr;
serFromString(val.get<string>(), drr);
-
+
auto key = keyv.get<string_view>();
- rr.dr.d_name = compoundOrdername::getQName(key) + d_lookupqname;
- rr.domain_id = compoundOrdername::getDomainID(key);
rr.dr.d_type = compoundOrdername::getQType(key).getCode();
- rr.dr.d_ttl = drr.ttl;
- rr.auth = drr.auth;
-
+
if(rr.dr.d_type == QType::NSEC3) {
- // cout << "Had a magic NSEC3, skipping it" << endl;
+ // Hit a magic NSEC3 skipping
if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
d_getcursor.reset();
}
+
continue;
}
+
+ rr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain;
+ rr.domain_id = compoundOrdername::getDomainID(key);
+ rr.dr.d_ttl = drr.ttl;
rr.dr.d_content = unserializeContentZR(rr.dr.d_type, rr.dr.d_name, drr.content);
-
+ rr.auth = drr.auth;
+
if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
d_getcursor.reset();
}
+
break;
}
+
return true;
}
-bool LMDBBackend::get_lookup(DNSZoneRecord& rr)
+bool LMDBBackend::get(DNSResourceRecord& rr)
{
- for(;;) {
- if(!d_getcursor) {
- d_rotxn.reset();
- return false;
- }
- MDBOutVal keyv, val;
- d_getcursor->current(keyv, val);
- DNSResourceRecord drr;
- serFromString(val.get<string>(), drr);
-
- auto key = keyv.get<string_view>();
-
- rr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain;
-
- rr.domain_id = compoundOrdername::getDomainID(key);
- // cout << "We found "<<rr.qname<< " in zone id "<<rr.domain_id <<endl;
- rr.dr.d_type = compoundOrdername::getQType(key).getCode();
- rr.dr.d_ttl = drr.ttl;
- if(rr.dr.d_type == QType::NSEC3) {
- // cout << "Hit a magic NSEC3 skipping" << endl;
- if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
- d_getcursor.reset();
- d_rotxn.reset();
- }
- continue;
- }
-
- rr.dr.d_content = unserializeContentZR(rr.dr.d_type, rr.dr.d_name, drr.content);
- rr.auth = drr.auth;
- if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
- d_getcursor.reset();
- d_rotxn.reset();
- }
- break;
+ DNSZoneRecord dzr;
+ if(!get(dzr)) {
+ return false;
}
-
+ rr.qname = dzr.dr.d_name;
+ rr.ttl = dzr.dr.d_ttl;
+ rr.qtype = dzr.dr.d_type;
+ rr.content = dzr.dr.d_content->getZoneRepresentation(true);
+ rr.domain_id = dzr.domain_id;
+ rr.auth = dzr.auth;
+
return true;
}
bool get(DNSResourceRecord &rr) override;
bool get(DNSZoneRecord& dzr) override;
- bool getSOA(const DNSName &domain, SOAData &sd) override;
void getUnfreshSlaveInfos(vector<DomainInfo>* domains) override;
bool setMaster(const DNSName &domain, const string &ip) override;
bool get_list(DNSZoneRecord &rr);
bool get_lookup(DNSZoneRecord &rr);
- bool d_inlist{false};
- QType d_lookuptype; // for get after lookup
std::string d_matchkey;
- int32_t d_lookupdomainid; // for get after lookup
- DNSName d_lookupqname;
DNSName d_lookupdomain;
DNSName d_transactiondomain;
}
}
+void RemoteBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
+{
+ std::vector<std::string> meta;
+ getDomainMetadata(domain, "ALSO-NOTIFY", meta);
+ ips->insert(meta.begin(), meta.end());
+}
+
void RemoteBackend::getUpdatedMasters(vector<DomainInfo>* domains)
{
Json query = Json::object{
bool searchComments(const string &pattern, int maxResults, vector<Comment>& result) override;
void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false) override;
void getUpdatedMasters(vector<DomainInfo>* domains) override;
+ void alsoNotifies(const DNSName &domain, set<string> *ips) override;
static DNSBackend *maker();
BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"TEST", meta));
}
+BOOST_AUTO_TEST_CASE(test_method_alsoNotifies) {
+ BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"ALSO-NOTIFY", {"192.0.2.1"}));
+ std::set<std::string> alsoNotifies;
+ BOOST_TEST_MESSAGE("Testing alsoNotifies method");
+ be->alsoNotifies(DNSName("unit.test."), &alsoNotifies);
+ BOOST_CHECK_EQUAL(alsoNotifies.size(), 1);
+ if (alsoNotifies.size() > 0)
+ BOOST_CHECK_EQUAL(alsoNotifies.count("192.0.2.1"), 1);
+ BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"ALSO-NOTIFY", std::vector<std::string>()));
+}
+
BOOST_AUTO_TEST_CASE(test_method_getDomainMetadata) {
std::vector<std::string> meta;
BOOST_TEST_MESSAGE("Testing getDomainMetadata method");
continue;
}
+ moveCacheItemToBack<SequencedTag>(mc.d_map, iter);
iter->value = entry.value;
iter->ttd = now + ourttl;
iter->created = now;
/* no existing entry found to refresh */
mc.d_map.insert(entry);
- (*d_statnumentries)++;
+
+ if (*d_statnumentries >= d_maxEntries) {
+ /* remove the least recently inserted or replaced entry */
+ auto& sidx = mc.d_map.get<SequencedTag>();
+ sidx.pop_front();
+ }
+ else {
+ (*d_statnumentries)++;
+ }
}
}
indexed_by <
hashed_non_unique<tag<HashTag>, member<CacheEntry,uint32_t,&CacheEntry::hash> >,
ordered_non_unique<tag<NameTag>, member<CacheEntry,DNSName,&CacheEntry::qname>, CanonDNSNameCompare >,
+ /* Note that this sequence holds 'least recently inserted or replaced', not least recently used.
+ Making it a LRU would require taking a write-lock when fetching from the cache, making the RW-lock inefficient compared to a mutex */
sequenced<tag<SequencedTag>>
>
> cmap_t;
if (!inserted) {
mc.d_map.replace(place, val);
+ moveCacheItemToBack<SequencedTag>(mc.d_map, place);
}
else {
- (*d_statnumentries)++;
+ if (*d_statnumentries >= d_maxEntries) {
+ /* remove the least recently inserted or replaced entry */
+ auto& sidx = mc.d_map.get<SequencedTag>();
+ sidx.pop_front();
+ }
+ else {
+ (*d_statnumentries)++;
+ }
}
}
}
member<CacheEntry,uint16_t,&CacheEntry::qtype>,
member<CacheEntry,int, &CacheEntry::zoneID> > > ,
ordered_non_unique<tag<NameTag>, member<CacheEntry,DNSName,&CacheEntry::qname>, CanonDNSNameCompare >,
+ /* Note that this sequence holds 'least recently inserted or replaced', not least recently used.
+ Making it a LRU would require taking a write-lock when fetching from the cache, making the RW-lock inefficient compared to a mutex */
sequenced<tag<SequencedTag>>
>
> cmap_t;
"', do = " <<question.d_dnssecOk <<", bufsize = "<< question.getMaxReplyLen();
if(question.d_ednsRawPacketSizeLimit > 0 && question.getMaxReplyLen() != (unsigned int)question.d_ednsRawPacketSizeLimit)
g_log<<" ("<<question.d_ednsRawPacketSizeLimit<<")";
- g_log<<": ";
}
if(PC.enabled() && (question.d.opcode != Opcode::Notify && question.d.opcode != Opcode::Update) && question.couldBeCached()) {
bool haveSomething=PC.get(question, cached); // does the PacketCache recognize this question?
if (haveSomething) {
if(logDNSQueries)
- g_log<<"packetcache HIT"<<endl;
+ g_log<<": packetcache HIT"<<endl;
cached.setRemote(&question.d_remote); // inlined
cached.setSocket(question.getSocket()); // inlined
cached.d_anyLocal = question.d_anyLocal;
}
if(distributor->isOverloaded()) {
- if(logDNSQueries)
- g_log<<"Dropped query, backends are overloaded"<<endl;
+ if(logDNSQueries)
+ g_log<<": Dropped query, backends are overloaded"<<endl;
overloadDrops++;
continue;
}
-
- if(PC.enabled() && logDNSQueries)
- g_log<<"packetcache MISS"<<endl;
+
+ if (logDNSQueries) {
+ if (PC.enabled()) {
+ g_log<<": packetcache MISS"<<endl;
+ } else {
+ g_log<<endl;
+ }
+ }
try {
distributor->question(question, &sendout); // otherwise, give to the distributor
PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
QC.setMaxEntries(::arg().asNum("max-cache-entries"));
+ if (!PC.enabled() && ::arg().mustDo("log-dns-queries")) {
+ g_log<<Logger::Warning<<"Packet cache disabled, logging queries without HIT/MISS"<<endl;
+ }
+
stubParseResolveConf();
if(!::arg()["chroot"].empty()) {
{ "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
{ "getResponseRing", true, "", "return the current content of the response ring" },
{ "getRespRing", true, "", "return the qname/rcode content of the response ring" },
- { "getServer", true, "n", "returns server with index n" },
+ { "getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string" },
{ "getServers", true, "", "returns a table with all defined servers" },
{ "getStatisticsCounters", true, "", "returns a map of statistic counters" },
{ "getTLSContext", true, "n", "returns the TLS context with index n" },
{ "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
{ "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
{ "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
- { "rmServer", true, "n", "remove server with index n" },
+ { "rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string" },
{ "roundrobin", false, "", "Simple round robin over available servers" },
{ "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
{ "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
{ "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
{ "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
{ "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
- { "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
- { "SpoofCNAMEAction", true, "cname", "Forge a response with the specified CNAME value" },
+ { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
+ { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
{ "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
{ "TagAction", true, "name, value", "set the tag named 'name' to the given value" },
{ "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
bool d_enabled{false};
};
+ struct DynBlockRatioRule: DynBlockRule
+ {
+ DynBlockRatioRule(): DynBlockRule()
+ {
+ }
+
+ DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses): DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio)
+ {
+ }
+
+ bool ratioExceeded(unsigned int total, unsigned int count) const
+ {
+ if (!d_enabled) {
+ return false;
+ }
+
+ if (total < d_minimumNumberOfResponses) {
+ return false;
+ }
+
+ double allowed = d_ratio * static_cast<double>(total);
+ return (count > allowed);
+ }
+
+ bool warningRatioExceeded(unsigned int total, unsigned int count) const
+ {
+ if (d_warningRate == 0) {
+ return false;
+ }
+
+ if (total < d_minimumNumberOfResponses) {
+ return false;
+ }
+
+ double allowed = d_warningRatio * static_cast<double>(total);
+ return (count > allowed);
+ }
+
+ std::string toString() const
+ {
+ if (!isEnabled()) {
+ return "";
+ }
+
+ std::stringstream result;
+ if (d_action != DNSAction::Action::None) {
+ result << DNSAction::typeToString(d_action) << " ";
+ }
+ else {
+ result << "Apply the global DynBlock action ";
+ }
+ result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
+
+ return result.str();
+ }
+
+ size_t d_minimumNumberOfResponses{0};
+ double d_ratio{0.0};
+ double d_warningRatio{0.0};
+ };
+
typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
public:
entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
}
+ void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
+ {
+ auto& entry = d_rcodeRatioRules[rcode];
+ entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses);
+ }
+
void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
{
auto& entry = d_qtypeRules[qtype];
apply(now);
}
- void apply(const struct timespec& now)
- {
- counts_t counts;
- StatNode statNodeRoot;
-
- size_t entriesCount = 0;
- if (hasQueryRules()) {
- entriesCount += g_rings.getNumberOfQueryEntries();
- }
- if (hasResponseRules()) {
- entriesCount += g_rings.getNumberOfResponseEntries();
- }
- counts.reserve(entriesCount);
-
- processQueryRules(counts, now);
- processResponseRules(counts, statNodeRoot, now);
-
- if (counts.empty() && statNodeRoot.empty()) {
- return;
- }
-
- boost::optional<NetmaskTree<DynBlock> > blocks;
- bool updated = false;
-
- for (const auto& entry : counts) {
- const auto& requestor = entry.first;
- const auto& counters = entry.second;
-
- if (d_queryRateRule.warningRateExceeded(counters.queries, now)) {
- handleWarning(blocks, now, requestor, d_queryRateRule, updated);
- }
-
- if (d_queryRateRule.rateExceeded(counters.queries, now)) {
- addBlock(blocks, now, requestor, d_queryRateRule, updated);
- continue;
- }
-
- if (d_respRateRule.warningRateExceeded(counters.respBytes, now)) {
- handleWarning(blocks, now, requestor, d_respRateRule, updated);
- }
-
- if (d_respRateRule.rateExceeded(counters.respBytes, now)) {
- addBlock(blocks, now, requestor, d_respRateRule, updated);
- continue;
- }
-
- for (const auto& pair : d_qtypeRules) {
- const auto qtype = pair.first;
-
- const auto& typeIt = counters.d_qtypeCounts.find(qtype);
- if (typeIt != counters.d_qtypeCounts.cend()) {
-
- if (pair.second.warningRateExceeded(typeIt->second, now)) {
- handleWarning(blocks, now, requestor, pair.second, updated);
- }
-
- if (pair.second.rateExceeded(typeIt->second, now)) {
- addBlock(blocks, now, requestor, pair.second, updated);
- break;
- }
- }
- }
-
- for (const auto& pair : d_rcodeRules) {
- const auto rcode = pair.first;
-
- const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
- if (rcodeIt != counters.d_rcodeCounts.cend()) {
- if (pair.second.warningRateExceeded(rcodeIt->second, now)) {
- handleWarning(blocks, now, requestor, pair.second, updated);
- }
-
- if (pair.second.rateExceeded(rcodeIt->second, now)) {
- addBlock(blocks, now, requestor, pair.second, updated);
- break;
- }
- }
- }
- }
-
- if (updated && blocks) {
- g_dynblockNMG.setState(std::move(*blocks));
- }
-
- if (!statNodeRoot.empty()) {
- StatNode::Stat node;
- std::unordered_set<DNSName> namesToBlock;
- statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
- bool block = false;
-
- if (d_smtVisitorFFI) {
- dnsdist_ffi_stat_node_t tmp(*node_, self, children);
- block = d_smtVisitorFFI(&tmp);
- }
- else {
- block = d_smtVisitor(*node_, self, children);
- }
-
- if (block) {
- namesToBlock.insert(DNSName(node_->fullname));
- }
- },
- node);
-
- if (!namesToBlock.empty()) {
- updated = false;
- SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
- for (const auto& name : namesToBlock) {
- addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated);
- }
- if (updated) {
- g_dynblockSMT.setState(std::move(smtBlocks));
- }
- }
- }
- }
+ void apply(const struct timespec& now);
void excludeRange(const Netmask& range)
{
for (const auto& rule : d_rcodeRules) {
result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
}
+ for (const auto& rule : d_rcodeRatioRules) {
+ result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
+ }
result << "QType rules: " << std::endl;
for (const auto& rule : d_qtypeRules) {
result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl;
}
private:
- bool checkIfQueryTypeMatches(const Rings::Query& query)
- {
- auto rule = d_qtypeRules.find(query.qtype);
- if (rule == d_qtypeRules.end()) {
- return false;
- }
-
- return rule->second.matches(query.when);
- }
-
- bool checkIfResponseCodeMatches(const Rings::Response& response)
- {
- auto rule = d_rcodeRules.find(response.dh.rcode);
- if (rule == d_rcodeRules.end()) {
- return false;
- }
-
- return rule->second.matches(response.when);
- }
-
- void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning)
- {
- if (d_excludedSubnets.match(requestor)) {
- /* do not add a block for excluded subnets */
- return;
- }
-
- if (!blocks) {
- blocks = g_dynblockNMG.getCopy();
- }
- struct timespec until = now;
- until.tv_sec += rule.d_blockDuration;
- unsigned int count = 0;
- const auto& got = blocks->lookup(Netmask(requestor));
- bool expired = false;
- bool wasWarning = false;
-
- if (got) {
- if (warning && !got->second.warning) {
- /* we have an existing entry which is not a warning,
- don't override it */
- return;
- }
- else if (!warning && got->second.warning) {
- wasWarning = true;
- }
- else {
- if (until < got->second.until) {
- // had a longer policy
- return;
- }
- }
- if (now < got->second.until) {
- // only inherit count on fresh query we are extending
- count = got->second.blocks;
- }
- else {
- expired = true;
- }
- }
-
- DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
- db.blocks = count;
- db.warning = warning;
- if (!d_beQuiet && (!got || expired || wasWarning)) {
- warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
- }
- blocks->insert(Netmask(requestor)).second = db;
- updated = true;
- }
-
- void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
- {
- if (d_excludedDomains.check(name)) {
- /* do not add a block for excluded domains */
- return;
- }
-
- struct timespec until = now;
- until.tv_sec += rule.d_blockDuration;
- unsigned int count = 0;
- const auto& got = blocks.lookup(name);
- bool expired = false;
- DNSName domain(name.makeLowerCase());
-
- if (got) {
- if (until < got->until) {
- // had a longer policy
- return;
- }
-
- if (now < got->until) {
- // only inherit count on fresh query we are extending
- count = got->blocks;
- }
- else {
- expired = true;
- }
- }
-
- DynBlock db{rule.d_blockReason, until, domain, rule.d_action};
- db.blocks = count;
-
- if (!d_beQuiet && (!got || expired)) {
- warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, rule.d_blockDuration, rule.d_blockReason);
- }
- blocks.add(domain, db);
- updated = true;
- }
+ bool checkIfQueryTypeMatches(const Rings::Query& query);
+ bool checkIfResponseCodeMatches(const Rings::Response& response);
+ void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning);
+ void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
{
bool hasResponseRules() const
{
- return d_respRateRule.isEnabled() || !d_rcodeRules.empty();
+ return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty();
}
bool hasSuffixMatchRules() const
return hasQueryRules() || hasResponseRules();
}
- void processQueryRules(counts_t& counts, const struct timespec& now)
- {
- if (!hasQueryRules()) {
- return;
- }
-
- d_queryRateRule.d_cutOff = d_queryRateRule.d_minTime = now;
- d_queryRateRule.d_cutOff.tv_sec -= d_queryRateRule.d_seconds;
-
- for (auto& rule : d_qtypeRules) {
- rule.second.d_cutOff = rule.second.d_minTime = now;
- rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
- }
-
- for (const auto& shard : g_rings.d_shards) {
- std::lock_guard<std::mutex> rl(shard->queryLock);
- for(const auto& c : shard->queryRing) {
- if (now < c.when) {
- continue;
- }
-
- bool qRateMatches = d_queryRateRule.matches(c.when);
- bool typeRuleMatches = checkIfQueryTypeMatches(c);
-
- if (qRateMatches || typeRuleMatches) {
- auto& entry = counts[c.requestor];
- if (qRateMatches) {
- entry.queries++;
- }
- if (typeRuleMatches) {
- entry.d_qtypeCounts[c.qtype]++;
- }
- }
- }
- }
- }
-
- void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now)
- {
- if (!hasResponseRules() && !hasSuffixMatchRules()) {
- return;
- }
-
- d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now;
- d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds;
-
- d_suffixMatchRule.d_cutOff = d_suffixMatchRule.d_minTime = now;
- d_suffixMatchRule.d_cutOff.tv_sec -= d_suffixMatchRule.d_seconds;
-
- for (auto& rule : d_rcodeRules) {
- rule.second.d_cutOff = rule.second.d_minTime = now;
- rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
- }
-
- for (const auto& shard : g_rings.d_shards) {
- std::lock_guard<std::mutex> rl(shard->respLock);
- for(const auto& c : shard->respRing) {
- if (now < c.when) {
- continue;
- }
-
- bool respRateMatches = d_respRateRule.matches(c.when);
- bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when);
- bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
-
- if (respRateMatches || rcodeRuleMatches) {
- auto& entry = counts[c.requestor];
- if (respRateMatches) {
- entry.respBytes += c.size;
- }
- if (rcodeRuleMatches) {
- entry.d_rcodeCounts[c.dh.rcode]++;
- }
- }
-
- if (suffixMatchRuleMatches) {
- root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, boost::none);
- }
- }
- }
- }
+ void processQueryRules(counts_t& counts, const struct timespec& now);
+ void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
std::map<uint8_t, DynBlockRule> d_rcodeRules;
+ std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
std::map<uint16_t, DynBlockRule> d_qtypeRules;
DynBlockRule d_queryRateRule;
DynBlockRule d_respRateRule;
{
dq->dh->rcode = d_rcode;
dq->dh->qr = true; // for good measure
+ setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
return Action::HeaderModify;
}
std::string toString() const override
return "set rcode "+std::to_string(d_rcode);
}
+ ResponseConfig d_responseConfig;
private:
uint8_t d_rcode;
};
dq->dh->rcode = (d_rcode & 0xF);
dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
dq->dh->qr = true; // for good measure
+ setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
return Action::HeaderModify;
}
std::string toString() const override
return "set ercode "+ERCode::to_s(d_rcode);
}
+ ResponseConfig d_responseConfig;
private:
uint8_t d_rcode;
};
char* dest = ((char*)dq->dh) + dq->len;
dq->dh->qr = true; // for good measure
- dq->dh->ra = dq->dh->rd; // for good measure
- dq->dh->ad = false;
+ setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
dq->dh->ancount = 0;
dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
dq->du->setHTTPResponse(d_code, d_body, d_contentType);
dq->dh->qr = true; // for good measure
+ setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
return Action::HeaderModify;
}
{
return "return an HTTP status of " + std::to_string(d_code);
}
+
+ ResponseConfig d_responseConfig;
private:
std::string d_body;
std::string d_contentType;
});
}
+typedef std::unordered_map<std::string, boost::variant<bool> > responseParams_t;
+
+static void parseResponseConfig(boost::optional<responseParams_t> vars, ResponseConfig& config)
+{
+ if (vars) {
+ if (vars->count("aa")) {
+ config.setAA = boost::get<bool>((*vars)["aa"]);
+ }
+ if (vars->count("ad")) {
+ config.setAD = boost::get<bool>((*vars)["ad"]);
+ }
+ if (vars->count("ra")) {
+ config.setRA = boost::get<bool>((*vars)["ra"]);
+ }
+ }
+}
+
+void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config)
+{
+ if (config.setAA) {
+ dh.aa = *config.setAA;
+ }
+ if (config.setAD) {
+ dh.ad = *config.setAD;
+ }
+ else {
+ dh.ad = false;
+ }
+ if (config.setRA) {
+ dh.ra = *config.setRA;
+ }
+ else {
+ dh.ra = dh.rd; // for good measure
+ }
+}
+
void setupLuaActions()
{
g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
});
- g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b ) {
+ g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<responseParams_t> vars ) {
vector<ComboAddress> addrs;
if(auto s = boost::get<std::string>(&inp))
addrs.push_back(ComboAddress(*s));
for(const auto& a: v)
addrs.push_back(ComboAddress(a.second));
}
- if(b)
+ if(b) {
addrs.push_back(ComboAddress(*b));
- return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+ }
+
+ auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+ auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+ parseResponseConfig(vars, sa->d_responseConfig);
+ return ret;
});
- g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a) {
- return std::shared_ptr<DNSAction>(new SpoofAction(a));
+ g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new SpoofAction(a));
+ ResponseConfig responseConfig;
+ parseResponseConfig(vars, responseConfig);
+ auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+ sa->d_responseConfig = responseConfig;
+ return ret;
});
g_lua.writeFunction("DropAction", []() {
return std::shared_ptr<DNSResponseAction>(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
});
- g_lua.writeFunction("RCodeAction", [](uint8_t rcode) {
- return std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+ g_lua.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+ auto rca = std::dynamic_pointer_cast<RCodeAction>(ret);
+ parseResponseConfig(vars, rca->d_responseConfig);
+ return ret;
});
- g_lua.writeFunction("ERCodeAction", [](uint8_t rcode) {
- return std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+ g_lua.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+ auto erca = std::dynamic_pointer_cast<ERCodeAction>(ret);
+ parseResponseConfig(vars, erca->d_responseConfig);
+ return ret;
});
g_lua.writeFunction("SkipCacheAction", []() {
});
#ifdef HAVE_DNS_OVER_HTTPS
- g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType) {
- return std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
+ g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
+ auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
+ parseResponseConfig(vars, hsa->d_responseConfig);
+ return ret;
});
#endif /* HAVE_DNS_OVER_HTTPS */
);
g_lua.registerMember("order", &DownstreamState::order);
g_lua.registerMember("name", &DownstreamState::name);
+ g_lua.registerFunction<std::string(DownstreamState::*)()>("getID", [](const DownstreamState& s) { return boost::uuids::to_string(s.id); });
/* dnsheader */
g_lua.registerFunction<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) {
return (bool)dh.rd;
});
+ g_lua.registerFunction<void(dnsheader::*)(bool)>("setRA", [](dnsheader& dh, bool v) {
+ dh.ra=v;
+ });
+
+ g_lua.registerFunction<bool(dnsheader::*)()>("getRA", [](dnsheader& dh) {
+ return (bool)dh.ra;
+ });
+
+ g_lua.registerFunction<void(dnsheader::*)(bool)>("setAD", [](dnsheader& dh, bool v) {
+ dh.ad=v;
+ });
+
+ g_lua.registerFunction<bool(dnsheader::*)()>("getAD", [](dnsheader& dh) {
+ return (bool)dh.ad;
+ });
+
+ g_lua.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
+ dh.aa=v;
+ });
+
+ g_lua.registerFunction<bool(dnsheader::*)()>("getAA", [](dnsheader& dh) {
+ return (bool)dh.aa;
+ });
+
g_lua.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
dh.cd=v;
});
group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
}
});
+ g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, double, unsigned int, const std::string&, unsigned int, size_t, boost::optional<DNSAction::Action>, boost::optional<double>)>("setRCodeRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio) {
+ if (group) {
+ group->setRCodeRatio(rcode, ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses);
+ }
+ });
g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQTypeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint16_t qtype, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
if (group) {
group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
#include "dnsdist.hh"
#include "dnsdist-console.hh"
#include "dnsdist-ecs.hh"
+#include "dnsdist-healthchecks.hh"
#include "dnsdist-lua.hh"
#include "dnsdist-rings.hh"
#include "dnsdist-secpoll.hh"
#endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
-void setupLuaConfig(bool client)
+void setupLuaConfig(bool client, bool configCheck)
{
typedef std::unordered_map<std::string, boost::variant<bool, std::string, vector<pair<int, std::string> >, DownstreamState::checkfunc_t > > newserver_t;
g_lua.writeFunction("inClientStartup", [client]() {
return client && !g_configurationDone;
});
+ g_lua.writeFunction("inConfigCheck", [client, configCheck]() {
+ return !configCheck;
+ });
+
g_lua.writeFunction("newServer",
- [client](boost::variant<string,newserver_t> pvars, boost::optional<int> qps) {
+ [client, configCheck](boost::variant<string,newserver_t> pvars, boost::optional<int> qps) {
setLuaSideEffect();
std::shared_ptr<DownstreamState> ret = std::make_shared<DownstreamState>(ComboAddress());
}
}
- if(client) {
- // do not construct DownstreamState now, it would try binding sockets.
- return ret;
- }
- ret=std::make_shared<DownstreamState>(serverAddr, sourceAddr, sourceItf, sourceItfName, numberOfSockets);
+ // create but don't connect the socket in client or check-config modes
+ ret=std::make_shared<DownstreamState>(serverAddr, sourceAddr, sourceItf, sourceItfName, numberOfSockets, !(client || configCheck));
if(vars.count("qps")) {
int qpsVal=std::stoi(boost::get<string>(vars["qps"]));
} );
g_lua.writeFunction("rmServer",
- [](boost::variant<std::shared_ptr<DownstreamState>, int> var)
+ [](boost::variant<std::shared_ptr<DownstreamState>, int, std::string> var)
{
setLuaSideEffect();
- shared_ptr<DownstreamState> server;
- auto* rem = boost::get<shared_ptr<DownstreamState>>(&var);
+ shared_ptr<DownstreamState> server = nullptr;
auto states = g_dstates.getCopy();
- if(rem) {
+ if (auto* rem = boost::get<shared_ptr<DownstreamState>>(&var)) {
server = *rem;
}
+ else if (auto str = boost::get<std::string>(&var)) {
+ const auto uuid = getUniqueID(*str);
+ for (auto& state : states) {
+ if (state->id == uuid) {
+ server = state;
+ }
+ }
+ }
else {
int idx = boost::get<int>(var);
server = states.at(idx);
}
+ if (!server) {
+ throw std::runtime_error("unable to locate the requested server");
+ }
auto localPools = g_pools.getCopy();
for (const string& poolName : server->pools) {
removeServerFromPool(localPools, poolName, server);
return getDownstreamCandidates(g_pools.getCopy(), pool);
});
- g_lua.writeFunction("getServer", [client](int i) {
- if (client)
+ g_lua.writeFunction("getServer", [client](boost::variant<unsigned int, std::string> i) {
+ if (client) {
return std::make_shared<DownstreamState>(ComboAddress());
- return g_dstates.getCopy().at(i);
+ }
+ auto states = g_dstates.getCopy();
+ if (auto str = boost::get<std::string>(&i)) {
+ const auto uuid = getUniqueID(*str);
+ for (auto& state : states) {
+ if (state->id == uuid) {
+ return state;
+ }
+ }
+ }
+ else if (auto pos = boost::get<unsigned int>(&i)) {
+ return states.at(*pos);
+ }
+
+ g_outputBuffer = "Error: no rule matched\n";
+ return std::shared_ptr<DownstreamState>(nullptr);
});
g_lua.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName,
g_carbon.setState(ours);
});
- g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders) {
+ g_lua.writeFunction("webserver", [client,configCheck](const std::string& address, const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders) {
setLuaSideEffect();
ComboAddress local;
try {
throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason);
}
- if (client) {
+ if (client || configCheck) {
return;
}
}
});
- g_lua.writeFunction("controlSocket", [client](const std::string& str) {
+ g_lua.writeFunction("controlSocket", [client,configCheck](const std::string& str) {
setLuaSideEffect();
ComboAddress local(str, 5199);
- if(client) {
+ if(client || configCheck) {
g_serverControl = local;
return;
}
#endif
});
- g_lua.writeFunction("generateDNSCryptProviderKeys", [](const std::string& publicKeyFile, const std::string privateKeyFile) {
+ g_lua.writeFunction("generateDNSCryptProviderKeys", [client](const std::string& publicKeyFile, const std::string privateKeyFile) {
setLuaNoSideEffect();
#ifdef HAVE_DNSCRYPT
+ if (client) {
+ return;
+ }
unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
sodium_mlock(privateKey, sizeof(privateKey));
});
#ifdef HAVE_DNSCRYPT
- g_lua.writeFunction("generateDNSCryptCertificate", [](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
+ g_lua.writeFunction("generateDNSCryptCertificate", [client](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
setLuaNoSideEffect();
+ if (client) {
+ return;
+ }
DNSCryptPrivateKey privateKey;
DNSCryptCert cert;
g_useTCPSinglePipe = flag;
});
- g_lua.writeFunction("snmpAgent", [client](bool enableTraps, boost::optional<std::string> masterSocket) {
- if(client)
+ g_lua.writeFunction("snmpAgent", [client,configCheck](bool enableTraps, boost::optional<std::string> masterSocket) {
+ if(client || configCheck)
return;
#ifdef HAVE_NET_SNMP
if (g_configurationDone) {
g_lua.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse=allow; });
#if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN)
- g_lua.writeFunction("generateOCSPResponse", [](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
- return libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
+ g_lua.writeFunction("generateOCSPResponse", [client](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
+ if (client) {
+ return;
+ }
+
+ libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
});
#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN*/
}
-vector<std::function<void(void)>> setupLua(bool client, const std::string& config)
+vector<std::function<void(void)>> setupLua(bool client, bool configCheck, const std::string& config)
{
g_launchWork= new vector<std::function<void(void)>>();
setupLuaActions();
- setupLuaConfig(client);
+ setupLuaConfig(client, configCheck);
setupLuaBindings(client);
setupLuaBindingsDNSCrypt();
setupLuaBindingsDNSQuestion();
setupLuaBindingsKVS(client);
setupLuaBindingsPacketCache();
- setupLuaBindingsProtoBuf(client);
+ setupLuaBindingsProtoBuf(client, configCheck);
setupLuaInspection();
setupLuaRules();
setupLuaVars();
*/
#pragma once
+struct ResponseConfig
+{
+ boost::optional<bool> setAA{boost::none};
+ boost::optional<bool> setAD{boost::none};
+ boost::optional<bool> setRA{boost::none};
+};
+void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
+
class LuaAction : public DNSAction
{
public:
}
return ret;
}
+
+
+ ResponseConfig d_responseConfig;
private:
std::vector<ComboAddress> d_addrs;
DNSName d_cname;
typedef NetmaskTree<DynBlock> nmts_t;
+vector<std::function<void(void)>> setupLua(bool client, bool configCheck, const std::string& config);
void setupLuaActions();
void setupLuaBindings(bool client);
void setupLuaBindingsDNSCrypt();
void setupLuaBindingsDNSQuestion();
void setupLuaBindingsKVS(bool client);
void setupLuaBindingsPacketCache();
-void setupLuaBindingsProtoBuf(bool client);
+void setupLuaBindingsProtoBuf(bool client, bool configCheck);
void setupLuaRules();
void setupLuaInspection();
void setupLuaVars();
}
d_tcpclientthreads.push_back(pipefds[1]);
+ ++d_numthreads;
}
-
- ++d_numthreads;
}
static void cleanupClosedTCPConnections()
ComboAddress remote;
remote.sin4.sin_family = cs->local.sin4.sin_family;
- g_tcpclientthreads->addTCPClientThread();
+ if(!g_tcpclientthreads->hasReachedMaxThreads()) {
+ g_tcpclientthreads->addTCPClientThread();
+ }
auto acl = g_ACL.getLocal();
for(;;) {
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "dnsdist.hh"
+#include "dnsdist-healthchecks.hh"
+
#include "sstuff.hh"
#include "ext/json11/json11.hpp"
#include "ext/incbin/incbin.h"
#include "dnsdist-cache.hh"
#include "dnsdist-console.hh"
#include "dnsdist-ecs.hh"
+#include "dnsdist-healthchecks.hh"
#include "dnsdist-lua.hh"
#include "dnsdist-rings.hh"
#include "dnsdist-secpoll.hh"
#include "dolog.hh"
#include "dnsname.hh"
#include "dnsparser.hh"
-#include "dnswriter.hh"
#include "ednsoptions.hh"
#include "gettime.hh"
#include "lock.hh"
MetricDefinitionStorage g_metricDefinitions;
uint16_t g_maxOutstanding{std::numeric_limits<uint16_t>::max()};
-bool g_verboseHealthChecks{false};
uint32_t g_staleCacheEntriesTTL{0};
bool g_syslog{true};
bool g_allowEmptyResponse{false};
}
}
-DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf_, const std::string& sourceItfName_, size_t numberOfSockets): sourceItfName(sourceItfName_), remote(remote_), sourceAddr(sourceAddr_), sourceItf(sourceItf_)
+DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf_, const std::string& sourceItfName_, size_t numberOfSockets, bool connect=true): sourceItfName(sourceItfName_), remote(remote_), sourceAddr(sourceAddr_), sourceItf(sourceItf_)
{
pthread_rwlock_init(&d_lock, nullptr);
id = getUniqueID();
fd = -1;
}
- if (!IsAnyAddress(remote)) {
+ if (connect && !IsAnyAddress(remote)) {
reconnect();
idStates.resize(g_maxOutstanding);
sw.start();
case DNSAction::Action::Truncate:
dq.dh->tc = true;
dq.dh->qr = true;
+ dq.dh->ra = dq.dh->rd;
+ dq.dh->aa = false;
+ dq.dh->ad = false;
return true;
break;
case DNSAction::Action::HeaderModify:
vinfolog("Query from %s truncated because of dynamic block", dq.remote->toStringWithPort());
dq.dh->tc = true;
dq.dh->qr = true;
+ dq.dh->ra = dq.dh->rd;
+ dq.dh->aa = false;
+ dq.dh->ad = false;
return true;
}
else {
vinfolog("Query from %s for %s truncated because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
dq.dh->tc = true;
dq.dh->qr = true;
+ dq.dh->ra = dq.dh->rd;
+ dq.dh->aa = false;
+ dq.dh->ad = false;
return true;
}
else {
uint16_t getRandomDNSID()
{
#ifdef HAVE_LIBSODIUM
- return (randombytes_random() % 65536);
+ return randombytes_uniform(65536);
#else
return (random() % 65536);
#endif
}
-static bool upCheck(const shared_ptr<DownstreamState>& ds)
-try
-{
- DNSName checkName = ds->checkName;
- uint16_t checkType = ds->checkType.getCode();
- uint16_t checkClass = ds->checkClass;
- dnsheader checkHeader;
- memset(&checkHeader, 0, sizeof(checkHeader));
-
- checkHeader.qdcount = htons(1);
- checkHeader.id = getRandomDNSID();
-
- checkHeader.rd = true;
- if (ds->setCD) {
- checkHeader.cd = true;
- }
-
- if (ds->checkFunction) {
- std::lock_guard<std::mutex> lock(g_luamutex);
- auto ret = ds->checkFunction(checkName, checkType, checkClass, &checkHeader);
- checkName = std::get<0>(ret);
- checkType = std::get<1>(ret);
- checkClass = std::get<2>(ret);
- }
-
- vector<uint8_t> packet;
- DNSPacketWriter dpw(packet, checkName, checkType, checkClass);
- dnsheader * requestHeader = dpw.getHeader();
- *requestHeader = checkHeader;
-
- Socket sock(ds->remote.sin4.sin_family, SOCK_DGRAM);
- sock.setNonBlocking();
- if (!IsAnyAddress(ds->sourceAddr)) {
- sock.setReuseAddr();
- if (!ds->sourceItfName.empty()) {
-#ifdef SO_BINDTODEVICE
- int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, ds->sourceItfName.c_str(), ds->sourceItfName.length());
- if (res != 0 && g_verboseHealthChecks) {
- infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", ds->getNameWithAddr(), stringerror());
- }
-#endif
- }
- sock.bind(ds->sourceAddr);
- }
- sock.connect(ds->remote);
- ssize_t sent = udpClientSendRequestToBackend(ds, sock.getHandle(), reinterpret_cast<char*>(&packet[0]), packet.size(), true);
- if (sent < 0) {
- int ret = errno;
- if (g_verboseHealthChecks)
- infolog("Error while sending a health check query to backend %s: %d", ds->getNameWithAddr(), ret);
- return false;
- }
-
- int ret = waitForRWData(sock.getHandle(), true, /* ms to seconds */ ds->checkTimeout / 1000, /* remaining ms to us */ (ds->checkTimeout % 1000) * 1000);
- if(ret < 0 || !ret) { // error, timeout, both are down!
- if (ret < 0) {
- ret = errno;
- if (g_verboseHealthChecks)
- infolog("Error while waiting for the health check response from backend %s: %d", ds->getNameWithAddr(), ret);
- }
- else {
- if (g_verboseHealthChecks)
- infolog("Timeout while waiting for the health check response from backend %s", ds->getNameWithAddr());
- }
- return false;
- }
-
- string reply;
- ComboAddress from;
- sock.recvFrom(reply, from);
-
- /* we are using a connected socket but hey.. */
- if (from != ds->remote) {
- if (g_verboseHealthChecks)
- infolog("Invalid health check response received from %s, expecting one from %s", from.toStringWithPort(), ds->remote.toStringWithPort());
- return false;
- }
-
- const dnsheader * responseHeader = reinterpret_cast<const dnsheader *>(reply.c_str());
-
- if (reply.size() < sizeof(*responseHeader)) {
- if (g_verboseHealthChecks)
- infolog("Invalid health check response of size %d from backend %s, expecting at least %d", reply.size(), ds->getNameWithAddr(), sizeof(*responseHeader));
- return false;
- }
-
- if (responseHeader->id != requestHeader->id) {
- if (g_verboseHealthChecks)
- infolog("Invalid health check response id %d from backend %s, expecting %d", responseHeader->id, ds->getNameWithAddr(), requestHeader->id);
- return false;
- }
-
- if (!responseHeader->qr) {
- if (g_verboseHealthChecks)
- infolog("Invalid health check response from backend %s, expecting QR to be set", ds->getNameWithAddr());
- return false;
- }
-
- if (responseHeader->rcode == RCode::ServFail) {
- if (g_verboseHealthChecks)
- infolog("Backend %s responded to health check with ServFail", ds->getNameWithAddr());
- return false;
- }
-
- if (ds->mustResolve && (responseHeader->rcode == RCode::NXDomain || responseHeader->rcode == RCode::Refused)) {
- if (g_verboseHealthChecks)
- infolog("Backend %s responded to health check with %s while mustResolve is set", ds->getNameWithAddr(), responseHeader->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
- return false;
- }
-
- uint16_t receivedType;
- uint16_t receivedClass;
- DNSName receivedName(reply.c_str(), reply.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
-
- if (receivedName != checkName || receivedType != checkType || receivedClass != checkClass) {
- if (g_verboseHealthChecks)
- infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", ds->getNameWithAddr(), receivedName.toLogString(), checkName.toLogString(), QType(receivedType).getName(), QType(checkType).getName(), receivedClass, checkClass);
- return false;
- }
-
- return true;
-}
-catch(const std::exception& e)
-{
- if (g_verboseHealthChecks)
- infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
- return false;
-}
-catch(...)
-{
- if (g_verboseHealthChecks)
- infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
- return false;
-}
-
uint64_t g_maxTCPClientThreads{10};
std::atomic<uint16_t> g_cacheCleaningDelay{60};
std::atomic<uint16_t> g_cacheCleaningPercentage{100};
{
setThreadName("dnsdist/healthC");
- int interval = 1;
+ static const int interval = 1;
for(;;) {
sleep(interval);
- if(g_tcpclientthreads->getQueuedCount() > 1 && !g_tcpclientthreads->hasReachedMaxThreads())
+ if(g_tcpclientthreads->getQueuedCount() > 1 && !g_tcpclientthreads->hasReachedMaxThreads()) {
g_tcpclientthreads->addTCPClientThread();
+ }
+ auto mplexer = std::shared_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
auto states = g_dstates.getLocal(); // this points to the actual shared_ptrs!
for(auto& dss : *states) {
- if(++dss->lastCheck < dss->checkInterval)
+ if(++dss->lastCheck < dss->checkInterval) {
continue;
- dss->lastCheck = 0;
- if(dss->availability==DownstreamState::Availability::Auto) {
- bool newState=upCheck(dss);
- if (newState) {
- /* check succeeded */
- dss->currentCheckFailures = 0;
-
- if (!dss->upStatus) {
- /* we were marked as down */
- dss->consecutiveSuccessfulChecks++;
- if (dss->consecutiveSuccessfulChecks < dss->minRiseSuccesses) {
- /* if we need more than one successful check to rise
- and we didn't reach the threshold yet,
- let's stay down */
- newState = false;
- }
- }
- }
- else {
- /* check failed */
- dss->consecutiveSuccessfulChecks = 0;
-
- if (dss->upStatus) {
- /* we are currently up */
- dss->currentCheckFailures++;
- if (dss->currentCheckFailures < dss->maxCheckFailures) {
- /* we need more than one failure to be marked as down,
- and we did not reach the threshold yet, let's stay down */
- newState = true;
- }
- }
- }
-
- if(newState != dss->upStatus) {
- warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
-
- if (newState && !dss->connected) {
- newState = dss->reconnect();
+ }
- if (dss->connected && !dss->threadStarted.test_and_set()) {
- dss->tid = thread(responderThread, dss);
- }
- }
+ dss->lastCheck = 0;
- dss->upStatus = newState;
- dss->currentCheckFailures = 0;
- dss->consecutiveSuccessfulChecks = 0;
- if (g_snmpAgent && g_snmpTrapsEnabled) {
- g_snmpAgent->sendBackendStatusChangeTrap(dss);
- }
+ if (dss->availability == DownstreamState::Availability::Auto) {
+ if (!queueHealthCheck(mplexer, dss)) {
+ updateHealthCheckResult(dss, false);
}
}
dss->prev.queries.store(dss->queries.load());
dss->prev.reuseds.store(dss->reuseds.load());
- for(IDState& ids : dss->idStates) { // timeouts
+ for (IDState& ids : dss->idStates) { // timeouts
int64_t usageIndicator = ids.usageIndicator;
if(IDState::isInUse(usageIndicator) && ids.age++ > g_udpTimeout) {
/* We mark the state as unused as soon as possible
}
}
}
+
+ handleQueuedHealthChecks(mplexer);
}
}
g_policy.setState(leastOutstandingPol);
if(g_cmdLine.beClient || !g_cmdLine.command.empty()) {
- setupLua(true, g_cmdLine.config);
+ setupLua(true, false, g_cmdLine.config);
if (clientAddress != ComboAddress())
g_serverControl = clientAddress;
doClient(g_serverControl, g_cmdLine.command);
g_consoleACL.setState(consoleACL);
if (g_cmdLine.checkConfig) {
- setupLua(true, g_cmdLine.config);
+ setupLua(false, true, g_cmdLine.config);
// No exception was thrown
infolog("Configuration '%s' OK!", g_cmdLine.config);
_exit(EXIT_SUCCESS);
}
- auto todo=setupLua(false, g_cmdLine.config);
+ auto todo=setupLua(false, false, g_cmdLine.config);
auto localPools = g_pools.getCopy();
{
checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount);
+ auto mplexer = std::shared_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
for(auto& dss : g_dstates.getCopy()) { // it is a copy, but the internal shared_ptrs are the real deal
- if(dss->availability==DownstreamState::Availability::Auto) {
- bool newState=upCheck(dss);
- warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
- dss->upStatus = newState;
+ if (dss->availability == DownstreamState::Availability::Auto) {
+ if (!queueHealthCheck(mplexer, dss, true)) {
+ dss->upStatus = false;
+ warnlog("Marking downstream %s as 'down'", dss->getNameWithAddr());
+ }
}
}
+ handleQueuedHealthChecks(mplexer, true);
for(auto& cs : g_frontends) {
if (cs->dohFrontend != nullptr) {
{
typedef std::function<std::tuple<DNSName, uint16_t, uint16_t>(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t;
- DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf, const std::string& sourceItfName, size_t numberOfSockets);
- DownstreamState(const ComboAddress& remote_): DownstreamState(remote_, ComboAddress(), 0, std::string(), 1) {}
+ DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf, const std::string& sourceItfName, size_t numberOfSockets, bool connect);
+ DownstreamState(const ComboAddress& remote_): DownstreamState(remote_, ComboAddress(), 0, std::string(), 1, true) {}
~DownstreamState()
{
for (auto& fd : sockets) {
extern size_t g_maxTCPConnectionsPerClient;
extern std::atomic<uint16_t> g_cacheCleaningDelay;
extern std::atomic<uint16_t> g_cacheCleaningPercentage;
-extern bool g_verboseHealthChecks;
extern uint32_t g_staleCacheEntriesTTL;
extern bool g_apiReadWrite;
extern std::string g_apiConfigDirectory;
struct dnsheader;
void controlThread(int fd, ComboAddress local);
-vector<std::function<void(void)>> setupLua(bool client, const std::string& config);
std::shared_ptr<ServerPool> getPool(const pools_t& pools, const std::string& poolName);
std::shared_ptr<ServerPool> createPoolIfNotExists(pools_t& pools, const string& poolName);
NumberedServerVector getDownstreamCandidates(const pools_t& pools, const std::string& poolName);
dnsdist-carbon.cc \
dnsdist-console.cc dnsdist-console.hh \
dnsdist-dnscrypt.cc \
- dnsdist-dynblocks.hh \
+ dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
dnsdist-ecs.cc dnsdist-ecs.hh \
+ dnsdist-healthchecks.cc dnsdist-healthchecks.hh \
dnsdist-idstate.cc \
dnsdist-kvs.hh dnsdist-kvs.cc \
dnsdist-lua.hh dnsdist-lua.cc \
circular_buffer.hh \
dnsdist.hh \
dnsdist-cache.cc dnsdist-cache.hh \
+ dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
dnsdist-ecs.cc dnsdist-ecs.hh \
dnsdist-kvs.cc dnsdist-kvs.hh \
dnsdist-rings.hh \
--- /dev/null
+
+#include "dnsdist.hh"
+#include "dnsdist-dynblocks.hh"
+
+void DynBlockRulesGroup::apply(const struct timespec& now)
+{
+ counts_t counts;
+ StatNode statNodeRoot;
+
+ size_t entriesCount = 0;
+ if (hasQueryRules()) {
+ entriesCount += g_rings.getNumberOfQueryEntries();
+ }
+ if (hasResponseRules()) {
+ entriesCount += g_rings.getNumberOfResponseEntries();
+ }
+ counts.reserve(entriesCount);
+
+ processQueryRules(counts, now);
+ processResponseRules(counts, statNodeRoot, now);
+
+ if (counts.empty() && statNodeRoot.empty()) {
+ return;
+ }
+
+ boost::optional<NetmaskTree<DynBlock> > blocks;
+ bool updated = false;
+
+ for (const auto& entry : counts) {
+ const auto& requestor = entry.first;
+ const auto& counters = entry.second;
+
+ if (d_queryRateRule.warningRateExceeded(counters.queries, now)) {
+ handleWarning(blocks, now, requestor, d_queryRateRule, updated);
+ }
+
+ if (d_queryRateRule.rateExceeded(counters.queries, now)) {
+ addBlock(blocks, now, requestor, d_queryRateRule, updated);
+ continue;
+ }
+
+ if (d_respRateRule.warningRateExceeded(counters.respBytes, now)) {
+ handleWarning(blocks, now, requestor, d_respRateRule, updated);
+ }
+
+ if (d_respRateRule.rateExceeded(counters.respBytes, now)) {
+ addBlock(blocks, now, requestor, d_respRateRule, updated);
+ continue;
+ }
+
+ for (const auto& pair : d_qtypeRules) {
+ const auto qtype = pair.first;
+
+ const auto& typeIt = counters.d_qtypeCounts.find(qtype);
+ if (typeIt != counters.d_qtypeCounts.cend()) {
+
+ if (pair.second.warningRateExceeded(typeIt->second, now)) {
+ handleWarning(blocks, now, requestor, pair.second, updated);
+ }
+
+ if (pair.second.rateExceeded(typeIt->second, now)) {
+ addBlock(blocks, now, requestor, pair.second, updated);
+ break;
+ }
+ }
+ }
+
+ for (const auto& pair : d_rcodeRules) {
+ const auto rcode = pair.first;
+
+ const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
+ if (rcodeIt != counters.d_rcodeCounts.cend()) {
+ if (pair.second.warningRateExceeded(rcodeIt->second, now)) {
+ handleWarning(blocks, now, requestor, pair.second, updated);
+ }
+
+ if (pair.second.rateExceeded(rcodeIt->second, now)) {
+ addBlock(blocks, now, requestor, pair.second, updated);
+ break;
+ }
+ }
+ }
+
+ for (const auto& pair : d_rcodeRatioRules) {
+ const auto rcode = pair.first;
+
+ const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
+ if (rcodeIt != counters.d_rcodeCounts.cend()) {
+ if (pair.second.warningRatioExceeded(counters.queries, rcodeIt->second)) {
+ handleWarning(blocks, now, requestor, pair.second, updated);
+ }
+
+ if (pair.second.ratioExceeded(counters.queries, rcodeIt->second)) {
+ addBlock(blocks, now, requestor, pair.second, updated);
+ break;
+ }
+ }
+ }
+ }
+
+ if (updated && blocks) {
+ g_dynblockNMG.setState(std::move(*blocks));
+ }
+
+ if (!statNodeRoot.empty()) {
+ StatNode::Stat node;
+ std::unordered_set<DNSName> namesToBlock;
+ statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
+ bool block = false;
+
+ if (d_smtVisitorFFI) {
+ dnsdist_ffi_stat_node_t tmp(*node_, self, children);
+ block = d_smtVisitorFFI(&tmp);
+ }
+ else {
+ block = d_smtVisitor(*node_, self, children);
+ }
+
+ if (block) {
+ namesToBlock.insert(DNSName(node_->fullname));
+ }
+ },
+ node);
+
+ if (!namesToBlock.empty()) {
+ updated = false;
+ SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
+ for (const auto& name : namesToBlock) {
+ addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated);
+ }
+ if (updated) {
+ g_dynblockSMT.setState(std::move(smtBlocks));
+ }
+ }
+ }
+}
+
+bool DynBlockRulesGroup::checkIfQueryTypeMatches(const Rings::Query& query)
+{
+ auto rule = d_qtypeRules.find(query.qtype);
+ if (rule == d_qtypeRules.end()) {
+ return false;
+ }
+
+ return rule->second.matches(query.when);
+}
+
+bool DynBlockRulesGroup::checkIfResponseCodeMatches(const Rings::Response& response)
+{
+ auto rule = d_rcodeRules.find(response.dh.rcode);
+ if (rule != d_rcodeRules.end() && rule->second.matches(response.when)) {
+ return true;
+ }
+
+ auto ratio = d_rcodeRatioRules.find(response.dh.rcode);
+ if (ratio != d_rcodeRatioRules.end() && ratio->second.matches(response.when)) {
+ return true;
+ }
+
+ return false;
+}
+
+void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+{
+ if (d_excludedSubnets.match(requestor)) {
+ /* do not add a block for excluded subnets */
+ return;
+ }
+
+ if (!blocks) {
+ blocks = g_dynblockNMG.getCopy();
+ }
+ struct timespec until = now;
+ until.tv_sec += rule.d_blockDuration;
+ unsigned int count = 0;
+ const auto& got = blocks->lookup(Netmask(requestor));
+ bool expired = false;
+ bool wasWarning = false;
+
+ if (got) {
+ if (warning && !got->second.warning) {
+ /* we have an existing entry which is not a warning,
+ don't override it */
+ return;
+ }
+ else if (!warning && got->second.warning) {
+ wasWarning = true;
+ }
+ else {
+ if (until < got->second.until) {
+ // had a longer policy
+ return;
+ }
+ }
+
+ if (now < got->second.until) {
+ // only inherit count on fresh query we are extending
+ count = got->second.blocks;
+ }
+ else {
+ expired = true;
+ }
+ }
+
+ DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
+ db.blocks = count;
+ db.warning = warning;
+ if (!d_beQuiet && (!got || expired || wasWarning)) {
+ warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+ }
+ blocks->insert(Netmask(requestor)).second = db;
+ updated = true;
+}
+
+void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+{
+ if (d_excludedDomains.check(name)) {
+ /* do not add a block for excluded domains */
+ return;
+ }
+
+ struct timespec until = now;
+ until.tv_sec += rule.d_blockDuration;
+ unsigned int count = 0;
+ const auto& got = blocks.lookup(name);
+ bool expired = false;
+ DNSName domain(name.makeLowerCase());
+
+ if (got) {
+ if (until < got->until) {
+ // had a longer policy
+ return;
+ }
+
+ if (now < got->until) {
+ // only inherit count on fresh query we are extending
+ count = got->blocks;
+ }
+ else {
+ expired = true;
+ }
+ }
+
+ DynBlock db{rule.d_blockReason, until, domain, rule.d_action};
+ db.blocks = count;
+
+ if (!d_beQuiet && (!got || expired)) {
+ warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, rule.d_blockDuration, rule.d_blockReason);
+ }
+ blocks.add(domain, db);
+ updated = true;
+}
+
+void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timespec& now)
+{
+ if (!hasQueryRules()) {
+ return;
+ }
+
+ d_queryRateRule.d_cutOff = d_queryRateRule.d_minTime = now;
+ d_queryRateRule.d_cutOff.tv_sec -= d_queryRateRule.d_seconds;
+
+ for (auto& rule : d_qtypeRules) {
+ rule.second.d_cutOff = rule.second.d_minTime = now;
+ rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
+ }
+
+ for (const auto& shard : g_rings.d_shards) {
+ std::lock_guard<std::mutex> rl(shard->queryLock);
+ for(const auto& c : shard->queryRing) {
+ if (now < c.when) {
+ continue;
+ }
+
+ bool qRateMatches = d_queryRateRule.matches(c.when);
+ bool typeRuleMatches = checkIfQueryTypeMatches(c);
+
+ if (qRateMatches || typeRuleMatches) {
+ auto& entry = counts[c.requestor];
+ if (qRateMatches) {
+ ++entry.queries;
+ }
+ if (typeRuleMatches) {
+ ++entry.d_qtypeCounts[c.qtype];
+ }
+ }
+ }
+ }
+}
+
+void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now)
+{
+ if (!hasResponseRules() && !hasSuffixMatchRules()) {
+ return;
+ }
+
+ d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now;
+ d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds;
+
+ d_suffixMatchRule.d_cutOff = d_suffixMatchRule.d_minTime = now;
+ d_suffixMatchRule.d_cutOff.tv_sec -= d_suffixMatchRule.d_seconds;
+
+ for (auto& rule : d_rcodeRules) {
+ rule.second.d_cutOff = rule.second.d_minTime = now;
+ rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
+ }
+
+ for (auto& rule : d_rcodeRatioRules) {
+ rule.second.d_cutOff = rule.second.d_minTime = now;
+ rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
+ }
+
+ for (const auto& shard : g_rings.d_shards) {
+ std::lock_guard<std::mutex> rl(shard->respLock);
+ for(const auto& c : shard->respRing) {
+ if (now < c.when) {
+ continue;
+ }
+
+ auto& entry = counts[c.requestor];
+ ++entry.queries;
+ bool respRateMatches = d_respRateRule.matches(c.when);
+ bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when);
+ bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
+
+ if (respRateMatches || rcodeRuleMatches) {
+ if (respRateMatches) {
+ entry.respBytes += c.size;
+ }
+ if (rcodeRuleMatches) {
+ ++entry.d_rcodeCounts[c.dh.rcode];
+ }
+ }
+
+ if (suffixMatchRuleMatches) {
+ root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, boost::none);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dnsdist-healthchecks.hh"
+#include "dnswriter.hh"
+#include "dolog.hh"
+
+bool g_verboseHealthChecks{false};
+
+void updateHealthCheckResult(const std::shared_ptr<DownstreamState>& dss, bool newState)
+{
+ if (newState) {
+ /* check succeeded */
+ dss->currentCheckFailures = 0;
+
+ if (!dss->upStatus) {
+ /* we were marked as down */
+ dss->consecutiveSuccessfulChecks++;
+ if (dss->consecutiveSuccessfulChecks < dss->minRiseSuccesses) {
+ /* if we need more than one successful check to rise
+ and we didn't reach the threshold yet,
+ let's stay down */
+ newState = false;
+ }
+ }
+ }
+ else {
+ /* check failed */
+ dss->consecutiveSuccessfulChecks = 0;
+
+ if (dss->upStatus) {
+ /* we are currently up */
+ dss->currentCheckFailures++;
+ if (dss->currentCheckFailures < dss->maxCheckFailures) {
+ /* we need more than one failure to be marked as down,
+ and we did not reach the threshold yet, let's stay down */
+ newState = true;
+ }
+ }
+ }
+ if(newState != dss->upStatus) {
+ warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
+
+ if (newState && !dss->connected) {
+ newState = dss->reconnect();
+
+ if (dss->connected && !dss->threadStarted.test_and_set()) {
+ dss->tid = std::thread(responderThread, dss);
+ }
+ }
+
+ dss->upStatus = newState;
+ dss->currentCheckFailures = 0;
+ dss->consecutiveSuccessfulChecks = 0;
+ if (g_snmpAgent && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendBackendStatusChangeTrap(dss);
+ }
+ }
+}
+
+static bool handleResponse(std::shared_ptr<HealthCheckData>& data)
+{
+ auto& ds = data->d_ds;
+ try {
+ string reply;
+ ComboAddress from;
+ data->d_sock.recvFrom(reply, from);
+
+ /* we are using a connected socket but hey.. */
+ if (from != ds->remote) {
+ if (g_verboseHealthChecks) {
+ infolog("Invalid health check response received from %s, expecting one from %s", from.toStringWithPort(), ds->remote.toStringWithPort());
+ }
+ return false;
+ }
+
+ const dnsheader * responseHeader = reinterpret_cast<const dnsheader *>(reply.c_str());
+
+ if (reply.size() < sizeof(*responseHeader)) {
+ if (g_verboseHealthChecks) {
+ infolog("Invalid health check response of size %d from backend %s, expecting at least %d", reply.size(), ds->getNameWithAddr(), sizeof(*responseHeader));
+ }
+ return false;
+ }
+
+ if (responseHeader->id != data->d_queryID) {
+ if (g_verboseHealthChecks) {
+ infolog("Invalid health check response id %d from backend %s, expecting %d", data->d_queryID, ds->getNameWithAddr(), data->d_queryID);
+ }
+ return false;
+ }
+
+ if (!responseHeader->qr) {
+ if (g_verboseHealthChecks) {
+ infolog("Invalid health check response from backend %s, expecting QR to be set", ds->getNameWithAddr());
+ }
+ return false;
+ }
+
+ if (responseHeader->rcode == RCode::ServFail) {
+ if (g_verboseHealthChecks) {
+ infolog("Backend %s responded to health check with ServFail", ds->getNameWithAddr());
+ }
+ return false;
+ }
+
+ if (ds->mustResolve && (responseHeader->rcode == RCode::NXDomain || responseHeader->rcode == RCode::Refused)) {
+ if (g_verboseHealthChecks) {
+ infolog("Backend %s responded to health check with %s while mustResolve is set", ds->getNameWithAddr(), responseHeader->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
+ }
+ return false;
+ }
+
+ uint16_t receivedType;
+ uint16_t receivedClass;
+ DNSName receivedName(reply.c_str(), reply.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
+
+ if (receivedName != data->d_checkName || receivedType != data->d_checkType || receivedClass != data->d_checkClass) {
+ if (g_verboseHealthChecks) {
+ infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", ds->getNameWithAddr(), receivedName.toLogString(), data->d_checkName.toLogString(), QType(receivedType).getName(), QType(data->d_checkType).getName(), receivedClass, data->d_checkClass);
+ }
+ return false;
+ }
+ }
+ catch(const std::exception& e)
+ {
+ if (g_verboseHealthChecks) {
+ infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
+ }
+ return false;
+ }
+ catch(...)
+ {
+ if (g_verboseHealthChecks) {
+ infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static void healthCheckCallback(int fd, FDMultiplexer::funcparam_t& param)
+{
+ auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
+ data->d_mplexer->removeReadFD(fd);
+ updateHealthCheckResult(data->d_ds, handleResponse(data));
+}
+
+static void initialHealthCheckCallback(int fd, FDMultiplexer::funcparam_t& param)
+{
+ auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
+ data->d_mplexer->removeReadFD(fd);
+ bool up = handleResponse(data);
+ warnlog("Marking downstream %s as '%s'", data->d_ds->getNameWithAddr(), up ? "up" : "down");
+ data->d_ds->upStatus = up;
+}
+
+bool queueHealthCheck(std::shared_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, bool initialCheck)
+{
+ try
+ {
+ uint16_t queryID = getRandomDNSID();
+ DNSName checkName = ds->checkName;
+ uint16_t checkType = ds->checkType.getCode();
+ uint16_t checkClass = ds->checkClass;
+ dnsheader checkHeader;
+ memset(&checkHeader, 0, sizeof(checkHeader));
+
+ checkHeader.qdcount = htons(1);
+ checkHeader.id = queryID;
+
+ checkHeader.rd = true;
+ if (ds->setCD) {
+ checkHeader.cd = true;
+ }
+
+ if (ds->checkFunction) {
+ std::lock_guard<std::mutex> lock(g_luamutex);
+ auto ret = ds->checkFunction(checkName, checkType, checkClass, &checkHeader);
+ checkName = std::get<0>(ret);
+ checkType = std::get<1>(ret);
+ checkClass = std::get<2>(ret);
+ }
+
+ vector<uint8_t> packet;
+ DNSPacketWriter dpw(packet, checkName, checkType, checkClass);
+ dnsheader * requestHeader = dpw.getHeader();
+ *requestHeader = checkHeader;
+
+ Socket sock(ds->remote.sin4.sin_family, SOCK_DGRAM);
+ sock.setNonBlocking();
+ if (!IsAnyAddress(ds->sourceAddr)) {
+ sock.setReuseAddr();
+ if (!ds->sourceItfName.empty()) {
+#ifdef SO_BINDTODEVICE
+ int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, ds->sourceItfName.c_str(), ds->sourceItfName.length());
+ if (res != 0 && g_verboseHealthChecks) {
+ infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", ds->getNameWithAddr(), stringerror());
+ }
+#endif
+ }
+ sock.bind(ds->sourceAddr);
+ }
+ sock.connect(ds->remote);
+ ssize_t sent = udpClientSendRequestToBackend(ds, sock.getHandle(), reinterpret_cast<char*>(&packet[0]), packet.size(), true);
+ if (sent < 0) {
+ int ret = errno;
+ if (g_verboseHealthChecks)
+ infolog("Error while sending a health check query to backend %s: %d", ds->getNameWithAddr(), ret);
+ return false;
+ }
+
+ auto data = std::make_shared<HealthCheckData>(mplexer, ds, std::move(sock), std::move(checkName), checkType, checkClass, queryID);
+ struct timeval ttd;
+ gettimeofday(&ttd, nullptr);
+ ttd.tv_sec += ds->checkTimeout / 1000; /* ms to seconds */
+ ttd.tv_usec += (ds->checkTimeout % 1000) * 1000; /* remaining ms to us */
+ if (ttd.tv_usec > 1000000) {
+ ++ttd.tv_sec;
+ ttd.tv_usec -= 1000000;
+ }
+ mplexer->addReadFD(data->d_sock.getHandle(), initialCheck ? &initialHealthCheckCallback : &healthCheckCallback, data, &ttd);
+
+ return true;
+ }
+ catch(const std::exception& e)
+ {
+ if (g_verboseHealthChecks) {
+ infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
+ }
+ return false;
+ }
+ catch(...)
+ {
+ if (g_verboseHealthChecks) {
+ infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
+ }
+ return false;
+ }
+}
+
+void handleQueuedHealthChecks(std::shared_ptr<FDMultiplexer>& mplexer, bool initial)
+{
+ while (mplexer->getWatchedFDCount(false) > 0) {
+ struct timeval now;
+ int ret = mplexer->run(&now, 100);
+ if (ret == -1) {
+ if (g_verboseHealthChecks) {
+ infolog("Error while waiting for the health check response from backends: %d", ret);
+ }
+ break;
+ }
+ auto timeouts = mplexer->getTimeouts(now);
+ for (const auto& timeout : timeouts) {
+ mplexer->removeReadFD(timeout.first);
+ auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
+ if (g_verboseHealthChecks) {
+ infolog("Timeout while waiting for the health check response from backend %s", data->d_ds->getNameWithAddr());
+ }
+ if (initial) {
+ warnlog("Marking downstream %s as 'down'", data->d_ds->getNameWithAddr());
+ data->d_ds->upStatus = false;
+ }
+ else {
+ updateHealthCheckResult(data->d_ds, false);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "dnsdist.hh"
+#include "mplexer.hh"
+#include "sstuff.hh"
+
+struct HealthCheckData
+{
+ HealthCheckData(std::shared_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, Socket&& sock, DNSName&& checkName, uint16_t checkType, uint16_t checkClass, uint16_t queryID): d_mplexer(mplexer), d_ds(ds), d_sock(std::move(sock)), d_checkName(std::move(checkName)), d_checkType(checkType), d_checkClass(checkClass), d_queryID(queryID)
+ {
+ }
+
+ std::shared_ptr<FDMultiplexer> d_mplexer;
+ const std::shared_ptr<DownstreamState> d_ds;
+ Socket d_sock;
+ DNSName d_checkName;
+ uint16_t d_checkType;
+ uint16_t d_checkClass;
+ uint16_t d_queryID;
+};
+
+extern bool g_verboseHealthChecks;
+
+void updateHealthCheckResult(const std::shared_ptr<DownstreamState>& dss, bool newState);
+bool queueHealthCheck(std::shared_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, bool initial=false);
+void handleQueuedHealthChecks(std::shared_ptr<FDMultiplexer>& mplexer, bool initial=false);
+
#include "ipcipher.hh"
#endif /* HAVE_LIBCRYPTO */
-void setupLuaBindingsProtoBuf(bool client)
+void setupLuaBindingsProtoBuf(bool client, bool configCheck)
{
#ifdef HAVE_LIBCRYPTO
g_lua.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipencrypt", [](const ComboAddress& ca, const std::string& key) {
g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); });
g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(uint8_t)>("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); });
g_lua.registerFunction<std::string(DNSDistProtoBufMessage::*)()>("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); });
- g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) {
+ g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&, boost::optional<uint16_t>)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr, boost::optional<uint16_t> port) {
message.setRequestor(addr);
+ if (port) {
+ message.setRequestorPort(*port);
+ }
});
- g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str) {
+ g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&, boost::optional<uint16_t>)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str, boost::optional<uint16_t> port) {
message.setRequestor(str);
+ if (port) {
+ message.setRequestorPort(*port);
+ }
});
- g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) {
+ g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&, boost::optional<uint16_t>)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr, boost::optional<uint16_t> port) {
message.setResponder(addr);
+ if (port) {
+ message.setResponderPort(*port);
+ }
});
- g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str) {
+ g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&, boost::optional<uint16_t>)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str, boost::optional<uint16_t> port) {
message.setResponder(str);
+ if (port) {
+ message.setResponderPort(*port);
+ }
});
g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setServerIdentity", [](DNSDistProtoBufMessage& message, const std::string& str) {
message.setServerIdentity(str);
});
/* RemoteLogger */
- g_lua.writeFunction("newRemoteLogger", [client](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) {
- if (client) {
+ g_lua.writeFunction("newRemoteLogger", [client,configCheck](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) {
+ if (client || configCheck) {
return std::shared_ptr<RemoteLoggerInterface>(nullptr);
}
return std::shared_ptr<RemoteLoggerInterface>(new RemoteLogger(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? (*maxQueuedEntries*100) : 10000, reconnectWaitTime ? *reconnectWaitTime : 1, client));
});
- g_lua.writeFunction("newFrameStreamUnixLogger", [client](const std::string& address) {
+ g_lua.writeFunction("newFrameStreamUnixLogger", [client,configCheck](const std::string& address) {
#ifdef HAVE_FSTRM
- if (client) {
+ if (client || configCheck) {
return std::shared_ptr<RemoteLoggerInterface>(nullptr);
}
return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_UNIX, address, !client));
#endif /* HAVE_FSTRM */
});
- g_lua.writeFunction("newFrameStreamTcpLogger", [client](const std::string& address) {
+ g_lua.writeFunction("newFrameStreamTcpLogger", [client,configCheck](const std::string& address) {
#if defined(HAVE_FSTRM) && defined(HAVE_FSTRM_TCP_WRITER_INIT)
- if (client) {
+ if (client || configCheck) {
return std::shared_ptr<RemoteLoggerInterface>(nullptr);
}
return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_INET, address, !client));
return newDNSName("powerdns.com."), dnsdist.AAAA, qclass
end
- newServer("2620:0:0ccd::2")
+ newServer({address="2620:0:0ccd::2", checkFunction=myHealthCheck})
Source address selection
------------------------
You can also set the hash perturbation value, see :func:`setWHashedPertubation`. To achieve consistent distribution over :program:`dnsdist` restarts, you will also need to explicitly set the backend's UUIDs with the ``id`` option of :func:`newServer`. You can get the current UUIDs of your backends by calling :func:`showServers` with the ``showUUIDs=true`` option.
-Since 1.5.0, a bounded-load version is also supported, preventing one server from receiving much more queries than the others, even if the distribution of queries is not perfect. This "consistent hashing with bounded loads" algorithm is enabled by setting func:`setConsistentHashingBalancingFactor` to a value other than 0, which is the default. This value is the maximum number of outstanding queries that a given server can have at a given time, as a ratio of the average number of outstanding queries for all the active servers in the pool.
-For example, setting func:`setConsistentHashingBalancingFactor` to 1.5 means that no server will be allowed to have more outstanding queries than 1.5 times the average of all outstanding queries in the pool. The algorithm will try to select a server based on the hash of the qname, as is done when no bounded-load is set, but will disqualify all servers that have more outstanding queries than the average times the factor, until a suitable server is found.
+Since 1.5.0, a bounded-load version is also supported, preventing one server from receiving much more queries than the others, even if the distribution of queries is not perfect. This "consistent hashing with bounded loads" algorithm is enabled by setting :func:`setConsistentHashingBalancingFactor` to a value other than 0, which is the default. This value is the maximum number of outstanding queries that a given server can have at a given time, as a ratio of the average number of outstanding queries for all the active servers in the pool.
+
+For example, setting :func:`setConsistentHashingBalancingFactor` to 1.5 means that no server will be allowed to have more outstanding queries than 1.5 times the average of all outstanding queries in the pool. The algorithm will try to select a server based on the hash of the qname, as is done when no bounded-load is set, but will disqualify all servers that have more outstanding queries than the average times the factor, until a suitable server is found.
The higher the factor, the more imbalance between the servers is allowed.
``roundrobin``
Returns true while the console client is parsing the configuration.
+.. function:: inConfigCheck()
+
+ .. versionadded:: 1.5.0
+
+ Returns true while the configuration is being checked, ie when run with ``--check-config``.
+
.. function:: makeKey()
Generate and print an encryption key.
.. function:: getServer(index) -> Server
+ .. versionchanged:: 1.5.0
+ ``index`` might be an UUID.
+
Get a :class:`Server`
- :param int index: The number of the server (as seen in :func:`showServers`).
+ :param int or str index: The number of the server (as seen in :func:`showServers`) or its UUID as a string.
:returns: The :class:`Server` object or nil
.. function:: getServers()
Returns a table with all defined servers.
.. function:: rmServer(index)
+ rmServer(uuid)
rmServer(server)
+ .. versionchanged:: 1.5.0
+ ``uuid`` selection added.
+
Remove a backend server.
- :param int index: The number of the server (as seen in :func:`showServers`).
+ :param int or str index: The number of the server (as seen in :func:`showServers`), its UUID as a string, or a server object.
:param Server server: A :class:`Server` object as returned by e.g. :func:`getServer`.
Server Functions
:param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
:param int warningRate: If set to a non-zero value, the rate above which a warning message will be issued and a no-op block inserted
+ .. method:: DynBlockRulesGroup:setRCodeRatio(rcode, ratio, seconds, reason, blockingTime, minimumNumberOfResponses [, action [, warningRate]])
+
+ .. versionadded:: 1.5.0
+
+ Adds a rate-limiting rule for the ratio of responses of code ``rcode`` over the total number of responses for a given client.
+
+ :param int rcode: The response code
+ :param int ratio: Ratio of responses per second of the given rcode over the total number of responses for this client to exceed
+ :param int seconds: Number of seconds the ratio has been exceeded
+ :param string reason: The message to show next to the blocks
+ :param int blockingTime: The number of seconds this block to expire
+ :param int minimumNumberOfResponses: How many total responses is required for this rule to apply
+ :param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+ :param int warningRatio: If set to a non-zero value, the ratio above which a warning message will be issued and a no-op block inserted
+
.. method:: DynBlockRulesGroup:setQTypeRate(qtype, rate, seconds, reason, blockingTime [, action [, warningRate]])
.. versionchanged:: 1.3.3
:param int sec: Optional query time in seconds.
:param int usec: Optional query time in additional micro-seconds.
- .. method:: DNSDistProtoBufMessage:setRequestor(address)
+ .. method:: DNSDistProtoBufMessage:setRequestor(address [, port])
+
+ .. versionchanged:: 1.5.0
+ ``port`` optional parameter added.
Set the requestor's address.
:param ComboAddress address: The address to set to
+ :param int port: The requestor source port
+
+ .. method:: DNSDistProtoBufMessage:setRequestorFromString(address [, port])
- .. method:: DNSDistProtoBufMessage:setRequestorFromString(address)
+ .. versionchanged:: 1.5.0
+ ``port`` optional parameter added.
Set the requestor's address from a string.
:param string address: The address to set to
+ :param int port: The requestor source port
- .. method:: DNSDistProtoBufMessage:setResponder(address)
+ .. method:: DNSDistProtoBufMessage:setResponder(address [, port])
+
+ .. versionchanged:: 1.5.0
+ ``port`` optional parameter added.
Set the responder's address.
:param ComboAddress address: The address to set to
+ :param int port: The responder port
+
+ .. method:: DNSDistProtoBufMessage:setResponderFromString(address [, port])
- .. method:: DNSDistProtoBufMessage:setResponderFromString(address)
+ .. versionchanged:: 1.5.0
+ ``port`` optional parameter added.
Set the responder's address.
:param string address: The address to set to
+ :param int port: The responder port
.. method:: DNSDistProtoBufMessage:setResponseCode(rcode)
:param KeyValueLookupKey lookupKey: The key to use for the lookup
.. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction]]]]]])
+
.. versionchanged:: 1.3.1
Added the optional parameters ``expiration``, ``cleanupDelay`` and ``scanFraction``.
:param int v6: The IPv6 netmask length
-.. function:: ERCodeAction(rcode)
+.. function:: ERCodeAction(rcode [, options])
.. versionadded:: 1.4.0
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
Reply immediately by turning the query into a response with the specified EDNS extended ``rcode``.
``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
:param int rcode: The extended RCODE to respond with.
+ :param table options: A table with key: value pairs with options.
-.. function:: HTTPStatusAction(status, body, contentType="")
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: HTTPStatusAction(status, body, contentType="" [, options])
.. versionadded:: 1.4.0
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
Return an HTTP response with a status code of ''status''. For HTTP redirects, ''body'' should be the redirect URL.
:param int status: The HTTP status code to return.
:param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx).
:param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ''application/dns-message''.
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
.. function:: KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)
:param int maxqps: The QPS limit for that pool
:param string poolname: The name of the pool
-.. function:: RCodeAction(rcode)
+.. function:: RCodeAction(rcode [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
Reply immediately by turning the query into a response with the specified ``rcode``.
``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
:param int rcode: The RCODE to respond with.
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
.. function:: RemoteLogAction(remoteLogger[, alterFunction [, options]])
:param string message: The message to include
-.. function:: SpoofAction(ip[, ip[...]])
- SpoofAction(ips)
+.. function:: SpoofAction(ip [, options])
+ SpoofAction(ips [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
:param string ip: An IPv4 and/or IPv6 address to spoof
:param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
+ :param table options: A table with key: value pairs with options.
+
+ Options:
-.. function:: SpoofCNAMEAction(cname)
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: SpoofCNAMEAction(cname [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
Forge a response with the specified CNAME value.
:param string cname: The name to respond with
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
.. function:: TagAction(name, value)
#include <sodium.h>
#endif /* HAVE_LIBSODIUM */
-#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL)
/* OpenSSL < 1.1.0 needs support for threading/locking in the calling application. */
static pthread_mutex_t *openssllocks{nullptr};
OPENSSL_free(openssllocks);
}
-#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) */
+#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL) */
static std::atomic<uint64_t> s_users;
static int s_ticketsKeyIndex{-1};
void registerOpenSSLUser()
{
if (s_users.fetch_add(1) == 0) {
-#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
+#if (OPENSSL_VERSION_NUMBER >= 0x1010000fL && (!defined LIBRESSL_VERSION_NUMBER || LIBRESSL_VERSION_NUMBER >= 0x2070000fL))
+ /* load the default configuration file (or one specified via OPENSSL_CONF),
+ which can then be used to load engines */
+ OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, nullptr);
+#endif
+
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x2090100fL))
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
openssl_thread_setup();
void unregisterOpenSSLUser()
{
if (s_users.fetch_sub(1) == 1) {
-#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x2090100fL))
ERR_free_strings();
EVP_cleanup();
}
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
+ dnsheader dh;
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1");
+ ComboAddress requestor2("192.0.2.2");
+ ComboAddress backend("192.0.2.42");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ unsigned int responseTime = 100 * 1000; /* 100ms */
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock> emptyNMG;
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query ratio";
+ const uint16_t rcode = RCode::ServFail;
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* block above 0.2 ServFail/Total ratio over numberOfSeconds seconds, no warning, minimum number of queries should be at least 51 */
+ dbrg.setRCodeRatio(rcode, 0.2, 0, numberOfSeconds, reason, blockDuration, action, 51);
+
+ {
+ /* insert 20 ServFail and 80 NoErrors from a given client in the last 10s
+ this should not trigger the rule */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < 20; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ dh.rcode = RCode::NoError;
+ for (size_t idx = 0; idx < 80; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert just 50 FormErrs and nothing else, from a given client in the last 10s */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = RCode::FormErr;
+ for (size_t idx = 0; idx < 50; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert 21 ServFails and 79 NoErrors from a given client in the last 10s
+ this should trigger the rule this time */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < 21; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ dh.rcode = RCode::NoError;
+ for (size_t idx = 0; idx < 79; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+
+ {
+ /* insert 11 ServFails and 39 NoErrors from a given client in the last 10s
+ this should NOT trigger the rule since we don't have more than 50 queries */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < 11; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ dh.rcode = RCode::NoError;
+ for (size_t idx = 0; idx < 39; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+}
+
BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
dnsheader dh;
DNSName qname("rings.powerdns.com.");
optional bytes deviceId = 17; // Device ID of the requestor (could be mac address IP address or e.g. IMEI)
optional bool newlyObservedDomain = 18; // True if the domain has not been seen before
optional string deviceName = 19; // Device name of the requestor
+ optional uint32 fromPort = 20; // Source port of the DNS query (client)
+ optional uint32 toPort = 21; // Destination port of the DNS query (server)
}
if(d_ourDB)
delete d_keymetadb;
}
+
+ static uint64_t dbdnssecCacheSizes(const std::string& str);
+ static void clearAllCaches();
+ static void clearCaches(const DNSName& name);
+
bool doesDNSSEC();
bool isSecuredZone(const DNSName& zone);
- static uint64_t dbdnssecCacheSizes(const std::string& str);
keyset_t getEntryPoints(const DNSName& zname);
keyset_t getKeys(const DNSName& zone, bool useCache = true);
DNSSECPrivateKey getKeyById(const DNSName& zone, unsigned int id);
bool checkNSEC3PARAM(const NSEC3PARAMRecordContent& ns3p, string& msg);
bool setNSEC3PARAM(const DNSName& zname, const NSEC3PARAMRecordContent& n3p, const bool& narrow=false);
bool unsetNSEC3PARAM(const DNSName& zname);
- void clearAllCaches();
- void clearCaches(const DNSName& name);
bool getPreRRSIGs(UeberBackend& db, const DNSName& signer, const DNSName& qname, const DNSName& wildcardname, const QType& qtype, DNSResourceRecord::Place, vector<DNSZoneRecord>& rrsigs, uint32_t signTTL);
bool isPresigned(const DNSName& zname);
bool setPresigned(const DNSName& zname);
string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
{
- DNSSECKeeper dk;
ostringstream os;
int ret=0;
for (vector<string>::const_iterator i=++parts.begin();i<parts.end();++i) {
ret+=purgeAuthCaches(*i);
if(!boost::ends_with(*i, "$"))
- dk.clearCaches(DNSName(*i));
+ DNSSECKeeper::clearCaches(DNSName(*i));
else
- dk.clearAllCaches(); // at least we do what we promise.. and a bit more!
+ DNSSECKeeper::clearAllCaches(); // at least we do what we promise.. and a bit more!
}
}
else {
ret = purgeAuthCaches();
- dk.clearAllCaches();
+ DNSSECKeeper::clearAllCaches();
}
os<<ret;
}
Policy pol;
- if (!allEmpty) {
- count = 0;
- for(const auto& z : d_zones) {
- if (zoneEnabled[count] && z->findExactNSPolicy(qname, pol)) {
- // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
- return pol;
- }
+ if (allEmpty) {
+ return pol;
+ }
+
+ /* prepare the wildcard-based names */
+ std::vector<DNSName> wcNames;
+ wcNames.reserve(qname.countLabels());
+ DNSName s(qname);
+ while (s.chopOff()){
+ wcNames.emplace_back(g_wildcarddnsname+s);
+ }
+
+ count = 0;
+ for(const auto& z : d_zones) {
+ if (!zoneEnabled[count]) {
++count;
+ continue;
}
- DNSName s(qname);
- while(s.chopOff()){
- count = 0;
- for(const auto& z : d_zones) {
- if (zoneEnabled[count] && z->findExactNSPolicy(g_wildcarddnsname+s, pol)) {
- // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
- return pol;
- }
- ++count;
+ if (z->findExactNSPolicy(qname, pol)) {
+ // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+ return pol;
+ }
+
+ for (const auto& wc : wcNames) {
+ if (z->findExactNSPolicy(wc, pol)) {
+ // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+ return pol;
}
}
+ ++count;
}
return pol;
DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies) const
{
- // cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
+ // cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
std::vector<bool> zoneEnabled(d_zones.size());
size_t count = 0;
bool allEmpty = true;
enabled = false;
}
else {
- if (z->hasQNamePolicies()) {
+ if (z->hasQNamePolicies() || z->hasClientPolicies()) {
allEmpty = false;
}
else {
}
Policy pol;
- if (!allEmpty) {
- count = 0;
- for(const auto& z : d_zones) {
- if (zoneEnabled[count] && z->findExactQNamePolicy(qname, pol)) {
- // cerr<<"Had a hit on the name of the query"<<endl;
- return pol;
- }
+ if (allEmpty) {
+ return pol;
+ }
+
+ /* prepare the wildcard-based names */
+ std::vector<DNSName> wcNames;
+ wcNames.reserve(qname.countLabels());
+ DNSName s(qname);
+ while (s.chopOff()){
+ wcNames.emplace_back(g_wildcarddnsname+s);
+ }
+
+ count = 0;
+ for (const auto& z : d_zones) {
+ if (!zoneEnabled[count]) {
++count;
+ continue;
}
- DNSName s(qname);
- while(s.chopOff()){
- count = 0;
- for(const auto& z : d_zones) {
- if (zoneEnabled[count] && z->findExactQNamePolicy(g_wildcarddnsname+s, pol)) {
- // cerr<<"Had a hit on the name of the query"<<endl;
- return pol;
- }
- ++count;
+ if (z->findExactQNamePolicy(qname, pol)) {
+ // cerr<<"Had a hit on the name of the query"<<endl;
+ return pol;
+ }
+
+ for (const auto& wc : wcNames) {
+ if (z->findExactQNamePolicy(wc, pol)) {
+ // cerr<<"Had a hit on the name of the query"<<endl;
+ return pol;
}
}
- }
- count = 0;
- for(const auto& z : d_zones) {
- if (zoneEnabled[count] && z->findClientPolicy(ca, pol)) {
- // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
+ if (z->findClientPolicy(ca, pol)) {
+ // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
return pol;
}
+
++count;
}
{
Policy pol;
ComboAddress ca;
- for(const auto& r : records) {
- if(r.d_place != DNSResourceRecord::ANSWER)
+ for (const auto& r : records) {
+ if (r.d_place != DNSResourceRecord::ANSWER)
continue;
- if(r.d_type == QType::A) {
+ if (r.d_type == QType::A) {
if (auto rec = getRR<ARecordContent>(r)) {
ca = rec->getCA();
}
else
continue;
- for(const auto& z : d_zones) {
+ for (const auto& z : d_zones) {
const auto zoneName = z->getName();
- if(zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+ if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
continue;
}
- if(z->findResponsePolicy(ca, pol)) {
+ if (z->findResponsePolicy(ca, pol)) {
return pol;
}
}
return ntohs(sin4.sin_port);
}
- ComboAddress setPort(uint16_t port) const
+ void setPort(uint16_t port)
{
- ComboAddress ret(*this);
- ret.sin4.sin_port=htons(port);
- return ret;
+ sin4.sin_port = htons(port);
}
void reset()
pol.d_name = std::make_shared<std::string>(name);
});
d_lw->registerMember("policyKind", &DNSFilterEngine::Policy::d_kind);
+ d_lw->registerMember("policyType", &DNSFilterEngine::Policy::d_type);
d_lw->registerMember("policyTTL", &DNSFilterEngine::Policy::d_ttl);
d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
[](const DNSFilterEngine::Policy& pol) -> std::string {
/* tv will be updated to 'now' before run returns */
/* timeout is in ms */
+ /* returns 0 on timeout, -1 in case of error (but all implementations
+ actually throw in that case) and the number of ready events otherwise */
virtual int run(struct timeval* tv, int timeout=500) = 0;
- /* timeout is in ms, 0 will return immediatly, -1 will block until at least one FD is ready */
+ /* timeout is in ms, 0 will return immediately, -1 will block until at least one FD is ready */
virtual void getAvailableFDs(std::vector<int>& fds, int timeout) = 0;
//! Add an fd to the read watch list - currently an fd can only be on one list at a time!
int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName& tsigkeyname)
{
- ComboAddress remote = p.getRemote().setPort(53);
+ ComboAddress remote = p.getRemote();
if(p.hasEDNSSubnet() && ::arg().contains("trusted-notification-proxy", remote.toString())) {
- remote = p.getRealRemote().getNetwork().setPort(53);
+ remote = p.getRealRemote().getNetwork();
}
+ remote.setPort(53);
Resolver::res_t nsset;
try {
}
Netmask requestorNM(remote, remote.sin4.sin_family == AF_INET ? maskV4 : maskV6);
- const ComboAddress& requestor = requestorNM.getMaskedNetwork();
+ ComboAddress requestor = requestorNM.getMaskedNetwork();
+ requestor.setPort(remote.getPort());
RecProtoBufMessage message(DNSProtoBufMessage::Query, uniqueId, &requestor, &local, qname, qtype, qclass, id, tcp, len);
message.setServerIdentity(SyncRes::s_serverID);
message.setEDNSSubnet(ednssubnet, ednssubnet.isIPv4() ? maskV4 : maskV6);
#ifdef HAVE_PROTOBUF
if (checkProtobufExport(luaconfsLocal)) {
Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- const ComboAddress& requestor = requestorNM.getMaskedNetwork();
+ ComboAddress requestor = requestorNM.getMaskedNetwork();
+ requestor.setPort(dc->d_source.getPort());
pbMessage = RecProtoBufMessage(RecProtoBufMessage::Response, dc->d_uuid, &requestor, &dc->d_destination, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass, dc->d_mdp.d_header.id, dc->d_tcp, 0);
pbMessage->setServerIdentity(SyncRes::s_serverID);
pbMessage->setEDNSSubnet(dc->d_ednssubnet.source, dc->d_ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
}
// Check if the query has a policy attached to it
- if (wantsRPZ) {
+ if (wantsRPZ && appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
appliedPolicy = luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_source, sr.d_discardedPolicies);
}
// Query got not handled for QNAME Policy reasons, now actually go out to find an answer
try {
+ sr.d_appliedPolicy = appliedPolicy;
res = sr.beginResolve(dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_mdp.d_qclass, ret);
shouldNotValidate = sr.wasOutOfBand();
}
- catch(ImmediateServFailException &e) {
- if(g_logCommonErrors)
+ catch(const ImmediateServFailException &e) {
+ if(g_logCommonErrors) {
g_log<<Logger::Notice<<"Sending SERVFAIL to "<<dc->getRemote()<<" during resolve of '"<<dc->d_mdp.d_qname<<"' because: "<<e.reason<<endl;
+ }
res = RCode::ServFail;
}
-
+ catch(const PolicyHitException& e) {
+ res = -2;
+ }
dq.validationState = sr.getValidationState();
// During lookup, an NSDNAME or NSIP trigger was hit in RPZ
}
}
- if (wantsRPZ) {
+ if (wantsRPZ && appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
appliedPolicy = luaconfsLocal->dfe.getPostPolicy(ret, sr.d_discardedPolicies);
}
}
}
}
- catch(ImmediateServFailException &e) {
+ catch(const ImmediateServFailException &e) {
if(g_logCommonErrors)
g_log<<Logger::Notice<<"Sending SERVFAIL to "<<dc->getRemote()<<" during validation of '"<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<"' because: "<<e.reason<<endl;
pw.getHeader()->rcode=RCode::ServFail;
#ifdef HAVE_PROTOBUF
if(t_protobufServers && logResponse && !(luaconfsLocal->protobufExportConfig.taggedOnly && pbMessage->getAppliedPolicy().empty() && pbMessage->getPolicyTags().empty())) {
Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- const ComboAddress& requestor = requestorNM.getMaskedNetwork();
+ ComboAddress requestor = requestorNM.getMaskedNetwork();
+ requestor.setPort(source.getPort());
pbMessage->update(uniqueId, &requestor, &destination, false, dh->id);
pbMessage->setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
if (g_useKernelTimestamp && tv.tv_sec) {
try {
doSecPoll(&last_secpoll);
}
- catch(std::exception& e)
+ catch(const std::exception& e)
{
g_log<<Logger::Error<<"Exception while performing security poll: "<<e.what()<<endl;
}
- catch(PDNSException& e)
+ catch(const PDNSException& e)
{
g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
}
- catch(ImmediateServFailException &e)
+ catch(const ImmediateServFailException &e)
{
g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
}
+ catch(const PolicyHitException& e) {
+ g_log<<Logger::Error<<"Policy hit while performing security poll"<<endl;
+ }
catch(...)
{
g_log<<Logger::Error<<"Exception while performing security poll"<<endl;
}
#endif /* NOD_ENABLED */
+static void checkSocketDir(void)
+{
+ struct stat st;
+ string dir(::arg()["socket-dir"]);
+ string msg;
+
+ if (stat(dir.c_str(), &st) == -1) {
+ msg = "it does not exist or cannot access";
+ }
+ else if (!S_ISDIR(st.st_mode)) {
+ msg = "it is not a directory";
+ }
+ else if (access(dir.c_str(), R_OK | W_OK | X_OK) != 0) {
+ msg = "cannot read, write or search";
+ } else {
+ return;
+ }
+ g_log << Logger::Error << "Problem with socket directory " << dir << ": " << msg << "; see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0-or-master" << endl;
+ _exit(1);
+}
+
static int serviceMain(int argc, char*argv[])
{
g_log.setName(s_programname);
SyncRes::s_qnameminimization = ::arg().mustDo("qname-minimization");
+ if (SyncRes::s_qnameminimization) {
+ // With an empty cache, a rev ipv6 query with dnssec enabled takes
+ // almost 100 queries. Default maxqperq is 60.
+ SyncRes::s_maxqperq = std::max(SyncRes::s_maxqperq, static_cast<unsigned int>(100));
+ }
+
SyncRes::s_hardenNXD = SyncRes::HardenNXD::DNSSEC;
string value = ::arg()["nothing-below-nxdomain"];
if (value == "yes") {
{
SuffixMatchNode dontThrottleNames;
vector<string> parts;
- stringtok(parts, ::arg()["dont-throttle-names"]);
+ stringtok(parts, ::arg()["dont-throttle-names"], " ,");
for (const auto &p : parts) {
dontThrottleNames.add(DNSName(p));
}
g_dontThrottleNames.setState(std::move(dontThrottleNames));
NetmaskGroup dontThrottleNetmasks;
- stringtok(parts, ::arg()["dont-throttle-netmasks"]);
+ stringtok(parts, ::arg()["dont-throttle-netmasks"], " ,");
for (const auto &p : parts) {
dontThrottleNetmasks.addMask(Netmask(p));
}
g_log<<Logger::Info<<"Chrooted to '"<<::arg()["chroot"]<<"'"<<endl;
}
+ checkSocketDir();
+
s_pidfname=::arg()["socket-dir"]+"/"+s_programname+".pid";
if(!s_pidfname.empty())
unlink(s_pidfname.c_str()); // remove possible old pid file
::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1232";
::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size")="1232";
::arg().set("minimum-ttl-override", "Set under adverse conditions, a minimum TTL")="0";
- ::arg().set("max-qperq", "Maximum outgoing queries per query")="50";
+ ::arg().set("max-qperq", "Maximum outgoing queries per query")="60";
::arg().set("max-total-msec", "Maximum total wall-clock time per query in milliseconds, 0 for unlimited")="7000";
::arg().set("max-recursion-depth", "Maximum number of internal recursion calls per query, 0 for unlimited")="40";
::arg().set("max-udp-queries-per-round", "Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing")="10000";
#endif /* HAVE_PROTOBUF */
}
+void DNSProtoBufMessage::setRequestorPort(uint16_t port)
+{
+#ifdef HAVE_PROTOBUF
+ d_message.set_fromport(port);
+#endif /* HAVE_PROTOBUF */
+}
+
void DNSProtoBufMessage::setRequestorId(const std::string& requestorId)
{
#ifdef HAVE_PROTOBUF
#endif /* HAVE_PROTOBUF */
}
+void DNSProtoBufMessage::setResponderPort(uint16_t port)
+{
+#ifdef HAVE_PROTOBUF
+ d_message.set_toport(port);
+#endif /* HAVE_PROTOBUF */
+}
+
void DNSProtoBufMessage::serialize(std::string& data) const
{
#ifdef HAVE_PROTOBUF
if (responder) {
setResponder(*responder);
+ setResponderPort(responder->getPort());
}
if (requestor) {
setRequestor(*requestor);
+ setRequestorPort(requestor->getPort());
}
}
void serialize(std::string& data) const;
void setRequestor(const std::string& requestor);
void setRequestor(const ComboAddress& requestor);
+ void setRequestorPort(uint16_t port);
void setResponder(const std::string& responder);
void setResponder(const ComboAddress& responder);
+ void setResponderPort(uint16_t port);
void setRequestorId(const std::string& requestorId);
void setDeviceId(const std::string& deviceId);
void setDeviceName(const std::string& deviceName);
Changelogs for 4.3.x
====================
+.. changelog::
+ :version: 4.3.0-beta2
+ :released: 16th of January 2020
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8704
+
+ Add the source and destination ports to the protobuf msg.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8673
+
+ Debian postinst / do not fail on user creation if it already exists.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8685
+ :tickets: 8676
+
+ Parsing `dont-throttle-names` and `dont-throttle-netmasks` as comma separated lists. (costypetrisor)
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8692
+ :tickets: 8664
+
+ An Opt-Out NSEC3 RR only proves that there is no secure delegation.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8670
+ :tickets: 8642
+
+ Fix wrong zoneCuts caused by cache only lookup.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8675
+ :tickets: 8646
+
+ Increase default max-qperq.
+
.. changelog::
:version: 4.3.0-beta1
:released: 12th of December 2019
``max-qperq``
-------------
- Integer
-- Default: 50
+- Default: 60
The maximum number of outgoing queries that will be sent out during the resolution of a single client query.
This is used to limit endlessly chasing CNAME redirections.
+If qname-minimization is enabled, the number will be forced to be 100
+at a minimum to allow for the extra queries qname-minimization generates when the cache is empty.
.. _setting-max-negative-ttl:
zone2->setName("Unit test policy 1");
const DNSName bad("bad.example.com.");
+ const DNSName badWildcard("*.bad-wildcard.example.com.");
+ const DNSName badUnderWildcard("sub.bad-wildcard.example.com.");
- zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1.example.net.")}));
- zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2.example.net.")}));
+ zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
+ zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
+ zone1->addQNameTrigger(badWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1b.example.net.")}));
+ zone2->addQNameTrigger(badUnderWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2b.example.net.")}));
dfe.addZone(zone1);
dfe.addZone(zone2);
BOOST_CHECK(record.d_class == QClass::IN);
auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
BOOST_CHECK(content != nullptr);
- BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1.example.net.");
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
+ }
+
+ {
+ /* zone 2 has an exact match for badUnderWildcard, but the wildcard from the first zone should match first */
+ const auto matchingPolicy = dfe.getQueryPolicy(badUnderWildcard, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+ auto records = matchingPolicy.getCustomRecords(badUnderWildcard, QType::A);
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ const auto& record = records.at(0);
+ BOOST_CHECK(record.d_type == QType::CNAME);
+ BOOST_CHECK(record.d_class == QClass::IN);
+ auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+ BOOST_CHECK(content != nullptr);
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1b.example.net.");
}
{
BOOST_CHECK(record.d_class == QClass::IN);
auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
BOOST_CHECK(content != nullptr);
- BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1.example.net.");
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
}
{
BOOST_CHECK(record.d_class == QClass::IN);
auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
BOOST_CHECK(content != nullptr);
- BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden2.example.net.");
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden2a.example.net.");
}
{
records.push_back(rec);
}
-void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records)
+void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, bool optOut)
{
NSEC3RecordContent nrc;
nrc.d_algorithm = 1;
- nrc.d_flags = 0;
+ nrc.d_flags = optOut ? 1 : 0;
nrc.d_iterations = iterations;
nrc.d_salt = salt;
nrc.d_nexthash = hashedNext;
records.push_back(rec);
}
-void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations)
+void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations, bool optOut)
{
static const std::string salt = "deadbeef";
std::string hashed = hashQNameWithSalt(salt, iterations, domain);
- addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, next, salt, iterations, types, ttl, records);
+ addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, next, salt, iterations, types, ttl, records, optOut);
}
-void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations)
+void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations, bool optOut)
{
static const std::string salt = "deadbeef";
std::string hashed = hashQNameWithSalt(salt, iterations, domain);
incrementHash(hashedNext);
decrementHash(hashed);
- addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, hashedNext, salt, iterations, types, ttl, records);
+ addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, hashedNext, salt, iterations, types, ttl, records, optOut);
}
void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys)
dsAnchors[name].insert(keys[name].second);
}
-int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut, boost::optional<time_t> now)
+int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut, boost::optional<time_t> now, bool nsec3, bool optOut)
{
if (type == QType::DS) {
auth.chopOff();
/* sign the SOA */
addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, now);
/* add a NSEC denying the DS */
- std::set<uint16_t> types = {QType::NSEC};
+ std::set<uint16_t> types = {nsec3 ? QType::NSEC : QType::NSEC3};
if (proveCut) {
types.insert(QType::NS);
}
- addNSECRecordToLW(domain, DNSName("z") + domain, types, 600, res->d_records);
+ if (!nsec3) {
+ addNSECRecordToLW(domain, DNSName("z") + domain, types, 600, res->d_records);
+ }
+ else {
+ addNSEC3UnhashedRecordToLW(domain, auth, (DNSName("z") + domain).toString(), types, 600, res->d_records, 10, optOut);
+ }
+
addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, now);
}
}
void addNSECRecordToLW(const DNSName& domain, const DNSName& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records);
-void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records);
+void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, bool optOut = false);
-void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations = 10);
+void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations = 10, bool optOut = false);
-void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations = 10);
+void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations = 10, bool OptOut = false);
void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys);
void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys, map<DNSName, dsmap_t>& dsAnchors);
-int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut = true, boost::optional<time_t> now = boost::none);
+int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut = true, boost::optional<time_t> now = boost::none, bool nsec3 = false, bool optOut = false);
int basicRecordsForQnameMinimization(LWResult* res, const DNSName& domain, int type);
g_luaconfs.setState(luaconfsCopy);
vector<DNSRecord> ret;
- int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
- BOOST_CHECK_EQUAL(res, -2);
- BOOST_CHECK_EQUAL(ret.size(), 0U);
+ BOOST_CHECK_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), PolicyHitException);
}
BOOST_AUTO_TEST_CASE(test_nameserver_ipv6_rpz)
g_luaconfs.setState(luaconfsCopy);
vector<DNSRecord> ret;
- int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
- BOOST_CHECK_EQUAL(res, -2);
- BOOST_CHECK_EQUAL(ret.size(), 0U);
+ BOOST_CHECK_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), PolicyHitException);
}
BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz)
g_luaconfs.setState(luaconfsCopy);
vector<DNSRecord> ret;
- int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
- BOOST_CHECK_EQUAL(res, -2);
- BOOST_CHECK_EQUAL(ret.size(), 0U);
+ BOOST_CHECK_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), PolicyHitException);
}
BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz_disabled)
BOOST_CHECK_EQUAL(queriesCount, 7U);
}
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_optout)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("powerdns.com.");
+ const ComboAddress targetAddr("192.0.2.42");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS) {
+ if (domain == target) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), DNSName("a.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* next closer */
+ addNSEC3UnhashedRecordToLW(DNSName("oowerdns.com."), DNSName("com."), DNSName("qowerdns.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ return 1;
+ }
+ else {
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
+ }
+ }
+ else if (type == QType::DNSKEY) {
+ if (domain == g_rootdnsname || domain == DNSName("com.")) {
+ setLWResult(res, 0, true, false, true);
+ addDNSKEY(keys, domain, 300, res->d_records);
+ addRRSIG(keys, res->d_records, domain, 300);
+ return 1;
+ }
+ else {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return 1;
+ }
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == DNSName("com.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ else {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+ /* no DS */
+ /* closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), DNSName("a.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* next closer */
+ addNSEC3UnhashedRecordToLW(DNSName("oowerdns.com."), DNSName("com."), DNSName("qowerdns.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.2:53")) {
+ setLWResult(res, 0, true, false, true);
+ if (type == QType::NS) {
+ addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+ }
+ else {
+ addRecordToLW(res, domain, QType::A, targetAddr.toString());
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 6);
+ BOOST_CHECK(ret[0].d_type == QType::SOA);
+ BOOST_CHECK_EQUAL(queriesCount, 4);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 6);
+ BOOST_CHECK(ret[0].d_type == QType::SOA);
+ BOOST_CHECK_EQUAL(queriesCount, 4);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_optout)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("powerdns.com.");
+ const ComboAddress targetAddr("192.0.2.42");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS) {
+ if (domain == target) {
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), DNSName("a.com.").toStringNoDot(), {QType::SOA, QType::NS, QType::DS, QType::DNSKEY, QType::RRSIG, QType::NSEC3PARAM}, 600, res->d_records, 10, true);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* next closer */
+ addNSEC3UnhashedRecordToLW(DNSName("oowerdns.com."), DNSName("com."), DNSName("qowerdns.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ return 1;
+ }
+ else {
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
+ }
+ }
+ else if (type == QType::DNSKEY) {
+ if (domain == g_rootdnsname || domain == DNSName("com.")) {
+ setLWResult(res, 0, true, false, true);
+ addDNSKEY(keys, domain, 300, res->d_records);
+ addRRSIG(keys, res->d_records, domain, 300);
+ return 1;
+ }
+ else {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return 1;
+ }
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == DNSName("com.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ else {
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), DNSName("a.com.").toStringNoDot(), {QType::SOA, QType::NS, QType::DS, QType::DNSKEY, QType::RRSIG, QType::NSEC3PARAM}, 600, res->d_records, 10, true);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* next closer */
+ addNSEC3UnhashedRecordToLW(DNSName("oowerdns.com."), DNSName("com."), DNSName("qowerdns.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 6);
+ BOOST_CHECK(ret[0].d_type == QType::SOA);
+ BOOST_CHECK_EQUAL(queriesCount, 6);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 6);
+ BOOST_CHECK(ret[0].d_type == QType::SOA);
+ BOOST_CHECK_EQUAL(queriesCount, 6);
+}
+
BOOST_AUTO_TEST_CASE(test_dnssec_secure_direct_ds)
{
/*
bool showflags = false;
bool hidesoadetails = false;
bool doh = false;
- bool stdin = false;
+ bool fromstdin = false;
boost::optional<Netmask> ednsnm;
uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
char *xpfsrc = NULL, *xpfdst = NULL;
if (*argv[1] == 'h') {
doh = true;
} else if(strcmp(argv[1], "stdin") == 0) {
- stdin = true;
+ fromstdin = true;
} else {
dest = ComboAddress(argv[1] + (*argv[1] == '@'), atoi(argv[2]));
}
#else
throw PDNSException("please link sdig against libcurl for DoH support");
#endif
- } else if (stdin) {
+ } else if (fromstdin) {
std::istreambuf_iterator<char> begin(std::cin), end;
reply = string(begin, end);
printReply(reply, showflags, hidesoadetails);
QLOG("Delegation seen, continue at step 1");
break;
}
+
if (res != RCode::NoError) {
// Case 5: unexpected answer
QLOG("Step5: other rcode, last effort final resolve");
* \param beenthere
* \param fromCache tells the caller the result came from the cache, may be nullptr
* \param stopAtDelegation if non-nullptr and pointed-to value is Stop requests the callee to stop at a delegation, if so pointed-to value is set to Stopped
- * \return DNS RCODE or -1 (Error) or -2 (RPZ hit)
+ * \return DNS RCODE or -1 (Error)
*/
int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool *fromCache, StopAtDelegation *stopAtDelegation)
{
LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
- if (res == -2)
- return res;
-
return res<0 ? RCode::ServFail : res;
}
d_requireAuthData = false;
d_DNSSECValidationRequested = false;
- vState newState = Indeterminate;
- res_t resv4;
- // If IPv4 ever becomes second class, we should revisit this
- if (doResolve(qname, QType::A, resv4, depth+1, beenthere, newState) == 0) { // this consults cache, OR goes out
- for (auto const &i : resv4) {
- if (i.d_type == QType::A) {
- if (auto rec = getRR<ARecordContent>(i)) {
- ret.push_back(rec->getCA(53));
+ try {
+ vState newState = Indeterminate;
+ res_t resv4;
+ // If IPv4 ever becomes second class, we should revisit this
+ if (doResolve(qname, QType::A, resv4, depth+1, beenthere, newState) == 0) { // this consults cache, OR goes out
+ for (auto const &i : resv4) {
+ if (i.d_type == QType::A) {
+ if (auto rec = getRR<ARecordContent>(i)) {
+ ret.push_back(rec->getCA(53));
+ }
}
}
}
- }
- if (s_doIPv6) {
- if (ret.empty()) {
- // We did not find IPv4 addresses, try to get IPv6 ones
- newState = Indeterminate;
- res_t resv6;
- if (doResolve(qname, QType::AAAA, resv6, depth+1, beenthere, newState) == 0) { // this consults cache, OR goes out
- for (const auto &i : resv6) {
- if (i.d_type == QType::AAAA) {
- if (auto rec = getRR<AAAARecordContent>(i))
- ret.push_back(rec->getCA(53));
+ if (s_doIPv6) {
+ if (ret.empty()) {
+ // We did not find IPv4 addresses, try to get IPv6 ones
+ newState = Indeterminate;
+ res_t resv6;
+ if (doResolve(qname, QType::AAAA, resv6, depth+1, beenthere, newState) == 0) { // this consults cache, OR goes out
+ for (const auto &i : resv6) {
+ if (i.d_type == QType::AAAA) {
+ if (auto rec = getRR<AAAARecordContent>(i))
+ ret.push_back(rec->getCA(53));
+ }
}
}
- }
- } else {
- // We have some IPv4 records, don't bother with going out to get IPv6, but do consult the cache
- // Once IPv6 adoption matters, this needs to be revisited
- res_t cset;
- if (t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) {
- for (const auto &i : cset) {
- if (i.d_ttl > (unsigned int)d_now.tv_sec ) {
- if (auto rec = getRR<AAAARecordContent>(i)) {
- ret.push_back(rec->getCA(53));
+ } else {
+ // We have some IPv4 records, don't bother with going out to get IPv6, but do consult the cache
+ // Once IPv6 adoption matters, this needs to be revisited
+ res_t cset;
+ if (t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) {
+ for (const auto &i : cset) {
+ if (i.d_ttl > (unsigned int)d_now.tv_sec ) {
+ if (auto rec = getRR<AAAARecordContent>(i)) {
+ ret.push_back(rec->getCA(53));
+ }
}
}
}
}
}
}
+ catch (const PolicyHitException& e) {
+ /* we ignore a policy hit while trying to retrieve the addresses
+ of a NS and keep processing the current query */
+ }
d_requireAuthData = oldRequireAuthData;
d_DNSSECValidationRequested = oldValidationRequested;
vState neValidationState = ne->d_validationState;
dState expectedState = res == RCode::NXDomain ? NXDOMAIN : NXQTYPE;
dState denialState = getDenialValidationState(*ne, state, expectedState, false);
- updateDenialValidationState(neValidationState, ne->d_name, state, denialState, expectedState, qtype == QType::DS);
+ updateDenialValidationState(neValidationState, ne->d_name, state, denialState, expectedState, qtype == QType::DS || expectedState == NXDOMAIN);
}
if (state != Indeterminate) {
/* validation succeeded, let's update the cache entry so we don't have to validate again */
bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers)
{
- if(d_wantsRPZ) {
+ /* we skip RPZ processing if:
+ - it was disabled (d_wantsRPZ is false) ;
+ - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
+ the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
+ process any further RPZ rules.
+ */
+ if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
for (auto const &ns : nameservers) {
d_appliedPolicy = dfe.getProcessingPolicy(ns.first, d_discardedPolicies);
if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
bool SyncRes::nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress& remoteIP)
{
- if (d_wantsRPZ) {
+ /* we skip RPZ processing if:
+ - it was disabled (d_wantsRPZ is false) ;
+ - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
+ the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
+ process any further RPZ rules.
+ */
+ if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
d_appliedPolicy = dfe.getProcessingPolicy(remoteIP, d_discardedPolicies);
if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
LOG(" (blocked by RPZ policy '"+(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")+"')");
else {
if (denialState == OPTOUT && allowOptOut) {
LOG(d_prefix<<"OPT-out denial found for "<<neName<<endl);
- neValidationState = Secure;
- return;
+ /* rfc5155 states:
+ "The AD bit, as defined by [RFC4035], MUST NOT be set when returning a
+ response containing a closest (provable) encloser proof in which the
+ NSEC3 RR that covers the "next closer" name has the Opt-Out bit set.
+
+ This rule is based on what this closest encloser proof actually
+ proves: names that would be covered by the Opt-Out NSEC3 RR may or
+ may not exist as insecure delegations. As such, not all the data in
+ responses containing such closest encloser proofs will have been
+ cryptographically verified, so the AD bit cannot be set."
+
+ At best the Opt-Out NSEC3 RR proves that there is no signed DS (so no
+ secure delegation).
+ */
+ neValidationState = Insecure;
}
else if (denialState == INSECURE) {
LOG(d_prefix<<"Insecure denial found for "<<neName<<", returning Insecure"<<endl);
if (state == Secure) {
dState denialState = getDenialValidationState(ne, state, NXDOMAIN, false);
- updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, NXDOMAIN, false);
+ updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, NXDOMAIN, true);
}
else {
ne.d_validationState = state;
if (denialState == NXQTYPE || denialState == OPTOUT || denialState == INSECURE) {
ne.d_ttd = lowestTTL + d_now.tv_sec;
ne.d_validationState = Secure;
+ if (denialState == OPTOUT) {
+ ne.d_validationState = Insecure;
+ }
LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
if(!wasVariable()) {
nameservers.clear();
for (auto const &nameserver : nsset) {
- if (d_wantsRPZ) {
+ if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
d_appliedPolicy = dfe.getProcessingPolicy(nameserver, d_discardedPolicies);
if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
LOG("however "<<nameserver<<" was blocked by RPZ policy '"<<(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")<<"'"<<endl);
- *rcode = -2;
- return true;
+ throw PolicyHitException();
}
}
nameservers.insert({nameserver, {{}, false}});
/** returns:
* -1 in case of no results
- * -2 when a FilterEngine Policy was hit
* rcode otherwise
*/
int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype,
LOG(prefix<<qname<<": Cache consultations done, have "<<(unsigned int)nameservers.size()<<" NS to contact");
if (nameserversBlockedByRPZ(luaconfsLocal->dfe, nameservers)) {
- return -2;
+ /* RPZ hit */
+ throw PolicyHitException();
}
LOG(endl);
}
}
LOG(endl);
- if (hitPolicy) //implies d_wantsRPZ
- return -2;
+ if (hitPolicy) { //implies d_wantsRPZ
+ /* RPZ hit */
+ throw PolicyHitException();
+ }
}
for(remoteIP = remoteIPs.cbegin(); remoteIP != remoteIPs.cend(); ++remoteIP) {
g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got ImmediateServFailException: "<<e.reason<<endl;
ret.clear();
}
+ catch(const PolicyHitException& e) {
+ g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got a policy hit"<<endl;
+ ret.clear();
+ }
catch(const std::exception& e) {
g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got STL error: "<<e.what()<<endl;
ret.clear();
catch(const ImmediateServFailException& e) {
g_log<<Logger::Error<<"Failed to update . records, got an exception: "<<e.reason<<endl;
}
+ catch(const PolicyHitException& e) {
+ g_log<<Logger::Error<<"Failed to update . records, got a policy hit"<<endl;
+ ret.clear();
+ }
catch(const std::exception& e) {
g_log<<Logger::Error<<"Failed to update . records, got an exception: "<<e.what()<<endl;
}
return i->value;
}
- counter_t incr(const ComboAddress& t, const struct timeval & now)
+ counter_t incr(const ComboAddress& address, const struct timeval& now)
{
- auto i = d_cont.insert(t).first;
+ auto i = d_cont.insert(address).first;
if (i->value < std::numeric_limits<counter_t>::max()) {
i->value++;
}
auto &ind = d_cont.get<ComboAddress>();
- ind.modify(i, [t = now.tv_sec](value_t &val) { val.last = t;});
+ time_t tm = now.tv_sec;
+ ind.modify(i, [tm](value_t &val) { val.last = tm; });
return i->value;
}
string reason; //! Print this to tell the user what went wrong
};
+class PolicyHitException
+{
+};
+
typedef boost::circular_buffer<ComboAddress> addrringbuf_t;
extern thread_local std::unique_ptr<addrringbuf_t> t_servfailremotes, t_largeanswerremotes, t_remotes, t_bogusremotes, t_timeouts;
else
remote_text = packet->getRemote().toString();
g_log << Logger::Notice<<"TCP Remote "<< remote_text <<" wants '" << packet->qdomain<<"|"<<packet->qtype.getName() <<
- "', do = " <<packet->d_dnssecOk <<", bufsize = "<< packet->getMaxReplyLen()<<": ";
+ "', do = " <<packet->d_dnssecOk <<", bufsize = "<< packet->getMaxReplyLen();
}
if(PC.enabled()) {
if(packet->couldBeCached() && PC.get(*packet, *cached)) { // short circuit - does the PacketCache recognize this question?
if(logDNSQueries)
- g_log<<"packetcache HIT"<<endl;
+ g_log<<": packetcache HIT"<<endl;
cached->setRemote(&packet->d_remote);
cached->d.id=packet->d.id;
cached->d.rd=packet->d.rd; // copy in recursion desired bit
continue;
}
if(logDNSQueries)
- g_log<<"packetcache MISS"<<endl;
+ g_log<<": packetcache MISS"<<endl;
+ } else {
+ if (logDNSQueries) {
+ g_log<<endl;
+ }
}
{
Lock l(&s_plock);
}
DNSSECKeeper dk(&db);
- dk.clearCaches(target);
+ DNSSECKeeper::clearCaches(target);
bool securedZone = dk.isSecuredZone(target);
bool presignedZone = dk.isPresigned(target);
NSEC3PARAMRecordContent ns3pr;
bool narrow;
- dk.clearCaches(q->qdomain);
+ DNSSECKeeper::clearCaches(q->qdomain);
bool securedZone = dk.isSecuredZone(q->qdomain);
if(dk.getNSEC3PARAM(q->qdomain, &ns3pr, &narrow)) {
if(narrow) {
d_answers.clear();
return false;
}
+
+ rr.dr.d_place=DNSResourceRecord::ANSWER;
+
d_ancount++;
d_answers.push_back(rr);
return true;
if (needWildcardProof) {
/* We now need to look for a NSEC3 covering the closest (provable) encloser
RFC 5155 section-7.2.1
- FRC 7129 section-5.5
+ RFC 7129 section-5.5
*/
LOG("Now looking for the closest encloser for "<<qname<<endl);
if (found == true) {
/* now that we have found the closest (provable) encloser,
- we can construct the next closer (FRC7129 section-5.5) name
+ we can construct the next closer (RFC7129 section-5.5) name
and look for a NSEC3 RR covering it */
unsigned int labelIdx = qname.countLabels() - closestEncloser.countLabels();
if (labelIdx >= 1) {
LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName());
nextCloserFound = true;
- if (qtype == QType::DS && nsec3->d_flags & 1) {
+ if ((qtype == QType::DS || qtype == 0) && nsec3->d_flags & 1) {
LOG(" but is opt-out!");
isOptOut = true;
}
throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
// clear caches
- DNSSECKeeper dk(&B);
- dk.clearCaches(zonename);
+ DNSSECKeeper::clearCaches(zonename);
purgeAuthCaches(zonename.toString() + "$");
// empty body on success
_healthCheckName = 'a.root-servers.net.'
_healthCheckCounter = 0
_answerUnexpected = True
+ _checkConfigExpectedOutput = None
@classmethod
def startResponders(cls):
output = subprocess.check_output(testcmd, stderr=subprocess.STDOUT, close_fds=True)
except subprocess.CalledProcessError as exc:
raise AssertionError('dnsdist --check-config failed (%d): %s' % (exc.returncode, exc.output))
- expectedOutput = ('Configuration \'%s\' OK!\n' % (confFile)).encode()
+ if cls._checkConfigExpectedOutput is not None:
+ expectedOutput = cls._checkConfigExpectedOutput
+ else:
+ expectedOutput = ('Configuration \'%s\' OK!\n' % (confFile)).encode()
if output != expectedOutput:
raise AssertionError('dnsdist --check-config failed: %s' % output)
"""
name = 'anytruncatetcp.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'ANY', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
"""
name = 'andnot.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'TXT', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.NOTIMP)
"""
name = 'aorudp.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'AAAA', 'IN')
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
3600,
"""
name = 'aorudp.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.NOTIMP)
"""
name = 'qpsnone.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'nmgrule.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'dstportrule.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# more than 6 labels, the query should be refused
name = 'not.ok.labelscount.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# less than 5 labels, the query should be refused
name = 'labelscountadvanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# too short, the query should be refused
name = 'short.qnamewirelength.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# too long, the query should be refused
name = 'toolongtobevalid.qnamewirelength.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# this one should be refused
name = 'notincludedir.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'tc.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
3600,
query = dns.message.make_query(name, 'A', 'IN')
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
+ expectedResponse.flags |= dns.flags.RA
for method in ("sendUDPQuery", "sendTCPQuery"):
sender = getattr(self, method)
addAction(EDNSVersionRule(0), ERCodeAction(DNSRCode.BADVERS))
"""
- def testDropped(self):
+ def testBadVers(self):
"""
- Advanced: A question with ECS version larger than 0 is dropped
+ Advanced: A question with ECS version larger than 0 yields BADVERS
"""
name = 'ednsversionrule.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=1)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.BADVERS)
"""
name = 'any.tests.powerdns.com.'
query = dns.message.make_query(name, 'ANY', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.flags |= dns.flags.TC
"""
name = 'evil4242.regex.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'nameAndQtype.tests.powerdns.com.'
query = dns.message.make_query(name, 'TXT', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.NOTIMP)
"""
name = 'dnsname.addaction.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
for name in ['dnsname-table{}.addaction.powerdns.com.'.format(i) for i in range(1,2)]:
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'refused.doh.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
query.id = 0
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
addDOHLocal("127.0.0.1:%s")
"""
_config_params = ['_testServerPort', '_dohServerPort']
+ _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:8480, running in DNS over HTTP mode instead of DNS over HTTPS
+Configuration 'configs/dnsdist_TestDOHOverHTTP.conf' OK!
+"""
def testDOHSimple(self):
"""
self.assertEquals(query, receivedQuery)
self.assertEquals(response, receivedResponse)
+ def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount):
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(rcode)
+
+ # start with normal responses
+ for _ in range(noerrorcount-1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ # wait for the maintenance function to run
+ time.sleep(2)
+
+ # we should NOT be dropped!
+ (_, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertEquals(receivedResponse, response)
+
+ # now with rcode!
+ sent = 0
+ allowed = 0
+ for _ in range(rcodecount):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(expectedResponse, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
+ self.assertGreaterEqual(allowed, rcodecount)
+
+ # wait for the maintenance function to run
+ time.sleep(2)
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ # again, over TCP this time
+ # start with normal responses
+ for _ in range(noerrorcount-1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ # wait for the maintenance function to run
+ time.sleep(2)
+
+ # we should NOT be dropped!
+ (_, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertEquals(receivedResponse, response)
+
+ # now with rcode!
+ sent = 0
+ allowed = 0
+ for _ in range(rcodecount):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(expectedResponse, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
+ self.assertGreaterEqual(allowed, rcodecount)
+
+ # wait for the maintenance function to run
+ time.sleep(2)
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
class TestDynBlockQPS(DynBlocksTest):
_dynBlockQPS = 10
"""
name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
60,
name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
+class TestDynBlockGroupServFailsRatio(DynBlocksTest):
+
+ _dynBlockPeriod = 2
+ _dynBlockDuration = 5
+ _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksServFailRatio(self):
+ """
+ Dyn Blocks (group): Server Failure Ratio
+ """
+ name = 'servfailratio.group.dynblocks.tests.powerdns.com.'
+ self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10)
+
class TestDynBlockResponseBytes(DynBlocksTest):
_dynBlockBytesPerSecond = 200
"""
name = 'no-edns.rcode.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'no-edns.tc.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.flags |= dns.flags.TC
"""
name = 'edns-no-do.rcode.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'edns-no-do.tc.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.flags |= dns.flags.TC
"""
name = 'edns-do.rcode.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'edns-do.tc.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.flags |= dns.flags.TC
name = 'edns-options.rcode.edns-self.tests.powerdns.com.'
ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'edns-options.tc.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.flags |= dns.flags.TC
"""
name = 'edns-no-do.rcode.edns-self-disabled.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'edns-no-do.tc.edns-self-disabled.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.flags |= dns.flags.TC
"""
name = 'refuseemptyar.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'refusetwoar.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ query.flags &= ~dns.flags.RD
query.additional.append(dns.rrset.from_text(name,
3600,
dns.rdataclass.IN,
"""
name = 'refusenoan.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'refusefouran.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ query.flags &= ~dns.flags.RD
rrset = dns.rrset.from_text_list(name,
3600,
dns.rdataclass.IN,
dns.rdatatype.NS,
'ns.tests.powerdns.com.')
query.authority.append(rrset)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
expectedResponse.authority.append(rrset)
"""
name = 'refuseoptinar.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
s1 = newServer{address="127.0.0.1:%s", weight=-1}
s2 = newServer{address="127.0.0.1:%s", weight=2147483648}
"""
+ _checkConfigExpectedOutput = b"""Error creating new server: downstream weight value must be greater than 0.
+Error creating new server: downstream weight value must be between 1 and 2147483647
+Configuration 'configs/dnsdist_TestRoutingBadWeightWRandom.conf' OK!
+"""
def testBadWeightWRandom(self):
"""
_config_template = """
addAction(makeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1"))
+ addAction(makeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {aa=true}))
+ addAction(makeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ad=true}))
+ addAction(makeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=true}))
+ addAction(makeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=false}))
addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
newServer{address="127.0.0.1:%s"}
self.assertTrue(receivedResponse)
self.assertEquals(expectedResponse, receivedResponse)
+ def testSpoofActionSetAA(self):
+ """
+ Spoofing: Spoof via Action, setting AA=1
+ """
+ name = 'spoofaction-aa.spoofing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.AA
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
+ def testSpoofActionSetAD(self):
+ """
+ Spoofing: Spoof via Action, setting AD=1
+ """
+ name = 'spoofaction-ad.spoofing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.AD
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
+ def testSpoofActionSetRA(self):
+ """
+ Spoofing: Spoof via Action, setting RA=1
+ """
+ name = 'spoofaction-ra.spoofing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.RA
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
+ def testSpoofActionSetNoRA(self):
+ """
+ Spoofing: Spoof via Action, setting RA=0
+ """
+ name = 'spoofaction-nora.spoofing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags &= ~dns.flags.RA
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
class TestSpoofingLuaSpoof(DNSDistTest):
_config_template = """
"""
name = 'refused.tcpka.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# check non-local policies, they should be overridden by the default policy
self.checkNXD('tc.example.', 'A')
self.checkNotBlocked('drop.example.')
+
+class RPZOrderingPrecedenceRecursorTesT(RPZRecursorTest):
+ """
+ This test makes sure that the recursor respects the RPZ ordering precedence rules
+ """
+
+ _confdir = 'RPZOrderingPrecedence'
+ _wsPort = 8042
+ _wsTimeout = 2
+ _wsPassword = 'secretpassword'
+ _apiKey = 'secretapikey'
+ _lua_config_file = """
+ rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
+ rpzFile('configs/%s/zone2.rpz', { policyName="zone2.rpz."})
+ """ % (_confdir, _confdir)
+ _config_template = """
+auth-zones=example=configs/%s/example.zone
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+api-key=%s
+""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ authzonepath = os.path.join(confdir, 'example.zone')
+ with open(authzonepath, 'w') as authzone:
+ authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+sub.test 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+
+ rpzFilePath = os.path.join(confdir, 'zone.rpz')
+ with open(rpzFilePath, 'w') as rpzZone:
+ rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+*.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
+""".format(soa=cls._SOA))
+
+ rpzFilePath = os.path.join(confdir, 'zone2.rpz')
+ with open(rpzFilePath, 'w') as rpzZone:
+ rpzZone.write("""$ORIGIN zone2.rpz.
+@ 3600 IN SOA {soa}
+sub.test.example.com.zone2.rpz. 60 IN CNAME .
+32.42.2.0.192.rpz-ip 60 IN CNAME .
+""".format(soa=cls._SOA))
+
+ super(RPZOrderingPrecedenceRecursorTesT, cls).generateRecursorConfig(confdir)
+
+ def testRPZ(self):
+ # we should first match on the qname (the wildcard, not on the exact name since
+ # we respect the order of the RPZ zones), see the pass-thru rule
+ # and stop RPZ processing. The subsequent rule on the content of the A
+ # should therefore not trigger a NXDOMAIN.
+ self.checkNotBlocked('sub.test.example.')