jobs:
checkout:
+ resource_class: small
+
docker:
- image: debian:buster
- pdns-auth
test-auth-regress-odbc-sqlite3:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
skip: 8bit-txt-unescaped
test-auth-regress-gsqlite3:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
context: gsqlite3-nsec3-narrow
test-auth-regress-bind:
+ resource_class: small
+
docker:
- image: debian:buster
- image: circleci/mysql:5 # for the hybrid test
context: bind-hybrid-nsec3
test-auth-regress-gmysql:
+ resource_class: small
+
docker:
- image: debian:buster
- image: circleci/mysql:5
context: gmysql-nsec3-narrow
test-auth-regress-gpgsql:
+ resource_class: small
+
docker:
- image: debian:buster
- image: circleci/postgres:9
context: gpgsql-nsec3-narrow
test-auth-regress-ldap:
+ resource_class: small
+
docker:
- image: debian:buster
environment:
doroot: false
test-auth-regress-tinydns:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
doroot: false
test-auth-regress-lmdb:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
context: lmdb-nsec3-narrow
test-auth-algorithms:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
command: /opt/pdns-auth/bin/pdnsutil test-algorithms
test-auth-api:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
- pdns-recursor
test-recursor-regression:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
./build-scripts/test-recursor
test-recursor-bulk:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
workdir: ~/project/regression-tests
test-recursor-api:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
./runtests recursor
build-auth-docs:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
- build-auth-docs
deploy-auth-docs:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
- upload-auth-docs
build-recursor-docs:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
- build-recursor-docs
deploy-recursor-docs:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
- upload-recursor-docs
build-dnsdist-docs:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
- build-dnsdist-docs
deploy-dnsdist-docs:
+ resource_class: small
+
docker:
- image: debian:buster
steps:
- dnsdist
test-dnsdist-regression:
+ resource_class: small
+
docker:
- image: debian:buster
environment:
apt-get -qq --no-install-recommends install snmpd
sed "s/agentxperms 0700 0755 dnsdist/agentxperms 0700 0755/g" regression-tests.dnsdist/snmpd.conf > /etc/snmp/snmpd.conf
/etc/init.d/snmpd start
+ - run:
+ name: install prometheus tools
+ command: |
+ apt-get -qq --no-install-recommends install prometheus
- run:
name: Run dnsdist tests
workdir: ~/project/regression-tests.dnsdist
./runtests
test-ixfrdist-regression:
+ resource_class: small
+
docker:
- image: debian:buster
environment:
test_dnsdist(){
run "cd regression-tests.dnsdist"
- run "DNSDISTBIN=$HOME/dnsdist/bin/dnsdist ./runtests -v --ignore-files='(?:^\.|^_,|^setup\.py$|^test_DOH\.py$|^test_OCSP\.py$|^test_TLSSessionResumption\.py$)'"
+ run "DNSDISTBIN=$HOME/dnsdist/bin/dnsdist ./runtests -v --ignore-files='(?:^\.|^_,|^setup\.py$|^test_DOH\.py$|^test_OCSP\.py$|^test_Prometheus\.py$|^test_TLSSessionResumption\.py$)'"
run "rm -f ./DNSCryptResolver.cert ./DNSCryptResolver.key"
run "cd .."
}
-@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019093001 10800 3600 604800 10800
+@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019103001 10800 3600 604800 10800
@ 3600 IN NS pdns-public-ns1.powerdns.com.
@ 3600 IN NS pdns-public-ns2.powerdns.com.
recursor-4.2.0-rc2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
recursor-4.2.0.security-status 60 IN TXT "1 OK"
recursor-4.3.0-alpha1.security-status 60 IN TXT "1 OK"
+recursor-4.3.0-alpha2.security-status 60 IN TXT "1 OK"
+recursor-4.3.0-alpha3.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/"
dnsdist-1.4.0-rc1.security-status 60 IN TXT "1 OK"
dnsdist-1.4.0-rc2.security-status 60 IN TXT "1 OK"
dnsdist-1.4.0-rc3.security-status 60 IN TXT "1 OK"
+dnsdist-1.4.0-rc4.security-status 60 IN TXT "1 OK"
+dnsdist-1.4.0-rc5.security-status 60 IN TXT "1 OK"
static vector<std::unique_ptr<GeoIPInterface> > s_geoip_files;
string getGeoForLua(const std::string& ip, int qaint);
-static string queryGeoIP(const string &ip, bool v6, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl);
+static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl);
void GeoIPBackend::initialize() {
YAML::Node config;
}
}
-bool GeoIPBackend::lookup_static(const GeoIPDomain &dom, const DNSName &search, const QType &qtype, const DNSName& qdomain, const std::string &ip, GeoIPNetmask &gl, bool v6) {
+bool GeoIPBackend::lookup_static(const GeoIPDomain &dom, const DNSName &search, const QType &qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask &gl) {
const auto& i = dom.records.find(search);
map<uint16_t,int> cumul_probabilities;
int probability_rnd = 1+(dns_random(1000)); // setting probability=0 means it never is used
if (qtype != QType::ANY && rr.qtype != qtype) continue;
if (rr.has_weight) {
- gl.netmask = (v6?128:32);
+ gl.netmask = (addr.isIpv6()?128:32);
int comp = cumul_probabilities[rr.qtype.getCode()];
cumul_probabilities[rr.qtype.getCode()] += rr.weight;
if (rr.weight == 0 || probability_rnd < comp || probability_rnd > (comp + rr.weight))
continue;
}
- const string& content = format2str(rr.content, ip, v6, gl);
+ const string& content = format2str(rr.content, addr, gl);
if (rr.qtype != QType::ENT && rr.qtype != QType::TXT && content.empty()) continue;
d_result.push_back(rr);
d_result.back().content = content;
if (!found) return; // not found
}
- string ip = "0.0.0.0";
- bool v6 = false;
- if (pkt_p != NULL) {
- ip = pkt_p->getRealRemote().toStringNoMask();
- v6 = pkt_p->getRealRemote().isIpv6();
- }
+ Netmask addr{"0.0.0.0/0"};
+ if (pkt_p != NULL)
+ addr = Netmask(pkt_p->getRealRemote());
gl.netmask = 0;
- (void)this->lookup_static(*dom, qdomain, qtype, qdomain, ip, gl, v6);
+ (void)this->lookup_static(*dom, qdomain, qtype, qdomain, addr, gl);
const auto& target = (*dom).services.find(qdomain);
if (target == (*dom).services.end()) return; // no hit
- const NetmaskTree<vector<string> >::node_type* node = target->second.masks.lookup(ComboAddress(ip));
+ const NetmaskTree<vector<string> >::node_type* node = target->second.masks.lookup(addr);
if (node == NULL) return; // no hit, again.
DNSName sformat;
GeoIPNetmask tmp_gl;
tmp_gl.netmask = 0;
// get netmask from geoip backend
- if (queryGeoIP(ip, v6, GeoIPInterface::Name, tmp_gl) == "unknown") {
- if (v6)
+ if (queryGeoIP(addr, GeoIPInterface::Name, tmp_gl) == "unknown") {
+ if (addr.isIpv6())
gl.netmask = target->second.netmask6;
else
gl.netmask = target->second.netmask4;
}
} else {
- if (v6)
+ if (addr.isIpv6())
gl.netmask = target->second.netmask6;
else
gl.netmask = target->second.netmask4;
// note that this means the array format won't work with indirect
for(auto it = node->second.begin(); it != node->second.end(); it++) {
- sformat = DNSName(format2str(*it, ip, v6, gl));
+ sformat = DNSName(format2str(*it, addr, gl));
// see if the record can be found
- if (this->lookup_static((*dom), sformat, qtype, qdomain, ip, gl, v6))
+ if (this->lookup_static((*dom), sformat, qtype, qdomain, addr, gl))
return;
}
return true;
}
-static string queryGeoIP(const string &ip, bool v6, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl) {
+static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl) {
string ret = "unknown";
for(auto const& gi: s_geoip_files) {
string val;
+ const string ip = addr.toStringNoMask();
bool found = false;
switch(attribute) {
case GeoIPInterface::ASn:
- if (v6) found = gi->queryASnumV6(val, gl, ip);
+ if (addr.isIpv6()) found = gi->queryASnumV6(val, gl, ip);
else found =gi->queryASnum(val, gl, ip);
break;
case GeoIPInterface::Name:
- if (v6) found = gi->queryNameV6(val, gl, ip);
+ if (addr.isIpv6()) found = gi->queryNameV6(val, gl, ip);
else found = gi->queryName(val, gl, ip);
break;
case GeoIPInterface::Continent:
- if (v6) found = gi->queryContinentV6(val, gl, ip);
+ if (addr.isIpv6()) found = gi->queryContinentV6(val, gl, ip);
else found = gi->queryContinent(val, gl, ip);
break;
case GeoIPInterface::Region:
- if (v6) found = gi->queryRegionV6(val, gl, ip);
+ if (addr.isIpv6()) found = gi->queryRegionV6(val, gl, ip);
else found = gi->queryRegion(val, gl, ip);
break;
case GeoIPInterface::Country:
- if (v6) found = gi->queryCountryV6(val, gl, ip);
+ if (addr.isIpv6()) found = gi->queryCountryV6(val, gl, ip);
else found = gi->queryCountry(val, gl, ip);
break;
case GeoIPInterface::Country2:
- if (v6) found = gi->queryCountry2V6(val, gl, ip);
+ if (addr.isIpv6()) found = gi->queryCountry2V6(val, gl, ip);
else found = gi->queryCountry2(val, gl, ip);
break;
case GeoIPInterface::City:
- if (v6) found = gi->queryCityV6(val, gl, ip);
+ if (addr.isIpv6()) found = gi->queryCityV6(val, gl, ip);
else found = gi->queryCity(val, gl, ip);
break;
case GeoIPInterface::Location:
double lat=0, lon=0;
boost::optional<int> alt, prec;
- if (v6) found = gi->queryLocationV6(gl, ip, lat, lon, alt, prec);
+ if (addr.isIpv6()) found = gi->queryLocationV6(gl, ip, lat, lon, alt, prec);
else found = gi->queryLocation(gl, ip, lat, lon, alt, prec);
val = std::to_string(lat)+" "+std::to_string(lon);
break;
break;
}
- if (ret == "unknown") gl.netmask = (v6?128:32); // prevent caching
+ if (ret == "unknown") gl.netmask = (addr.isIpv6()?128:32); // prevent caching
return ret;
}
{
GeoIPInterface::GeoIPQueryAttribute qa((GeoIPInterface::GeoIPQueryAttribute)qaint);
try {
+ const Netmask addr{ip};
GeoIPNetmask gl;
- string res=queryGeoIP(ip, false, qa, gl);
+ string res=queryGeoIP(addr, qa, gl);
// cout<<"Result for "<<ip<<" lookup: "<<res<<endl;
if(qa==GeoIPInterface::ASn && boost::starts_with(res, "as"))
return res.substr(2);
return "";
}
-bool queryGeoLocation(const string &ip, bool v6, GeoIPNetmask& gl, double& lat, double& lon,
+bool queryGeoLocation(const Netmask& addr, GeoIPNetmask& gl, double& lat, double& lon,
boost::optional<int>& alt, boost::optional<int>& prec)
{
for(auto const& gi: s_geoip_files) {
string val;
- if (v6) {
- if (gi->queryLocationV6(gl, ip, lat, lon, alt, prec))
+ if (addr.isIpv6()) {
+ if (gi->queryLocationV6(gl, addr.toStringNoMask(), lat, lon, alt, prec))
return true;
- } else if (gi->queryLocation(gl, ip, lat, lon, alt, prec))
+ } else if (gi->queryLocation(gl, addr.toStringNoMask(), lat, lon, alt, prec))
return true;
}
return false;
}
-string GeoIPBackend::format2str(string sformat, const string& ip, bool v6, GeoIPNetmask& gl) {
+string GeoIPBackend::format2str(string sformat, const Netmask& addr, GeoIPNetmask& gl) {
string::size_type cur,last;
boost::optional<int> alt, prec;
double lat, lon;
int nrep=3;
tmp_gl.netmask = 0;
if (!sformat.compare(cur,3,"%cn")) {
- rep = queryGeoIP(ip, v6, GeoIPInterface::Continent, tmp_gl);
+ rep = queryGeoIP(addr, GeoIPInterface::Continent, tmp_gl);
} else if (!sformat.compare(cur,3,"%co")) {
- rep = queryGeoIP(ip, v6, GeoIPInterface::Country, tmp_gl);
+ rep = queryGeoIP(addr, GeoIPInterface::Country, tmp_gl);
} else if (!sformat.compare(cur,3,"%cc")) {
- rep = queryGeoIP(ip, v6, GeoIPInterface::Country2, tmp_gl);
+ rep = queryGeoIP(addr, GeoIPInterface::Country2, tmp_gl);
} else if (!sformat.compare(cur,3,"%af")) {
- rep = (v6?"v6":"v4");
+ rep = (addr.isIpv6()?"v6":"v4");
} else if (!sformat.compare(cur,3,"%as")) {
- rep = queryGeoIP(ip, v6, GeoIPInterface::ASn, tmp_gl);
+ rep = queryGeoIP(addr, GeoIPInterface::ASn, tmp_gl);
} else if (!sformat.compare(cur,3,"%re")) {
- rep = queryGeoIP(ip, v6, GeoIPInterface::Region, tmp_gl);
+ rep = queryGeoIP(addr, GeoIPInterface::Region, tmp_gl);
} else if (!sformat.compare(cur,3,"%na")) {
- rep = queryGeoIP(ip, v6, GeoIPInterface::Name, tmp_gl);
+ rep = queryGeoIP(addr, GeoIPInterface::Name, tmp_gl);
} else if (!sformat.compare(cur,3,"%ci")) {
- rep = queryGeoIP(ip, v6, GeoIPInterface::City, tmp_gl);
+ rep = queryGeoIP(addr, GeoIPInterface::City, tmp_gl);
} else if (!sformat.compare(cur,4,"%loc")) {
char ns, ew;
int d1, d2, m1, m2;
double s1, s2;
- if (!queryGeoLocation(ip, v6, gl, lat, lon, alt, prec)) {
+ if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
rep = "";
} else {
ns = (lat>0) ? 'N' : 'S';
}
nrep = 4;
} else if (!sformat.compare(cur,4,"%lat")) {
- if (!queryGeoLocation(ip, v6, gl, lat, lon, alt, prec)) {
+ if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
rep = "";
} else {
rep = str(boost::format("%lf") % lat);
}
nrep = 4;
} else if (!sformat.compare(cur,4,"%lon")) {
- if (!queryGeoLocation(ip, v6, gl, lat, lon, alt, prec)) {
+ if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
rep = "";
} else {
rep = str(boost::format("%lf") % lon);
nrep = 4;
} else if (!sformat.compare(cur,3,"%hh")) {
rep = boost::str(boost::format("%02d") % gtm.tm_hour);
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,3,"%yy")) {
rep = boost::str(boost::format("%02d") % (gtm.tm_year + 1900));
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,3,"%dd")) {
rep = boost::str(boost::format("%02d") % (gtm.tm_yday + 1));
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,4,"%wds")) {
nrep=4;
rep = GeoIP_WEEKDAYS[gtm.tm_wday];
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,4,"%mos")) {
nrep=4;
rep = GeoIP_MONTHS[gtm.tm_mon];
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,3,"%wd")) {
rep = boost::str(boost::format("%02d") % (gtm.tm_wday + 1));
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,3,"%mo")) {
rep = boost::str(boost::format("%02d") % (gtm.tm_mon + 1));
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,4,"%ip6")) {
nrep = 4;
- if (v6)
- rep = ip;
+ if (addr.isIpv6())
+ rep = addr.toStringNoMask();
else
rep = "";
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,4,"%ip4")) {
nrep = 4;
- if (!v6)
- rep = ip;
+ if (!addr.isIpv6())
+ rep = addr.toStringNoMask();
else
rep = "";
- tmp_gl.netmask = (v6?128:32);
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,3,"%ip")) {
- rep = ip;
- tmp_gl.netmask = (v6?128:32);
+ rep = addr.toStringNoMask();
+ tmp_gl.netmask = (addr.isIpv6()?128:32);
} else if (!sformat.compare(cur,2,"%%")) {
last = cur + 2; continue;
} else {
static pthread_rwlock_t s_state_lock;
void initialize();
- string format2str(string format, const string& ip, bool v6, GeoIPNetmask& gl);
+ string format2str(string format, const Netmask &addr, GeoIPNetmask& gl);
bool d_dnssec;
bool hasDNSSECkey(const DNSName& name);
- bool lookup_static(const GeoIPDomain &dom, const DNSName &search, const QType &qtype, const DNSName& qdomain, const std::string &ip, GeoIPNetmask& gl, bool v6);
+ bool lookup_static(const GeoIPDomain &dom, const DNSName &search, const QType &qtype, const DNSName& qdomain, const Netmask &addr, GeoIPNetmask& gl);
vector<DNSResourceRecord> d_result;
vector<GeoIPInterface> d_files;
};
threadNumber = dupPair.first->second;
++(dupPair.first->second);
}
- const std::string addrlabel = boost::str(boost::format("address=\"%1%\",thread=\"%2%\"") % frontName % threadNumber);
+ const std::string addrlabel = boost::str(boost::format("frontend=\"%1%\",thread=\"%2%\"") % frontName % threadNumber);
const std::string label = "{" + addrlabel + "} ";
output << frontsbase << "http_connects" << label << doh->d_httpconnects << "\n";
du->response = std::string(response, responseLen);
if (send(du->rsock, &du, sizeof(du), 0) != sizeof(du)) {
/* at this point we have the only remaining pointer on this
- DOHUnit object since we did set ids->du to nullptr earlier */
- delete du;
+ DOHUnit object since we did set ids->du to nullptr earlier,
+ except if we got the response before the pointer could be
+ released by the frontend */
+ du->release();
}
#endif /* HAVE_DNS_OVER_HTTPS */
du = nullptr;
Changelog
=========
+.. changelog::
+ :version: 1.4.0-rc5
+ :released: 30th of October 2019
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS, Metrics
+ :pullreq: 8465
+
+ Rename the 'address' label to 'frontend' for DoH metrics
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTPS
+ :pullreq: 8471
+
+ Increment the DOHUnit ref count when it's set in the IDState
+
+.. changelog::
+ :version: 1.4.0-rc4
+ :released: 25th of October 2019
+
+ .. change::
+ :tags: New Features, DNS over HTTPS, DNS over TLS
+ :pullreq: 8442
+
+ Add support dumping TLS keys via keyLogFile
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS
+ :pullreq: 8416
+
+ Implement reference counting for the DOHUnit object
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS, DNS over TLS, Metrics
+ :pullreq: 8447
+
+ Add metrics about TLS handshake failures for DoH and DoT
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8411
+ :tickets: 8390
+
+ Add more options to LogAction (non-verbose mode, timestamps)
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS, DNS over TLS
+ :pullreq: 8383
+
+ Merge the setup of TLS contexts in DoH and DoT
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8408
+
+ Fix the caching of large entries
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8415
+
+ Fix formatting in showTCPStats()
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8413
+ :tickets: 8412
+
+ Work around cmsg_space somehow not being a constexpr on macOS
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8372
+
+ Use SO_BINDTODEVICE when available for newServer's source interface
+
+ .. change::
+ :tags: Bug Fixes, Metrics
+ :pullreq: 8409
+
+ Add missing prometheus descriptions for cache-related metrics
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS, DNS over TLS, Metrics
+ :pullreq: 8406
+
+ Add metrics about unknown/inactive TLS ticket keys
+
+ .. change::
+ :tags: Improvements, DNS over TLS, Metrics
+ :pullreq: 8387
+
+ Add metrics about TLS versions with DNS over TLS
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS, Metrics
+ :pullreq: 8395
+
+ Count the number of concurrent connections for DoH as well
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTPS
+ :pullreq: 8388
+
+ Clear the DoH session ticket encryption key in the ctor
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS, DNS over TLS
+ :pullreq: 8382
+
+ Add a 'preferServerCiphers' option for DoH and DoT
+
+ .. change::
+ :tags: Bug Fixes, Metrics
+ :pullreq: 8381
+
+ Add a prometheus 'thread' label to distinguish identical frontends
+
+ .. change::
+ :tags: Bug Fixes, Metrics
+ :pullreq: 8378
+
+ Fix a typo in the prometheus description of 'senderrors'
+
+ .. change::
+ :tags: Bug Fixes, Metrics
+ :pullreq: 8368
+
+ More prometheus fixes
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS
+ :pullreq: 8365
+ :tickets: 8353
+
+ Lowercase custom DoH header names
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8364
+ :tickets: 8362
+
+ Check the address supplied to 'webserver' in check-config
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS, Metrics
+ :pullreq: 8361
+
+ Refactor DoH prometheus metrics again
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8359
+
+ Fix the creation order of rules when inserted via setRules()
+
.. changelog::
:version: 1.4.0-rc3
:released: 30th of September 2019
changelog_render_changeset = "https://github.com/PowerDNS/pdns/commit/%s"
changelog_sections = ['New Features', 'Improvements', 'Bug Fixes', 'Removals']
-changelog_inner_tag_sort = ['Security', 'DNS over HTTPS', 'DNS over TLS', 'DNSCrypt', 'Protobuf', 'Performance', 'Webserver']
+changelog_inner_tag_sort = ['Security', 'DNS over HTTPS', 'DNS over TLS', 'DNSCrypt', 'Protobuf', 'Performance', 'Webserver', 'Metrics']
changelog_render_tags = False
void release()
{
- --d_refcnt;
- if (d_refcnt == 0) {
+ if (--d_refcnt == 0) {
SSL_CTX_free(d_h2o_accept_ctx.ssl_ctx);
d_h2o_accept_ctx.ssl_ctx = nullptr;
delete this;
this function calls 'return -1' to drop a query without sending it
caller should make sure HTTPS thread hears of that
*/
-
static int processDOHQuery(DOHUnit* du)
{
uint16_t queryId = 0;
ComboAddress remote;
+ bool duRefCountIncremented = false;
try {
if(!du->req) {
// we got closed meanwhile. XXX small race condition here
}
ids->origFD = 0;
+ /* increase the ref count since we are about to store the pointer */
+ du->get();
+ duRefCountIncremented = true;
ids->du = du;
ids->cs = &cs;
int fd = pickBackendSocketForSending(ss);
try {
- /* increase the ref count since we are about to send the pointer */
- du->get();
/* you can't touch du after this line, because it might already have been freed */
ssize_t ret = udpClientSendRequestToBackend(ss, fd, query, dq.len);
if(ret < 0) {
- du->release();
/* we are about to handle the error, make sure that
this pointer is not accessed when the state is cleaned,
but first check that it still belongs to us */
if (ids->tryMarkUnused(generation)) {
ids->du = nullptr;
+ du->release();
+ duRefCountIncremented = false;
--ss->outstanding;
}
++ss->sendErrors;
}
}
catch (const std::exception& e) {
- du->release();
+ if (duRefCountIncremented) {
+ du->release();
+ }
throw;
}
return 0;
}
+/* called when a HTTP response is about to be sent */
static void on_response_ready_cb(struct st_h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot)
{
if (req == nullptr) {
}
}
+/* We allocate a DOHUnit and send it to dnsdistclient() function in the doh client thread
+ via a pipe */
static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, std::string&& query, const ComboAddress& local, const ComboAddress& remote)
{
try {
}
/*
- For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not.
- For POST, the payload is the payload.
+ A query has been parsed by h2o.
+ For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not.
+ For POST, the payload is the payload.
*/
static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
try
contentType = contentType_;
}
-void dnsdistclient(int qsock, int rsock)
+/* query has been parsed by h2o, which called doh_handler() in the main DoH thread.
+ In order not to blockfor long, doh_handler() called doh_dispatch_query() which allocated
+ a DOHUnit object and passed it to us */
+static void dnsdistclient(int qsock, int rsock)
{
setThreadName("dnsdist/doh-cli");
}
}
-// called if h2o finds that dnsdist gave us an answer
+/* called if h2o finds that dnsdist gave us an answer by writing into
+ the dohresponsepair[0] side of the pipe so from:
+ - handleDOHTimeout() when we did not get a response fast enough (called
+ either from the health check thread (active) or from the frontend ones (reused))
+ - dnsdistclient (error 500 because processDOHQuery() returned a negative value)
+ - processDOHQuery (self-answered queries)
+ */
static void on_dnsdist(h2o_socket_t *listener, const char *err)
{
DOHUnit *du = nullptr;
du->release();
}
+/* called when a TCP connection has been accepted, the TLS session has not been established */
static void on_accept(h2o_socket_t *listener, const char *err)
{
DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
void release()
{
- --d_refcnt;
- if (d_refcnt == 0) {
+ if (--d_refcnt == 0) {
delete this;
}
}
if(now.tv_sec - last_rootupdate > 7200) {
int res = SyncRes::getRootNS(g_now, nullptr);
- if (!res)
+ if (!res) {
last_rootupdate=now.tv_sec;
+ primeRootNSZones(g_dnssecmode != DNSSECMode::Off);
+ }
}
if(isHandlerThread()) {
CLEANFILES = htmlfiles.h \
dnsmessage.pb.cc \
dnsmessage.pb.h \
+ dnstap.pb.cc \
+ dnstap.pb.h \
recursor.conf-dist
htmlfiles.h: html/*
Changelogs for 4.3.x
====================
+.. changelog::
+ :version: 4.3.0-alpha3
+ :released: 29th of October 2019
+
+ .. change::
+ :tags: Bug fixes
+ :pullreq: 8470
+
+ Prime NS records of root-servers.net parent (.net)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8463
+
+ Update CentOS 6 init script (None)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8451
+
+ Basic validation of $GENERATE parameters
+
+ .. change::
+ :tags: Bug fixes
+ :pullreq: 8433
+
+ Dns64: stop hiding PTR indirection
+
+ .. change::
+ :tags: New features
+ :pullreq: 8391
+ :tickets: 8358
+
+ Allow multiple simultaneous incoming TCP queries over a connection
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8344
+
+ Add signal handling for SIGTERM and SIGINT in pdns_recursor, if we are PID1 (Frank Louwers)
+
+ .. change::
+ :tags: New Features
+ :pullreq: 8367
+
+ Implement RFC 8020 "NXDOMAIN: There Really Is Nothing Underneath"
+
+ .. change::
+ :tags: New features
+ :pullreq: 8400
+
+ Add CentOS 8 as builder target
+
+ .. change::
+ :tags: Bug fixes
+ :pullreq: 8371
+
+ Fix chmod paths in rules files
+
+ .. change::
+ :tags: New features
+ :pullreq: 8366
+
+ Build Newly Observed Domain (NOD) support by default.
+
+ .. change::
+ :tags: Bug fixes
+ :pullreq: 8360
+ :tickets: 8352
+
+ Rec: chmod/own recursor.conf for the systemd case
+
+ .. change::
+ :tags: Bug fixes
+ :pullreq: 8340
+ :tickets: 8338
+
+ Fix #8338: Issue with "zz" abbreviation for IPv6 RPZ triggers
+
+ .. change::
+ :tags: Bug fixes
+ :pullreq: 8317
+
+ Retry getrandom() on EINTR
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8287
+
+ Docs: Add small description for pipe backend about distributor-threads (Donatas Abraitis)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8290
+
+ Improve commandline error reporting for non-opts
+
+ .. change::
+ :tags: New features
+ :pullreq: 7758
+
+ Recursor webhandler for prometheus metrics (Greg Cockroft)
+
+.. changelog::
+ :version: 4.3.0-alpha2
+ :released: Never released
+
.. changelog::
:version: 4.3.0-alpha1
:released: 5th of September 2019
:pullreq: 7967
:tickets: 7949
- Silence unused lambda warning (retry) (None)
+ Silence unused lambda warning (retry) (fwSmit)
.. change::
:tags: New Features
.. _setting max-concurrent-requests-per-tcp-connection:
``max-concurrent-requests-per-tcp-connection``
-------------------------------------------
+----------------------------------------------
- Integer
- Default: 10
#include "config.h"
#endif
#include <boost/test/unit_test.hpp>
-#include <boost/test/floating_point_comparison.hpp>
#include "mtasker.hh"
BOOST_AUTO_TEST_SUITE(mtasker_cc)
#include "config.h"
#endif
#include <boost/test/unit_test.hpp>
-#include <boost/test/floating_point_comparison.hpp>
#include "iputils.hh"
#include "recursor_cache.hh"
return 0;
}
+void primeRootNSZones(bool)
+{
+}
+
bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret) const
{
return false;
extern int g_argc;
extern char** g_argv;
+static thread_local set<DNSName> t_rootNSZones;
+
+static void insertIntoRootNSZones(const DNSName &name) {
+ // do not insert dot, wiping dot's NS records from the cache in primeRootNSZones()
+ // will cause infinite recursion
+ if (!name.isRoot()) {
+ t_rootNSZones.insert(name);
+ }
+}
+
void primeHints(void)
{
// prime root cache
const vState validationState = Insecure;
vector<DNSRecord> nsset;
+ t_rootNSZones.clear();
if(!t_RC)
t_RC = std::unique_ptr<MemRecursorCache>(new MemRecursorCache());
templ[sizeof(templ)-1] = '\0';
*templ=c;
aaaarr.d_name=arr.d_name=DNSName(templ);
+ insertIntoRootNSZones(arr.d_name.getLastLabel());
nsrr.d_content=std::make_shared<NSRecordContent>(DNSName(templ));
arr.d_content=std::make_shared<ARecordContent>(ComboAddress(rootIps4[c-'a']));
vector<DNSRecord> aset;
rr.content=toLower(rr.content);
nsset.push_back(DNSRecord(rr));
}
+ insertIntoRootNSZones(rr.qname.getLastLabel());
}
}
t_RC->doWipeCache(g_rootdnsname, false, QType::NS);
t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, boost::none, validationState); // and stuff in the cache
}
+
+// Do not only put the root hints into the cache, but also make sure
+// the NS records of the top level domains of the names of the root
+// servers are in the cache. We need these to correctly determine the
+// security status of that specific domain (normally
+// root-servers.net). This is caused by the accident that the root
+// servers are authoritative for root-servers.net, and some
+// implementations reply not with a delegation on a root-servers.net
+// DS query, but with a NODATA response (the domain is unsigned).
+void primeRootNSZones(bool dnssecmode)
+{
+ struct timeval now;
+ gettimeofday(&now, 0);
+ SyncRes sr(now);
+
+ if (dnssecmode) {
+ sr.setDoDNSSEC(true);
+ sr.setDNSSECValidationRequested(true);
+ }
+ for (const auto & qname: t_rootNSZones) {
+ t_RC->doWipeCache(qname, false, QType::NS);
+ vector<DNSRecord> ret;
+ sr.beginResolve(qname, QType(QType::NS), QClass::IN, ret);
+ }
+}
+
static void makeNameToIPZone(std::shared_ptr<SyncRes::domainmap_t> newMap, const DNSName& hostname, const string& ip)
{
SyncRes::AuthDomain ad;
}
return newMap;
}
-
if(subdomain.isRoot() && !brokeloop) {
// We lost the root NS records
primeHints();
+ primeRootNSZones(g_dnssecmode != DNSSECMode::Off);
LOG(prefix<<qname<<": reprimed the root"<<endl);
/* let's prevent an infinite loop */
if (!d_updatingRootNS) {
uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree=false);
void doCarbonDump(void*);
void primeHints(void);
+void primeRootNSZones(bool);
extern __thread struct timeval g_now;
#include "config.h"
#endif
#include <boost/test/unit_test.hpp>
-#include <boost/test/floating_point_comparison.hpp>
#include "iputils.hh"
#include "nameserver.hh"
#include "statbag.hh"
sscanf(range.c_str(),"%u-%u/%u", &d_templatecounter, &d_templatestop, &d_templatestep);
if (d_templatestep < 1 ||
d_templatestop < d_templatecounter) {
- throw exception("Illegal $GENERATE parameters");
+ throw exception("Invalid $GENERATE parameters");
}
d_templateline=d_line;
parts.pop_front();
fi
. .venv/bin/activate
python -V
-pip install -r requirements.txt
+pip install -r requirements.txt | cat
if [ -z "${SDIG}" ]; then
export SDIG=$(type -P sdig)
. .venv/bin/activate
python -V
-pip install -q -r requirements.txt
+pip install -q -r requirements.txt | cat
mkdir -p configs
export CPPFLAGS=-I/usr/local/opt/openssl/include
fi
fi
-pip install -r requirements.txt
+pip install -r requirements.txt | cat
protoc -I=../pdns/ --python_out=. ../pdns/dnsmessage.proto
protoc -I=../pdns/ --python_out=. ../pdns/dnstap.proto
--- /dev/null
+#!/usr/bin/env python
+import requests
+import subprocess
+from dnsdisttests import DNSDistTest
+
+class TestPrometheus(DNSDistTest):
+
+ _webTimeout = 2.0
+ _webServerPort = 8083
+ _webServerBasicAuthPassword = 'secret'
+ _webServerAPIKey = 'apisecret'
+ _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+ webserver("127.0.0.1:%s", "%s", "%s")
+ """
+
+ def checkPrometheusContentBasic(self, content):
+ for line in content.splitlines():
+ if line.startswith('# HELP'):
+ tokens = line.split(' ')
+ self.assertGreaterEqual(len(tokens), 4)
+ elif line.startswith('# TYPE'):
+ tokens = line.split(' ')
+ self.assertEquals(len(tokens), 4)
+ self.assertIn(tokens[3], ['counter', 'gauge', 'histogram'])
+ elif not line.startswith('#'):
+ tokens = line.split(' ')
+ self.assertEquals(len(tokens), 2)
+ if not line.startswith('dnsdist_'):
+ raise AssertionError('Expecting prometheus metric to be prefixed by \'dnsdist_\', got: "%s"' % (line))
+
+ def checkPrometheusContentPromtool(self, content):
+ output = None
+ try:
+ testcmd = ['promtool', 'check', 'metrics']
+ process = subprocess.Popen(testcmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
+ output = process.communicate(input=content)
+ except subprocess.CalledProcessError as exc:
+ raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, process.output))
+
+ # commented out because promtool returns 3 because of the "_total" suffix warnings
+ #if process.returncode != 0:
+ # raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, output))
+
+ for line in output[0].splitlines():
+ if line.endswith(b"should have \"_total\" suffix"):
+ continue
+ raise AssertionError('%s returned an unexpected output. Faulty line is "%s", complete content is "%s"' % (testcmd, line, output))
+
+ def testMetrics(self):
+ """
+ Prometheus: Retrieve metrics
+ """
+ url = 'http://127.0.0.1:' + str(self._webServerPort) + '/metrics'
+ r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
+ self.assertTrue(r)
+ self.assertEquals(r.status_code, 200)
+ self.checkPrometheusContentBasic(r.text)
+ self.checkPrometheusContentPromtool(r.content)
fi
. .venv/bin/activate
python -V
-pip install -r requirements.txt
+pip install -r requirements.txt | cat
if [ -z "${IXFRDISTBIN}" ]; then
IXFRDISTBIN=$(ls ../pdns/ixfrdist)
example. 3600 IN DS 53174 13 1 50c9e913818767c236c06c2d8272723cb78cbf26
ns1.example. 3600 IN A {prefix}.10
-ns2.example. 3600 IN A {prefix}.11
+ns2.example. 3600 IN A {prefix}.18
""",
'example': """
example. 3600 IN SOA {soa}
example. 3600 IN NS ns1.example.
example. 3600 IN NS ns2.example.
ns1.example. 3600 IN A {prefix}.10
-ns2.example. 3600 IN A {prefix}.11
+ns2.example. 3600 IN A {prefix}.18
secure.example. 3600 IN NS ns.secure.example.
secure.example. 3600 IN DS 64723 13 1 53eb985040d3a89bacf29dbddb55a65834706f33
delay1.example. 3600 IN NS ns1.delay1.example.
ns1.delay1.example. 3600 IN A {prefix}.16
+delay1.example. 3600 IN DS 42043 13 2 7319fa605cf117f36e3de070157577ebb9a05a1d1f963d80eda55b5d6e793eb2
+
delay2.example. 3600 IN NS ns1.delay2.example.
ns1.delay2.example. 3600 IN A {prefix}.17
+delay2.example. 3600 IN DS 42043 13 2 60a047b87740c8564c21d5fd34626c10a77a6c41e3b34564230119c2f13937b8
""",
'secure.example': """
secure.example. 3600 IN SOA {soa}
Private-key-format: v1.2
Algorithm: 13 (ECDSAP256SHA256)
PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
+""",
+
+ 'delay1.example': """
+Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
+""",
+
+ 'delay2.example': """
+Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
"""
}
'zones': ['secure.example', 'islandofsecurity.example']},
'10': {'threads': 1,
'zones': ['example']},
- '11': {'threads': 1,
- 'zones': ['example']},
+
+ # 11 is used by CircleCI provided resolver
+
'12': {'threads': 1,
'zones': ['bogus.example', 'undelegated.secure.example', 'undelegated.insecure.example']},
'13': {'threads': 1,
'16': {'threads': 2,
'zones': ['delay1.example']},
'17': {'threads': 2,
- 'zones': ['delay2.example']}
+ 'zones': ['delay2.example']},
+ '18': {'threads': 1,
+ 'zones': ['example']}
}
_auth_cmd = ['authbind',
fi
. .venv/bin/activate
python -V
-pip install -U pip
-pip install -r requirements.txt
+pip install -U pip | cat
+pip install -r requirements.txt | cat
protoc -I=../pdns/ --python_out=. ../pdns/dnsmessage.proto
protoc -I=../pdns/ --python_out=. ../pdns/dnstap.proto
class testOOOTCP(RecursorTest):
_confdir = 'OOOTCP'
- _config_template = """dnssec=off
+ _config_template = """dnssec=validate
"""
@classmethod
queries = []
for zone in ['5.delay1.example.', '0.delay2.example.']:
expected[zone] = dns.rrset.from_text(zone, 0, dns.rdataclass.IN, 'TXT', 'a')
- query = dns.message.make_query(zone, 'TXT', want_dnssec=False)
+ query = dns.message.make_query(zone, 'TXT', want_dnssec=True)
query.flags |= dns.flags.AD
queries.append(query)
print(ress[i].answer[0].to_text())
print('exp')
print(exp.to_text())
- #self.assertMessageIsAuthenticated(ress[i])
+ self.assertMessageIsAuthenticated(ress[i])
self.assertRRsetInAnswer(ress[i], exp)
- #self.assertMatchingRRSIGInAnswer(ress[i], exp)
+ self.assertMatchingRRSIGInAnswer(ress[i], exp)
i = i + 1
- def XXXOOOTimeout(self):
+ def testOOOTimeout(self):
expected = {}
queries = []
for zone in ['25.delay1.example.', '1.delay2.example.']:
- query = dns.message.make_query(zone, 'TXT', want_dnssec=False)
+ query = dns.message.make_query(zone, 'TXT', want_dnssec=True)
query.flags |= dns.flags.AD
queries.append(query)
self.assertEqual(len(ress), 2)
exp = dns.rrset.from_text('1.delay2.example.', 0, dns.rdataclass.IN, 'TXT', 'a')
self.assertRRsetInAnswer(ress[0], exp)
+ self.assertMatchingRRSIGInAnswer(ress[0], exp)
self.assertRcodeEqual(ress[1], dns.rcode.SERVFAIL)
# Let the auth timeout happen to not disturb other tests
+ # this can happen if the auth is single-threaded
time.sleep(1)