/*
PowerDNS Versatile Database Driven Nameserver
- Copyright (C) 2011 Netherlabs Computer Consulting BV
+ Copyright (C) 2015 Netherlabs Computer Consulting BV
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
return ret;
}
+boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem)
+{
+ if(local.sin4.sin_family != AF_INET || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
+ if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) {
+ int bits =local.sin4.sin_family == AF_INET ? 24 : 64;
+ ComboAddress trunc(local);
+ trunc.truncate(bits);
+ return boost::optional<Netmask>(Netmask(trunc, bits));
+ }
+ }
+ return boost::optional<Netmask>();
+}
+
+void parseEDNSSubnetWhitelist(const std::string& wlist)
+{
+ vector<string> parts;
+ stringtok(parts, wlist, ",;");
+ for(const auto& a : parts) {
+ try {
+ Netmask nm(a);
+ g_ednssubnets.addMask(nm);
+ }
+ catch(...) {
+ g_ednsdomains.add(DNSName(a));
+ }
+ }
+}
#include "namespaces.hh"
#include "iputils.hh"
+#include "dnsname.hh"
+
+extern NetmaskGroup g_ednssubnets;
+extern SuffixMatchNode g_ednsdomains;
+
struct EDNSSubnetOpts
{
bool getEDNSSubnetOptsFromString(const string& options, EDNSSubnetOpts* eso);
string makeEDNSSubnetOptsString(const EDNSSubnetOpts& eso);
-
+boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem);
+void parseEDNSSubnetWhitelist(const std::string& wlist);
#endif
return match(&ip);
}
- //! Add this Netmask to the list of possible matches
+ //! Add this string to the list of possible matches
void addMask(const string &ip)
{
d_masks.push_back(Netmask(ip));
}
+ //! Add this Netmask to the list of possible matches
+ void addMask(const Netmask& nm)
+ {
+ d_masks.push_back(nm);
+ }
+
void clear()
{
d_masks.clear();
/** lwr is only filled out in case 1 was returned, and even when returning 1 for 'success', lwr might contain DNS errors
Never throws!
*/
-int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, LWResult *lwr)
+int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult *lwr)
{
int len;
int bufsize=1500;
if(EDNS0Level && !doTCP) {
DNSPacketWriter::optvect_t opts;
- EDNSSubnetOpts eo;
- eo.source = Netmask("2001:470:1f0b:27e:1850:ae41:cc31:7765");
- opts.push_back(make_pair(8, makeEDNSSubnetOptsString(eo)));
+ if(srcmask) {
+ EDNSSubnetOpts eo;
+ eo.source = *srcmask;
+ cout<<"Adding request mask: "<<eo.source.toString()<<endl;
+ opts.push_back(make_pair(8, makeEDNSSubnetOptsString(eo)));
+ srcmask=boost::optional<Netmask>(); // this is also our return value
+ }
pw.addOpt(1200, 0, EDNSOpts::DNSSECOK, opts); // 1200 bytes answer size
pw.commit();
EDNSOpts edo;
if(EDNS0Level > 0 && getEDNSOpts(mdp, &edo)) {
lwr->d_haveEDNS = true;
+
+ for(const auto& opt : edo.d_options) {
+ if(opt.first==8) {
+ EDNSSubnetOpts reso;
+ if(getEDNSSubnetOptsFromString(opt.second, &reso)) {
+ cerr<<"EDNS Subnet response: "<<reso.source.toString()<<", scope: "<<reso.scope.toString()<<", family = "<<reso.scope.getNetwork().sin4.sin_family<<endl;
+ if(srcmask)
+ srcmask = reso.scope;
+ }
+ }
+
+ }
}
return 1;
bool d_haveEDNS;
};
-int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, LWResult* res);
-
+int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res);
#endif // PDNS_LWRES_HH
__thread boost::circular_buffer<pair<DNSName, uint16_t> >* t_queryring, *t_servfailqueryring;
__thread shared_ptr<Regex>* t_traceRegex;
+NetmaskGroup g_ednssubnets;
+SuffixMatchNode g_ednsdomains;
+
DNSFilterEngine g_dfe;
RecursorControlChannel s_rcc; // only active in thread 0
makeUDPServerSockets();
makeTCPServerSockets();
+ parseEDNSSubnetWhitelist(::arg()["edns-subnet-whitelist"]);
+
int forks;
for(forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) {
if(!fork()) // we are child
// ::arg().setSwitch( "disable-edns-ping", "Disable EDNSPing - EXPERIMENTAL, LEAVE DISABLED" )= "no";
::arg().setSwitch( "disable-edns", "Disable EDNS - EXPERIMENTAL, LEAVE DISABLED" )= "";
::arg().setSwitch( "disable-packetcache", "Disable packetcache" )= "no";
+ ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for")="powerdns.com,82.94.213.34,2001:888:2000:1d::2";
::arg().setSwitch( "pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads")="";
::arg().setSwitch( "root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist")="no";
::arg().setSwitch( "any-to-tcp","Answer ANY queries with tc=1, shunting to TCP" )="no";
return ret;
}
-int MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures)
+int MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures)
{
unsigned int ttd=0;
// cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n";
) {
ttd = i->d_ttd;
- for(auto k=i->d_records.begin(); k != i->d_records.end(); ++k) {
+ auto records = &i->d_records;
+ if(!i->d_subnetspecific.empty()) {
+ for(const auto& p : i->d_subnetspecific) {
+ if(p.first.match(who)) {
+ records = &p.second;
+ break;
+ }
+ }
+ }
+ for(auto k=records->begin(); k != records->end(); ++k) {
if(res) {
DNSRecord dr;
dr.d_name = qname;
return true;
}
-void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth)
+void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask)
{
+ if(ednsmask) {
+ cerr<<"This data is actually subnet mask specific!!"<<endl;
+ }
d_cachecachevalid=false;
boost::tuple<DNSName, uint16_t> key=boost::make_tuple(qname, qt.getCode());
cache_t::iterator stored=d_cache.find(key);
// cerr<<"asked to store "<< qname<<"|"+qt.getName()<<" -> '"<<content.begin()->d_content->getZoneRepresentation()<<"', auth="<<auth<<", ce.auth="<<ce.d_auth<<"\n";
- ce.d_records.clear();
+ auto records = &ce.d_records;
+ if(ednsmask) {
+ records=nullptr; // there, we use nullptr!
+
+ /* so the logic here is.. if you update a specific mask, we'll update too.
+ If you supply anything more generic, we'll store that next to your more
+ specific answers.
+ If you supply more specific answers, we'll store those too. */
+ for(auto &p : ce.d_subnetspecific) {
+ if(p.first == *ednsmask) {
+ records = &p.second;
+ break;
+ }
+ }
+ if(records==nullptr) {
+ ce.d_subnetspecific.push_back({*ednsmask, CacheEntry::records_t()});
+ records = &ce.d_subnetspecific.rbegin()->second;
+ }
+
+ }
+ records->clear();
if(!auth && ce.d_auth) { // unauth data came in, we have some auth data, but is it fresh?
if(ce.d_ttd > now) { // we still have valid data, ignore unauth data
// make sure that we CAN refresh the root
if(auth && ((qname == DNSName()) || !attemptToRefreshNSTTL(qt, content, ce) ) ) {
// cerr<<"\tGot auth data, and it was not refresh attempt of an unchanged NS set, nuking storage"<<endl;
- ce.d_records.clear(); // clear non-auth data
+ records->clear(); // clear non-auth data
ce.d_auth = true;
}
// else cerr<<"\tNot nuking"<<endl;
for(auto i=content.cbegin(); i != content.cend(); ++i) {
// cerr<<"To store: "<<i->content<<" with ttl/ttd "<<i->ttl<<endl;
ce.d_ttd=min(maxTTD, i->d_ttl); // XXX this does weird things if TTLs differ in the set
- ce.d_records.push_back(i->d_content);
-
- /*
- else {
- range=equal_range(ce.d_records.begin(), ce.d_records.end(), dr);
-
- if(range.first != range.second) {
- // cerr<<"\t\tMay need to modify TTL of stored record\n";
- for(vector<StoredRecord>::iterator j=range.first ; j!=range.second; ++j) {
- // see http://mailman.powerdns.com/pipermail/pdns-users/2006-May/003413.html
- if(j->d_ttd > (unsigned int) now && i->ttl > j->d_ttd && qt.getCode()==QType::NS && auth) { // don't allow auth servers to *raise* TTL of an NS record
- //~ cerr<<"\t\tNot doing so, trying to raise TTL NS\n";
- continue;
- }
- if(i->ttl > j->d_ttd || (auth) ) { // authoritative packets can override the TTL to be lower
- //~ cerr<<"\t\tUpdating the ttl, diff="<<j->d_ttd - i->ttl<<endl;;
- j->d_ttd=i->ttl;
- }
- else {
- //~ cerr<<"\t\tNOT updating the ttl, old= " <<j->d_ttd - now <<", new: "<<i->ttl - now <<endl;
- }
- }
- }
- else {
- //~ cerr<<"\t\tThere was no exact copy of this record, so adding & sorting\n";
- ce.d_records.push_back(dr);
- sort(ce.d_records.begin(), ce.d_records.end());
- }
- }
- */
+ records->push_back(i->d_content);
+ // there was code here that did things with TTL and auth. Unsure if it was good. XXX
}
d_cache.replace(stored, ce);
}
unsigned int size();
unsigned int bytes();
- int get(time_t, const DNSName &qname, const QType& qt, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures=0);
+ int get(time_t, const DNSName &qname, const QType& qt, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures=0);
- void replace(time_t, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth);
+ void replace(time_t, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask=boost::optional<Netmask>());
void doPrune(void);
void doSlash(int perc);
uint64_t doDump(int fd);
#include "dnsparser.hh"
#include "dns_random.hh"
#include "lock.hh"
+#include "ednssubnet.hh"
#include "cachecleaner.hh"
__thread SyncRes::StaticStorage* t_sstorage;
fclose(fp);
}
-int SyncRes::asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, LWResult* res)
+int SyncRes::asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res)
{
/* what is your QUEST?
the goal is to get as many remotes as possible on the highest level of EDNS support
EDNSLevel = 0;
}
- ret=asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, res);
+ ret=asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, res);
if(ret == 0 || ret < 0) {
// cerr<< (ret < 0 ? "Transport error" : "Timeout")<<" for query to "<<ip.toString()<<" for '"<<domain.toString()<<"' (ret="<<ret<<"), no change in mode"<<endl;
const ComboAddress remoteIP = servers.front();
LOG(prefix<<qname.toString()<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname.toString()<<"'"<<endl);
- res=asyncresolveWrapper(remoteIP, qname, qtype.getCode(), false, false, &d_now, &lwr);
+ boost::optional<Netmask> nm;
+ res=asyncresolveWrapper(remoteIP, qname, qtype.getCode(), false, false, &d_now, nm, &lwr);
// filter out the good stuff from lwr.result()
for(const auto& rec : lwr.d_records) {
if(done) {
if(j==1 && s_doIPv6) { // we got an A record, see if we have some AAAA lying around
vector<DNSRecord> cset;
- if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), &cset) > 0) {
+ if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), &cset, d_requestor) > 0) {
for(auto k=cset.cbegin();k!=cset.cend();++k) {
if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
ComboAddress ca=std::dynamic_pointer_cast<AAAARecordContent>(k->d_content)->getCA(53);
LOG(prefix<<qname.toString()<<": Checking if we have NS in cache for '"<<subdomain.toString()<<"'"<<endl);
vector<DNSRecord> ns;
*flawedNSSet = false;
- if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), &ns) > 0) {
+ if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), &ns, d_requestor) > 0) {
for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
vector<DNSRecord> aset;
const DNSRecord& dr=*k;
auto nrr = std::dynamic_pointer_cast<NSRecordContent>(dr.d_content);
if(!nrr->getNS().isPartOf(subdomain) || t_RC->get(d_now.tv_sec, nrr->getNS(), s_doIPv6 ? QType(QType::ADDR) : QType(QType::A),
- doLog() ? &aset : 0) > 5) {
+ doLog() ? &aset : 0, d_requestor) > 5) {
bestns.push_back(dr);
LOG(prefix<<qname.toString()<<": NS (with ip, or non-glue) in cache for '"<<subdomain.toString()<<"' -> '"<<nrr->getNS()<<"'"<<endl);
LOG(prefix<<qname.toString()<<": within bailiwick: "<< nrr->getNS().isPartOf(subdomain));
LOG(prefix<<qname.toString()<<": Looking for CNAME cache hit of '"<<(qname.toString()+"|CNAME")<<"'"<<endl);
vector<DNSRecord> cset;
vector<std::shared_ptr<RRSIGRecordContent>> signatures;
- if(t_RC->get(d_now.tv_sec, qname,QType(QType::CNAME), &cset, &signatures) > 0) {
+ if(t_RC->get(d_now.tv_sec, qname,QType(QType::CNAME), &cset, d_requestor, &signatures) > 0) {
for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
if(j->d_ttl>(unsigned int) d_now.tv_sec) {
bool found=false, expired=false;
vector<std::shared_ptr<RRSIGRecordContent>> signatures;
uint32_t ttl=0;
- if(t_RC->get(d_now.tv_sec, sqname, sqt, &cset, d_doDNSSEC ? &signatures : 0) > 0) {
+ if(t_RC->get(d_now.tv_sec, sqname, sqt, &cset, d_requestor, d_doDNSSEC ? &signatures : 0) > 0) {
LOG(prefix<<sqname.toString()<<": Found cache hit for "<<sqt.getName()<<": ");
for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
LOG(j->d_content->getZoneRepresentation());
int resolveret;
bool pierceDontQuery=false;
bool sendRDQuery=false;
+ boost::optional<Netmask> ednsmask;
LWResult lwr;
if(tns->empty()) {
LOG(prefix<<qname.toString()<<": Domain is out-of-band"<<endl);
if(d_pdl && d_pdl->preoutquery(*remoteIP, d_requestor, qname, qtype, lwr.d_records, resolveret)) {
LOG(prefix<<qname.toString()<<": query handled by Lua"<<endl);
}
- else
+ else {
+ ednsmask=getEDNSSubnetMask(d_requestor, qname, *remoteIP);
resolveret=asyncresolveWrapper(*remoteIP, qname, qtype.getCode(),
- doTCP, sendRDQuery, &d_now, &lwr); // <- we go out on the wire!
-
+ doTCP, sendRDQuery, &d_now, ednsmask, &lwr); // <- we go out on the wire!
+ if(ednsmask && ednsmask->getBits()) {
+ cerr<<"Actually got something back.. "<<ednsmask->toString()<<endl;
+ }
+ }
if(resolveret==-3)
throw ImmediateServFailException("Query killed by policy");
// cout<<"Have "<<i->second.records.size()<<" records and "<<i->second.signatures.size()<<" signatures for "<<i->first.first.toString();
// cout<<'|'<<DNSRecordContent::NumberToType(i->first.second.getCode())<<endl;
- t_RC->replace(d_now.tv_sec, i->first.first, i->first.second, i->second.records, i->second.signatures, lwr.d_aabit);
+ t_RC->replace(d_now.tv_sec, i->first.first, i->first.second, i->second.records, i->second.signatures, lwr.d_aabit, ednsmask);
}
set<DNSName> nsset;
LOG(prefix<<qname.toString()<<": determining status after receiving this packet"<<endl);
}
- int asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, LWResult* res);
+ int asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res);
static void doEDNSDumpAndClose(int fd);