%description backend-sqlite
This package contains the SQLite backend for %{name}
+%package backend-tinydns
+Summary: TinyDNS backend for %{name}
+Group: System Environment/Daemons
+Requires: %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: tinycdb-devel
+%global backends %{backends} tinydns
+
+%description backend-tinydns
+This package contains the TinyDNS backend for %{name}
+
%prep
%setup -q -n %{name}-${TARBALLVERSION}
%doc modules/gsqlite3backend/dnssec-3.x_to_3.4.0_schema.sqlite3.sql
%doc modules/gsqlite3backend/nodnssec-3.x_to_3.4.0_schema.sqlite3.sql
%{_libdir}/%{name}/libgsqlite3backend.so
+
+%files backend-tinydns
+%{_libdir}/%{name}/libtinydnsbackend.so
EOF
;;
SLES\ 12*)
AC_MSG_NOTICE([Built-in modules: $modules])
AC_MSG_NOTICE([Dynamic modules: $dynmodules])
AC_MSG_NOTICE([])
-AS_IF([test "x$openssl_ecdsa" == "xyes"],
+AS_IF([test "x$openssl_ecdsa" = "xyes"],
[AC_MSG_NOTICE([OpenSSL ecdsa: yes])],
[AC_MSG_NOTICE([OpenSSL ecdsa: no])]
)
[AC_MSG_NOTICE([LuaJit: $LUAJITPC])],
[AC_MSG_NOTICE([Lua/LuaJit: no])])
])
-AS_IF([test "x$enable_experimental_gss_tsig" == "xyes"],
+AS_IF([test "x$enable_experimental_gss_tsig" = "xyes"],
[AC_MSG_NOTICE([GSS-TSIG: yes])]
)
AS_IF([test "x$systemd" != "xn"],
You can also download releases on the [website](https://downloads.powerdns.com/releases/).
These releases are PGP-signed with key-id [FBAE 0323 821C 7706 A5CA 151B DCF5
-13FA 7EED 19F3](https://pgp.mit.edu/pks/lookup?op=get&search=0xDCF513FA7EED19F3)
-or [1628 90D0 689D D12D D33E 4696 1C5E
-E990 D2E7 1575](https://pgp.mit.edu/pks/lookup?op=get&search=0x1C5EE990D2E71575).
+13FA 7EED 19F3](https://pgp.mit.edu/pks/lookup?op=get&search=0xDCF513FA7EED19F3),
+[1628 90D0 689D D12D D33E 4696 1C5E
+E990 D2E7 1575](https://pgp.mit.edu/pks/lookup?op=get&search=0x1C5EE990D2E71575)
+or [B76C D467 1C09 68BA A87D E61C 5E50 715B F2FF E1A7](https://pgp.mit.edu/pks/lookup?op=get&search=0x5E50715BF2FFE1A7).
## OS specific gotcha's
### AIX
* Default: yes
If a PID file should be written. Available since 4.0.
+
+## `xfr-max-received-mbytes`
+* Integer
+* Default: 100
+
+Specifies the maximum number of received megabytes allowed on an incoming AXFR/IXFR update, to prevent
+resource exhaustion. A value of 0 means no restriction.
**Note**: Beyond PowerDNS 2.9.20, the Authoritative Server and Recursor are released separately.
+# PowerDNS Recursor 4.0.1
+UNRELEASED
+
+This release improves interoperability with DNSSEC clients that expect an AD-bit on validated data when they query with only the DO-bit set.
+
+## Bug fixes
+
+ - [#4162](https://github.com/PowerDNS/pdns/pull/4162) Don't validate zones from the local auth store, go one level down while validating when there is a CNAME
+ - [#4187](https://github.com/PowerDNS/pdns/pull/4187):
+ * Don't go bogus on islands of security
+ * Check all possible chains for Insecures
+ * Don't go Bogus on a CNAME at the apex
+
+## Improvements
+
+ - [#4160](https://github.com/PowerDNS/pdns/pull/4160) Also validate on +DO
+
# PowerDNS Recursor 4.0.0
Released July 11th 2016
## `process`
When `dnssec` is set to `process` the behaviour is similar to [`process-no-validate`](#process-no-validate).
-However, when the query has the AD-bit set, the recursor will try to validate the
-data and set the AD-bit in the response when the data is validated and send a
-SERVFAIL on a bogus answer.
+However, the recursor will try to validate the data if at least one of the DO or AD bits is set in the query; in that case, it will set the AD-bit in the response when the data is validated successfully, or send SERVFAIL when the validation comes up bogus.
+
+**Note:** in 4.0.0, only the AD-bit was considered when determining whether to validate.
+This lead to interoperability issues with older client software.
+From 4.0.1-onward, the DO-bit is also taken into account when determining whether to validate.
## `log-fail`
-In this mode , the recursor will attempt to validate all data it retrieves from
+In this mode, the recursor will attempt to validate all data it retrieves from
authoritative servers, regardless of the client's DNSSEC desires, and will log the
validation result. This mode can be used to determine the extra load and amount
of possibly bogus answers before turning on full-blown validation. Responses to
| | `off` | `process-no-validate` | `process` | `log-fail` | `validate` |
|:------------|:-------|:-------------|:-------------|:-------------|:-------------|
-|Perform validation| No | No | Only on +AD from client | Always (logs result) | Always |
-|SERVFAIL on bogus| No | No | Only on +AD from client | Only on +AD from client | Always |
-|AD in response on authenticated data| Never | Never | Only on +AD from client | Only on +AD from client | Only on +AD from client |
+|Perform validation| No | No | Only on +AD or +DO from client | Always (logs result) | Always |
+|SERVFAIL on bogus| No | No | Only on +AD or +DO from client | Only on +AD or +DO from client | Always |
+|AD in response on authenticated data| Never | Never | Only on +AD or +DO from client | Only on +AD or +DO from client | Only on +AD or +DO from client |
|RRSIGs/NSECs in answer on +DO from client| No | Yes | Yes | Yes | Yes |
**Note**: the `dig` tool sets the AD-bit in the query. This might lead to unexpected
### `process`
Respond with DNSSEC records to clients that ask for it, set the DO bit on all
outgoing queries. Do validation for clients that request it (by means of the AD-
-bit in the query).
+bit or DO-bit in the query).
### `log-fail`
Similar behaviour to `process`, but validate RRSIGs on responses and log bogus
* tsigalgo = the name of the TSIG algorithm (like 'hmac-md5') used
* tsigsecret = base64 encoded TSIG secret
* refresh = an integer describing the interval between checks for updates. By default, the RPZ zone's default is used
+* maxReceivedMBytes = the maximum size in megabytes of an AXFR/IXFR update, to prevent resource exhaustion.
+The default value of 0 means no restriction.
If no settings are included, the RPZ is taken literally with no overrides applied.
Also note that unauthorized-tcp and unauthorized-udp packets do not end up in
the 'questions' count.
-Every half our or so, the recursor outputs a line with statistics. More
+Every half hour or so, the recursor outputs a line with statistics. More
infrastructure is planned so as to allow for Cricket or MRTG graphs. To force
the output of statistics, send the process a SIGUSR1. A line of statistics looks
like this:
queries may be needed to actually recurse the DNS and figure out the addresses
of nameservers.
-Finally, 12% of queries were not performed because identical queries had gone out previously, saving load servers worldwide.
+Finally, 12% of queries were not performed because identical queries had gone out
+previously, saving load on servers worldwide.
AS_IF([test "x$enable_remotebackend_zeromq" != "xno"],
[
- AS_IF([test "x$have_remotebackend" == "xyes"],
+ AS_IF([test "x$have_remotebackend" = "xyes"],
[
PKG_CHECK_MODULES([LIBZMQ], [libzmq],
[
return 1;
}
- lua_pushstring(lua, lb->dnspacket->getRemote().c_str());
+ lua_pushstring(lua, lb->dnspacket->getRemote().toString().c_str());
lua_pushinteger(lua, lb->dnspacket->getRemotePort());
- lua_pushstring(lua, lb->dnspacket->getLocal().c_str());
+ lua_pushstring(lua, lb->dnspacket->getLocal().toString().c_str());
lua_pushstring(lua, lb->dnspacket->getRealRemote().toString().c_str());
return 4;
if( type == WRITE && getArg( "backend" ) == "sqlite" )
{
- L.log( m_myname + " Using same SQLite connection for reading and writeing to '" + hosts[odbx_host_index[READ]] + "'", Logger::Notice );
+ L.log( m_myname + " Using same SQLite connection for reading and writing to '" + hosts[odbx_host_index[READ]] + "'", Logger::Notice );
m_handle[WRITE] = m_handle[READ];
return true;
}
string remoteIP="0.0.0.0";
Netmask realRemote("0.0.0.0/0");
if (pkt_p) {
- localIP=pkt_p->getLocal();
+ localIP=pkt_p->getLocal().toString();
realRemote = pkt_p->getRealRemote();
- remoteIP = pkt_p->getRemote();
+ remoteIP = pkt_p->getRemote().toString();
}
// abi-version = 1
// type qname qclass qtype id remote-ip-address
string realRemote="0.0.0.0/0";
if (pkt_p) {
- localIP=pkt_p->getLocal();
+ localIP=pkt_p->getLocal().toString();
realRemote = pkt_p->getRealRemote().toString();
- remoteIP = pkt_p->getRemote();
+ remoteIP = pkt_p->getRemote().toString();
}
Json query = Json::object{
* Number of entries in a given section (RecordsCountRule)
* Number of entries of a specific type in a given section (RecordsTypeCountRule)
* Presence of trailing data (TrailingDataRule)
+ * Number of labels in the qname (QNameLabelsCountRule)
+ * Wire length of the qname (QNameWireLengthRule)
Special rules are:
* an OpcodeRule
* an OrRule
* a QClassRule
+ * a QNameLabelsCountRule
+ * a QNameWireLengthRule
* a QTypeRule
* a RegexRule
* a RE2Rule
* `OrRule()`: matches if at least one of the sub-rules matches
* `OpcodeRule()`: matches queries with the specified opcode
* `QClassRule(qclass)`: matches queries with the specified qclass (numeric)
+ * `QNameLabelsCountRule(min, max)`: matches if the qname has less than `min` or more than `max` labels
+ * `QNameWireLengthRule(min, max)`: matches if the qname's length on the wire is less than `min` or more than `max` bytes
* `QTypeRule(qtype)`: matches queries with the specified qtype
* `RegexRule(regex)`: matches the query name against the supplied regex
* `RecordsCountRule(section, minCount, maxCount)`: matches if there is at least `minCount` and at most `maxCount` records in the `section` section
* `toStringWithPort()`: alias for `tostringWithPort()`
* DNSName related:
* `newDNSName(name)`: make a DNSName based on this .-terminated name
+ * member `countLabels()`: return the number of labels
* member `isPartOf(dnsname)`: is this dnsname part of that dnsname
* member `tostring()`: return as a human friendly . terminated string
* member `toString()`: alias for `tostring()`
+ * member `wirelength()`: return the length on the wire
* DNSQuestion related:
* member `dh`: DNSHeader
* member `len`: the question length
::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR")="no";
::arg().setSwitch("8bit-dns", "Allow 8bit dns queries")="no";
+
+ ::arg().set("xfr-max-received-mbytes", "Maximum number of megabytes received from an incoming XFR")="100";
}
static time_t s_start=time(0);
if(logDNSQueries) {
string remote;
if(P->hasEDNSSubnet())
- remote = P->getRemote() + "<-" + P->getRealRemote().toString();
+ remote = P->getRemote().toString() + "<-" + P->getRealRemote().toString();
else
- remote = P->getRemote();
+ remote = P->getRemote().toString();
L << Logger::Notice<<"Remote "<< remote <<" wants '" << P->qdomain<<"|"<<P->qtype.getName() <<
"', do = " <<P->d_dnssecOk <<", bufsize = "<< P->getMaxReplyLen()<<": ";
}
"PoolAction(", "printDNSCryptProviderFingerprint(",
"RegexRule(", "RemoteLogAction(", "RemoteLogResponseAction(", "rmResponseRule(",
"rmRule(", "rmServer(", "roundrobin",
- "QTypeRule(",
+ "QNameLabelsCountRule(", "QNameWireLengthRule(", "QTypeRule(",
"setACL(", "setDNSSECPool(", "setECSOverride(",
"setECSSourcePrefixV4(", "setECSSourcePrefixV6(", "setKey(", "setLocal(",
"setMaxTCPClientThreads(", "setMaxTCPQueuedConnections(", "setMaxUDPOutstanding(", "setRules(",
return std::shared_ptr<DNSRule>(new TrailingDataRule());
});
+ g_lua.writeFunction("QNameLabelsCountRule", [](unsigned int minLabelsCount, unsigned int maxLabelsCount) {
+ return std::shared_ptr<DNSRule>(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount));
+ });
+
+ g_lua.writeFunction("QNameWireLengthRule", [](size_t min, size_t max) {
+ return std::shared_ptr<DNSRule>(new QNameWireLengthRule(min, max));
+ });
+
g_lua.writeFunction("addAction", [](luadnsrule_t var, std::shared_ptr<DNSAction> ea)
{
setLuaSideEffect();
g_lua.registerFunction("toStringWithPort", &ComboAddress::toStringWithPort);
g_lua.registerFunction<uint16_t(ComboAddress::*)()>("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } );
g_lua.registerFunction("isPartOf", &DNSName::isPartOf);
+ g_lua.registerFunction("countLabels", &DNSName::countLabels);
+ g_lua.registerFunction("wirelength", &DNSName::wirelength);
g_lua.registerFunction<string(DNSName::*)()>("tostring", [](const DNSName&dn ) { return dn.toString(); });
g_lua.registerFunction<string(DNSName::*)()>("toString", [](const DNSName&dn ) { return dn.toString(); });
g_lua.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); });
return d_rawpacket;
}
-string DNSPacket::getRemote() const
+ComboAddress DNSPacket::getRemote() const
{
- return d_remote.toString();
+ return d_remote;
}
uint16_t DNSPacket::getRemotePort() const
// address & socket manipulation
void setRemote(const ComboAddress*);
- string getRemote() const;
+ ComboAddress getRemote() const;
Netmask getRealRemote() const;
- string getLocal() const
+ ComboAddress getLocal() const
{
ComboAddress ca;
socklen_t len=sizeof(ca);
getsockname(d_socket, (sockaddr*)&ca, &len);
- return ca.toString();
+ return ca;
}
uint16_t getRemotePort() const;
// dh->rd=1; // useful to replay traffic to auths to a recursor
uint16_t dlen = pr.d_len;
- addECSOption((char*)pr.d_payload, 1500, &dlen, pr.getSource(), stamp);
+ if (stamp >= 0) addECSOption((char*)pr.d_payload, 1500, &dlen, pr.getSource(), stamp);
pr.d_len=dlen;
s_socket->sendTo((const char*)pr.d_payload, dlen, remote);
sent=true;
}
};
+class QNameLabelsCountRule : public DNSRule
+{
+public:
+ QNameLabelsCountRule(unsigned int minLabelsCount, unsigned int maxLabelsCount): d_min(minLabelsCount), d_max(maxLabelsCount)
+ {
+ }
+ bool matches(const DNSQuestion* dq) const override
+ {
+ unsigned int count = dq->qname->countLabels();
+ return count < d_min || count > d_max;
+ }
+ string toString() const override
+ {
+ return "labels count < " + std::to_string(d_min) + " || labels count > " + std::to_string(d_max);
+ }
+private:
+ unsigned int d_min;
+ unsigned int d_max;
+};
+
+class QNameWireLengthRule : public DNSRule
+{
+public:
+ QNameWireLengthRule(size_t min, size_t max): d_min(min), d_max(max)
+ {
+ }
+ bool matches(const DNSQuestion* dq) const override
+ {
+ size_t const wirelength = dq->qname->wirelength();
+ return wirelength < d_min || wirelength > d_max;
+ }
+ string toString() const override
+ {
+ return "wire length < " + std::to_string(d_min) + " || wire length > " + std::to_string(d_max);
+ }
+private:
+ size_t d_min;
+ size_t d_max;
+};
+
class DropAction : public DNSAction
{
public:
// Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& oursr,
- const TSIGTriplet& tt, const ComboAddress* laddr)
+ const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes)
{
vector<pair<vector<DNSRecord>, vector<DNSRecord> > > ret;
vector<uint8_t> packet;
// CURRENT MASTER SOA
shared_ptr<SOARecordContent> masterSOA;
vector<DNSRecord> records;
+ size_t receivedBytes = 0;
for(;;) {
if(s.read((char*)&len, 2)!=2)
break;
// cout<<"Got chunk of "<<len<<" bytes"<<endl;
if(!len)
break;
+
+ if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len)
+ throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toString()+"' from master '"+master.toStringWithPort());
+
char reply[len];
readn2(s.getHandle(), reply, len);
+ receivedBytes += len;
MOADNSParser mdp(string(reply, len));
if(mdp.d_header.rcode)
throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone,
const DNSRecord& sr, const TSIGTriplet& tt=TSIGTriplet(),
- const ComboAddress* laddr=0);
+ const ComboAddress* laddr=0, size_t maxReceivedBytes=0);
return *this;
}
+
+Logger& Logger::operator<<(const ComboAddress &ca)
+{
+ *this<<ca.toString();
+ return *this;
+}
+
#include "namespaces.hh"
#include "dnsname.hh"
+#include "iputils.hh"
//! The Logger class can be used to log messages in various ways.
class Logger
Logger& operator<<(unsigned long); //!< log an unsigned int
Logger& operator<<(unsigned long long); //!< log an unsigned 64 bit int
Logger& operator<<(const DNSName&);
-
+ Logger& operator<<(const ComboAddress&); //!< log an address
Logger& operator<<(Urgency); //!< set the urgency, << style
Logger& operator<<(std::ostream & (&)(std::ostream &)); //!< this is to recognise the endl, and to commit the log
static int ldp_getRemote(lua_State *L) {
DNSPacket *p=ldp_checkDNSPacket(L);
- lua_pushstring(L, p->getRemote().c_str());
+ lua_pushstring(L, p->getRemote().toString().c_str());
return 1;
}
static int ldp_getRemoteRaw(lua_State *L) {
DNSPacket *p=ldp_checkDNSPacket(L);
- const ComboAddress& ca=p->d_remote;
+ const ComboAddress& ca=p->getRemote();
if(ca.sin4.sin_family == AF_INET) {
lua_pushlstring(L, (const char*)&ca.sin4.sin_addr.s_addr, 4);
}
* an "upstream query". To stay true to "dnssec=off means 3.X behaviour", we
* only set +CD on forwarded query in any mode other than dnssec=off.
*/
- pw.getHeader()->cd=(sendRDQuery && ::arg()["dnssec"] != "off");
+ pw.getHeader()->cd=(sendRDQuery && g_dnssecmode != DNSSECMode::Off);
string ping;
bool weWantEDNSSubnet=false;
try {
Resolver resolver;
uint32_t theirserial;
- resolver.getSoaSerial(p->getRemote(),p->qdomain, &theirserial);
- resolver.resolve(p->getRemote(), p->qdomain, QType::NS, &nsset);
+ resolver.getSoaSerial(p->getRemote().toString(),p->qdomain, &theirserial);
+ resolver.resolve(p->getRemote().toString(), p->qdomain, QType::NS, &nsset);
}
catch(ResolverException &re) {
L<<Logger::Error<<"Error resolving SOA or NS for "<<p->qdomain<<" at: "<< p->getRemote() <<": "<<re.reason<<endl;
return RCode::Refused;
}
- if(!B.superMasterBackend(p->getRemote(), p->qdomain, nsset, &nameserver, &account, &db)) {
+ if(!B.superMasterBackend(p->getRemote().toString(), p->qdomain, nsset, &nameserver, &account, &db)) {
L<<Logger::Error<<"Unable to find backend willing to host "<<p->qdomain<<" for potential supermaster "<<p->getRemote()<<". Remote nameservers: "<<endl;
for(const auto& rr: nsset) {
if(rr.qtype.getCode()==QType::NS)
return RCode::Refused;
}
try {
- db->createSlaveDomain(p->getRemote(), p->qdomain, nameserver, account);
+ db->createSlaveDomain(p->getRemote().toString(), p->qdomain, nameserver, account);
if (tsigkeyname.empty() == false) {
vector<string> meta;
meta.push_back(tsigkeyname.toStringNoDot());
}
}
- if(::arg().contains("trusted-notification-proxy", p->getRemote())) {
+ if(::arg().contains("trusted-notification-proxy", p->getRemote().toString())) {
L<<Logger::Error<<"Received NOTIFY for "<<p->qdomain<<" from trusted-notification-proxy "<< p->getRemote()<<endl;
if(di.masters.empty()) {
L<<Logger::Error<<"However, "<<p->qdomain<<" does not have any masters defined"<<endl;
L<<Logger::Error<<"Received NOTIFY for "<<p->qdomain<<" from "<<p->getRemote()<<" but we are master, rejecting"<<endl;
return RCode::Refused;
}
- else if(!db->isMaster(p->qdomain, p->getRemote())) {
+ else if(!db->isMaster(p->qdomain, p->getRemote().toString())) {
L<<Logger::Error<<"Received NOTIFY for "<<p->qdomain<<" from "<<p->getRemote()<<" which is not a master"<<endl;
return RCode::Refused;
}
}
- DLOG(L<<"After first ANY query for '"<<target<<"', id="<<sd.domain_id<<": weDone="<<weDone<<", weHaveUnauth="<<weHaveUnauth<<", weRedirected="<<weRedirected<<", haveAlias='"<<(haveAlias.empty() ? "(none)" : haveAlias)<<"'"<<endl);
+ DLOG(L<<"After first ANY query for '"<<target<<"', id="<<sd.domain_id<<": weDone="<<weDone<<", weHaveUnauth="<<weHaveUnauth<<", weRedirected="<<weRedirected<<", haveAlias='"<<haveAlias<<"'"<<endl);
if(p->qtype.getCode() == QType::DS && weHaveUnauth && !weDone && !weRedirected && d_dk.isSecuredZone(sd.qname)) {
DLOG(L<<"Q for DS of a name for which we do have NS, but for which we don't have on a zone with DNSSEC need to provide an AUTH answer that proves we don't"<<endl);
makeNOError(p, r, target, DNSName(), sd, 1);
bool tracedQuery=false; // we could consider letting Lua know about this too
bool variableAnswer = false;
+ bool shouldNotValidate = false;
int res;
DNSFilterEngine::Policy dfepol;
break;
}
-
if(!t_pdl->get() || !(*t_pdl)->preresolve(dc->d_remote, dc->d_local, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_tcp, ret, dc->d_ednsOpts.empty() ? 0 : &dc->d_ednsOpts, dc->d_tag, &appliedPolicy, &dc->d_policyTags, res, &variableAnswer)) {
try {
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)
pw.getHeader()->rcode=res;
// Does the validation mode or query demand validation?
- if(g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode==DNSSECMode::ValidateForLog || (dc->d_mdp.d_header.ad && g_dnssecmode==DNSSECMode::Process)) {
+ if(!shouldNotValidate && (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode==DNSSECMode::ValidateForLog || ((dc->d_mdp.d_header.ad || DNSSECOK) && g_dnssecmode==DNSSECMode::Process))) {
try {
if(sr.doLog()) {
- L<<Logger::Warning<<"Starting validation of answer to "<<dc->d_mdp.d_qname<<" for "<<dc->d_remote.toStringWithPort()<<endl;
+ L<<Logger::Warning<<"Starting validation of answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<endl;
}
auto state=validateRecords(ret);
if(state == Secure) {
if(sr.doLog()) {
- L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<" for "<<dc->d_remote.toStringWithPort()<<" validates correctly"<<endl;
+ L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates correctly"<<endl;
}
// Is the query source interested in the value of the ad-bit?
- if (dc->d_mdp.d_header.ad)
+ if (dc->d_mdp.d_header.ad || DNSSECOK)
pw.getHeader()->ad=1;
}
else if(state == Insecure) {
if(sr.doLog()) {
- L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<" for "<<dc->d_remote.toStringWithPort()<<" validates as Insecure"<<endl;
+ L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates as Insecure"<<endl;
}
pw.getHeader()->ad=0;
}
else if(state == Bogus) {
if(g_dnssecLogBogus || sr.doLog() || g_dnssecmode == DNSSECMode::ValidateForLog) {
- L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<" for "<<dc->d_remote.toStringWithPort()<<" validates as Bogus"<<endl;
+ L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates as Bogus"<<endl;
}
// Does the query or validation mode sending out a SERVFAIL on validation errors?
- if(!pw.getHeader()->cd && (g_dnssecmode == DNSSECMode::ValidateAll || dc->d_mdp.d_header.ad)) {
+ if(!pw.getHeader()->cd && (g_dnssecmode == DNSSECMode::ValidateAll || dc->d_mdp.d_header.ad || DNSSECOK)) {
if(sr.doLog()) {
- L<<Logger::Warning<<"Sending out SERVFAIL for "<<dc->d_mdp.d_qname<<" because recursor or query demands it for Bogus results"<<endl;
+ L<<Logger::Warning<<"Sending out SERVFAIL for "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" because recursor or query demands it for Bogus results"<<endl;
}
pw.getHeader()->rcode=RCode::ServFail;
goto sendit;
} else {
if(sr.doLog()) {
- L<<Logger::Warning<<"Not sending out SERVFAIL for "<<dc->d_mdp.d_qname<<" Bogus validation since neither config nor query demands this"<<endl;
+ L<<Logger::Warning<<"Not sending out SERVFAIL for "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" Bogus validation since neither config nor query demands this"<<endl;
}
}
}
}
catch(ImmediateServFailException &e) {
if(g_logCommonErrors)
- L<<Logger::Notice<<"Sending SERVFAIL to "<<dc->getRemote()<<" during validation of '"<<dc->d_mdp.d_qname<<"' because: "<<e.reason<<endl;
+ L<<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;
goto sendit;
}
bool needCommit = false;
for(auto i=ret.cbegin(); i!=ret.cend(); ++i) {
- if(!DNSSECOK && (i->d_type == QType::RRSIG || i->d_type==QType::NSEC || i->d_type==QType::NSEC3))
+ if( ! DNSSECOK &&
+ ( i->d_type == QType::NSEC3 ||
+ (
+ ( i->d_type == QType::RRSIG || i->d_type==QType::NSEC ) &&
+ (
+ ( dc->d_mdp.d_qtype != i->d_type && dc->d_mdp.d_qtype != QType::ANY ) ||
+ i->d_place != DNSResourceRecord::ANSWER
+ )
+ )
+ )
+ ) {
continue;
+ }
+
pw.startRecord(i->d_name, i->d_type, i->d_ttl, i->d_class, i->d_place);
if(i->d_type != QType::OPT) // their TTL ain't real
minTTL = min(minTTL, i->d_ttl);
TSIGTriplet tt;
int refresh=0;
std::string polName;
+ size_t maxReceivedXFRMBytes = 0;
if(options) {
auto& have = *options;
if(have.count("policyName")) {
if(have.count("refresh")) {
refresh = boost::get<int>(constGet(have,"refresh"));
}
+ if(have.count("maxReceivedMBytes")) {
+ maxReceivedXFRMBytes = static_cast<size_t>(boost::get<int>(constGet(have,"maxReceivedMBytes")));
+ }
}
ComboAddress master(master_, 53);
DNSName zone(zone_);
- auto sr=loadRPZFromServer(master, zone, lci.dfe, polName, defpol, 0, tt);
+ auto sr=loadRPZFromServer(master, zone, lci.dfe, polName, defpol, 0, tt, maxReceivedXFRMBytes * 1024 * 1024);
if(refresh)
sr->d_st.refresh=refresh;
- std::thread t(RPZIXFRTracker, master, zone, polName, tt, sr);
+ std::thread t(RPZIXFRTracker, master, zone, polName, tt, sr, maxReceivedXFRMBytes * 1024 * 1024);
t.detach();
}
catch(std::exception& e) {
MANPAGES=pdns_recursor.1 \
rec_control.1
-dist_man_MANS=$(MANPAGES)
+if HAVE_PANDOC
+ dist_man_MANS=$(MANPAGES)
+endif
+if HAVE_MANPAGES
+ dist_man_MANS=$(MANPAGES)
+endif
if HAVE_PANDOC
$(MANPAGES): %: %.md
$(AM_V_GEN)$(PANDOC) -s -t man $< -o $@
else
-if HAVE_MANPAGES
-#nothing
-else
$(MANPAGES):
echo "You need pandoc to generate the manpages"
exit 1
endif
-endif
if HAVE_SYSTEMD
pdns-recursor.service: pdns-recursor.service.in
}
-void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::string& polName, const TSIGTriplet& tt, shared_ptr<SOARecordContent> oursr)
+void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::string& polName, const TSIGTriplet& tt, shared_ptr<SOARecordContent> oursr, size_t maxReceivedBytes)
{
int refresh = oursr->d_st.refresh;
for(;;) {
L<<Logger::Info<<"Getting IXFR deltas for "<<zone<<" from "<<master.toStringWithPort()<<", our serial: "<<getRR<SOARecordContent>(dr)->d_st.serial<<endl;
vector<pair<vector<DNSRecord>, vector<DNSRecord> > > deltas;
try {
- deltas = getIXFRDeltas(master, zone, dr, tt);
+ deltas = getIXFRDeltas(master, zone, dr, tt, nullptr, maxReceivedBytes);
} catch(std::runtime_error& e ){
L<<Logger::Warning<<e.what()<<endl;
continue;
AXFRRetriever::AXFRRetriever(const ComboAddress& remote,
const DNSName& domain,
const TSIGTriplet& tt,
- const ComboAddress* laddr)
- : d_tt(tt), d_tsigPos(0), d_nonSignedMessages(0)
+ const ComboAddress* laddr,
+ size_t maxReceivedBytes)
+ : d_tt(tt), d_receivedBytes(0), d_maxReceivedBytes(maxReceivedBytes), d_tsigPos(0), d_nonSignedMessages(0)
{
ComboAddress local;
if (laddr != NULL) {
int len=getLength();
if(len<0)
throw ResolverException("EOF trying to read axfr chunk from remote TCP client");
-
- timeoutReadn(len);
+
+ if (d_maxReceivedBytes > 0 && (d_maxReceivedBytes - d_receivedBytes) < (size_t) len)
+ throw ResolverException("Reached the maximum number of received bytes during AXFR");
+
+ timeoutReadn(len);
+
+ d_receivedBytes += (uint16_t) len;
+
MOADNSParser mdp(d_buf.get(), len);
int err;
AXFRRetriever(const ComboAddress& remote,
const DNSName& zone,
const TSIGTriplet& tt = TSIGTriplet(),
- const ComboAddress* laddr = NULL);
- ~AXFRRetriever();
+ const ComboAddress* laddr = NULL,
+ size_t maxReceivedBytes=0);
+ ~AXFRRetriever();
int getChunk(Resolver::res_t &res, vector<DNSRecord>* records=0);
private:
TSIGTriplet d_tt;
string d_prevMac; // RFC2845 4.4
string d_signData;
+ size_t d_receivedBytes;
+ size_t d_maxReceivedBytes;
uint32_t d_tsigPos;
uint d_nonSignedMessages; // RFC2845 4.4
TSIGRecordContent d_trc;
if (! ::arg().mustDo("dnsupdate"))
return RCode::Refused;
- string msgPrefix="UPDATE (" + itoa(p->d.id) + ") from " + p->getRemote() + " for " + p->qdomain.toLogString() + ": ";
+ string msgPrefix="UPDATE (" + itoa(p->d.id) + ") from " + p->getRemote().toString() + " for " + p->qdomain.toLogString() + ": ";
L<<Logger::Info<<msgPrefix<<"Processing started."<<endl;
// Check permissions - IP based
}
}
-shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zone, DNSFilterEngine& target, const std::string& polName, boost::optional<DNSFilterEngine::Policy> defpol, int place, const TSIGTriplet& tt)
+shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zone, DNSFilterEngine& target, const std::string& polName, boost::optional<DNSFilterEngine::Policy> defpol, int place, const TSIGTriplet& tt, size_t maxReceivedBytes)
{
L<<Logger::Warning<<"Loading RPZ zone '"<<zone<<"' from "<<master.toStringWithPort()<<endl;
if(!tt.name.empty())
L<<Logger::Warning<<"With TSIG key '"<<tt.name<<"' of algorithm '"<<tt.algo<<"'"<<endl;
ComboAddress local= master.sin4.sin_family == AF_INET ? ComboAddress("0.0.0.0") : ComboAddress("::"); // should be configurable
- AXFRRetriever axfr(master, zone, tt, &local);
+ AXFRRetriever axfr(master, zone, tt, &local, maxReceivedBytes);
unsigned int nrecords=0;
Resolver::res_t nop;
vector<DNSRecord> chunk;
#include "dnsrecords.hh"
int loadRPZFromFile(const std::string& fname, DNSFilterEngine& target, const std::string& policyName, boost::optional<DNSFilterEngine::Policy> defpol, int place);
-std::shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zone, DNSFilterEngine& target, const std::string& policyName, boost::optional<DNSFilterEngine::Policy> defpol, int place, const TSIGTriplet& tt);
+std::shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zone, DNSFilterEngine& target, const std::string& policyName, boost::optional<DNSFilterEngine::Policy> defpol, int place, const TSIGTriplet& tt, size_t maxReceivedBytes);
void RPZRecordToPolicy(const DNSRecord& dr, DNSFilterEngine& target, const std::string& policyName, bool addOrRemove, boost::optional<DNSFilterEngine::Policy> defpol, int place);
-void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::string& policyName, const TSIGTriplet &tt, shared_ptr<SOARecordContent> oursr);
+void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::string& policyName, const TSIGTriplet &tt, shared_ptr<SOARecordContent> oursr, size_t maxReceivedBytes);
DNSRecord dr;
dr.d_content = std::make_shared<SOARecordContent>(DNSName("."), DNSName("."), st);
- auto deltas = getIXFRDeltas(remote, domain, dr, tt, laddr.sin4.sin_family ? &laddr : 0);
+ auto deltas = getIXFRDeltas(remote, domain, dr, tt, laddr.sin4.sin_family ? &laddr : 0, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
zs.numDeltas=deltas.size();
// cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, scoped_ptr<AuthLua>& pdl, ZoneStatus& zs)
{
vector<DNSResourceRecord> rrs;
- AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? NULL : &laddr);
+ AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? NULL : &laddr, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
Resolver::res_t recs;
bool first=true;
bool firstNSEC3{true};
{
s_queries++;
d_wasVariable=false;
+ d_wasOutOfBand=false;
if( (qtype.getCode() == QType::AXFR))
return -1;
else
dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::A, 1, "127.0.0.1"));
ret.push_back(dr);
+ d_wasOutOfBand=true;
return 0;
}
dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::TXT, 3, "\""+s_serverID+"\""));
ret.push_back(dr);
+ d_wasOutOfBand=true;
return 0;
}
const vector<ComboAddress>& servers = iter->second.d_servers;
if(servers.empty()) {
ret.clear();
- doOOBResolve(qname, qtype, ret, depth, res);
+ d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, res);
return res;
}
else {
}
}
- if(doCNAMECacheCheck(qname,qtype,ret,depth,res)) // will reroute us if needed
+ if(qtype != QType::DS && doCNAMECacheCheck(qname,qtype,ret,depth,res)) // will reroute us if needed
return res;
if(doCacheCheck(qname,qtype,ret,depth,res)) // we done
LWResult lwr;
if(tns->empty() && nameservers[*tns].first.empty() ) {
LOG(prefix<<qname<<": Domain is out-of-band"<<endl);
- doOOBResolve(qname, qtype, lwr.d_records, depth, lwr.d_rcode);
+ d_wasOutOfBand = doOOBResolve(qname, qtype, lwr.d_records, depth, lwr.d_rcode);
lwr.d_tcbit=false;
lwr.d_aabit=true;
}
return d_wasVariable;
}
+ bool wasOutOfBand() const
+ {
+ return d_wasOutOfBand;
+ }
+
int asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res);
static void doEDNSDumpAndClose(int fd);
bool d_doDNSSEC;
bool d_wasVariable{false};
+ bool d_wasOutOfBand{false};
typedef multi_index_container <
NegCacheEntry,
pthread_mutex_t TCPNameserver::s_plock = PTHREAD_MUTEX_INITIALIZER;
Semaphore *TCPNameserver::d_connectionroom_sem;
PacketHandler *TCPNameserver::s_P;
-int TCPNameserver::s_timeout;
NetmaskGroup TCPNameserver::d_ng;
void TCPNameserver::go()
if(logDNSQueries) {
string remote;
if(packet->hasEDNSSubnet())
- remote = packet->getRemote() + "<-" + packet->getRealRemote().toString();
+ remote = packet->getRemote().toString() + "<-" + packet->getRealRemote().toString();
else
- remote = packet->getRemote();
+ remote = packet->getRemote().toString();
L << Logger::Notice<<"TCP Remote "<< remote <<" wants '" << packet->qdomain<<"|"<<packet->qtype.getName() <<
"', do = " <<packet->d_dnssecOk <<", bufsize = "<< packet->getMaxReplyLen()<<": ";
}
vector<string> nsips=fns.lookup(j, B);
for(vector<string>::const_iterator k=nsips.begin();k!=nsips.end();++k) {
// cerr<<"got "<<*k<<" from AUTO-NS"<<endl;
- if(*k == q->getRemote())
+ if(*k == q->getRemote().toString())
{
// cerr<<"got AUTO-NS hit"<<endl;
L<<Logger::Warning<<"AXFR of domain '"<<q->qdomain<<"' allowed: client IP "<<q->getRemote()<<" is in NSset"<<endl;
extern CommunicatorClass Communicator;
- if(Communicator.justNotified(q->qdomain, q->getRemote())) { // we just notified this ip
+ if(Communicator.justNotified(q->qdomain, q->getRemote().toString())) { // we just notified this ip
L<<Logger::Warning<<"Approved AXFR of '"<<q->qdomain<<"' from recently notified slave "<<q->getRemote()<<endl;
return true;
}
// sem_init(&d_connectionroom_sem,0,::arg().asNum("max-tcp-connections"));
d_connectionroom_sem = new Semaphore( ::arg().asNum( "max-tcp-connections" ));
d_tid=0;
- s_timeout=10;
vector<string>locals;
stringtok(locals,::arg()["local-address"]," ,");
vector<int>d_sockets;
vector<struct pollfd> d_prfds;
- static int s_timeout;
};
#endif /* PDNS_TCPRECEIVER_HH */
return state;
}
+/*
+ * This inline possibly sets currentState based on the new state. It will only
+ * set it to Secure iff the newState is Secure and mayUpgradeToSecure == true.
+ * This should be set by the calling function when checking more than one record
+ * and this is not the first record, this way, we can never go *back* to Secure
+ * from an Insecure vState
+ */
+inline void processNewState(vState& currentState, const vState& newState, bool& hadNTA, const bool& mayUpgradeToSecure)
+{
+ if (mayUpgradeToSecure && newState == Secure)
+ currentState = Secure;
+
+ if (newState == Insecure || newState == NTA) // We can never go back to Secure
+ currentState = Insecure;
+
+ if (newState == NTA)
+ hadNTA = true;
+}
+
vState validateRecords(const vector<DNSRecord>& recs)
{
if(recs.empty())
SRRecordOracle sro;
vState state=Insecure;
+ bool hadNTA = false;
if(numsigs) {
+ bool first = true;
for(const auto& csp : cspmap) {
for(const auto& sig : csp.second.signatures) {
- state = getKeysFor(sro, sig->d_signer, keys); // XXX check validity here
- if(state == NTA) {
- increaseDNSSECStateCounter(state);
- return Insecure;
- }
+ vState newState = getKeysFor(sro, sig->d_signer, keys); // XXX check validity here
+
+ if (newState == Bogus) // No hope
+ return increaseDNSSECStateCounter(Bogus);
+
+ processNewState(state, newState, hadNTA, first);
+
+ first = false;
+
LOG("! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys"<<endl);
for(const auto& k : keys) {
LOG("Key: "<<k.getZoneRepresentation()<< " {tag="<<k.getTag()<<"}"<<endl);
}
- // this sort of charges on and 'state' ends up as the last thing to have been checked
- // maybe not the right idea
}
}
- if(state == Bogus)
- return increaseDNSSECStateCounter(state);
validateWithKeySet(cspmap, validrrsets, keys);
}
else {
LOG("! no sigs, hoping for Insecure status of "<<recs.begin()->d_name<<endl);
- state = getKeysFor(sro, recs.begin()->d_name, keys); // um WHAT DOES THIS MEAN - try first qname??
-
- LOG("! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys "<<endl);
-
+
+ bool first = true;
+ for(const auto& rec : recs) {
+ vState newState = getKeysFor(sro, rec.d_name, keys);
+
+ if (newState == Bogus) // We're done
+ return increaseDNSSECStateCounter(Bogus);
+
+ processNewState(state, newState, hadNTA, first);
+ first = false;
+
+ LOG("! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys "<<endl);
+ }
return increaseDNSSECStateCounter(state);
}
-
+
LOG("Took "<<sro.d_queries<<" queries"<<endl);
if(validrrsets.size() == cspmap.size())// shortcut - everything was ok
return increaseDNSSECStateCounter(Secure);
- if(keys.empty())
+ if(state == Insecure || keys.empty()) {
+ if (hadNTA) {
+ increaseDNSSECStateCounter(NTA);
+ return Insecure;
+ }
return increaseDNSSECStateCounter(Insecure);
+ }
#if 0
cerr<<"! validated "<<validrrsets.size()<<" RRsets out of "<<cspmap.size()<<endl;
return increaseDNSSECStateCounter(Bogus);
}
}
-
return increaseDNSSECStateCounter(Insecure);
}
}
vector<string> labels = zone.getRawLabels();
- vState state;
-
- state = Indeterminate;
typedef std::multimap<uint16_t, DSRecordContent> dsmap_t;
dsmap_t dsmap;
keyset_t validkeys;
DNSName qname = lowestTA;
- state = Secure; // the lowest Trust Anchor is secure
+ vState state = Secure; // the lowest Trust Anchor is secure
while(zone.isPartOf(qname))
{
for(const auto& v : validrrsets) {
LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
- if(v.first.second==QType::NSEC) { // check that it covers us!
+ if(v.first.second==QType::CNAME) {
+ LOG("Found CNAME for "<< v.first.first << ", ignoring records at this level."<<endl);
+ goto skipLevel;
+ }
+ else if(v.first.second==QType::NSEC) { // check that it covers us!
for(const auto& r : v.second.records) {
LOG("\t"<<r->getZoneRepresentation()<<endl);
auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r);
if(nsec) {
- if(v.first.first == qname && !nsec->d_set.count(QType::DS))
+ if(v.first.first == qname && !nsec->d_set.count(QType::DS)) {
+ LOG("Denies existence of DS!"<<endl);
return Insecure;
+ }
else if(v.first.first.canonCompare(qname) && qname.canonCompare(nsec->d_next) ) {
LOG("Did not find DS for this level, trying one lower"<<endl);
goto skipLevel;
}
}
catch(std::exception& ae) {
- throw ApiException("An error occured while parsing the zonedata: "+string(ae.what()));
+ throw ApiException("An error occurred while parsing the zonedata: "+string(ae.what()));
}
}
(_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
self.assertEquals(receivedResponse, expectedResponse)
+class TestAdvancedLabelsCountRule(DNSDistTest):
+
+ _config_template = """
+ addAction(QNameLabelsCountRule(5,6), RCodeAction(dnsdist.REFUSED))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testAdvancedLabelsCountRule(self):
+ """
+ Advanced: QNameLabelsCountRule(5,6)
+ """
+ # 6 labels, we should be fine
+ name = 'ok.labelscount.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ # 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')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ # less than 5 labels, the query should be refused
+ name = 'labelscountadvanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+class TestAdvancedWireLengthRule(DNSDistTest):
+
+ _config_template = """
+ addAction(QNameWireLengthRule(54,56), RCodeAction(dnsdist.REFUSED))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testAdvancedWireLengthRule(self):
+ """
+ Advanced: QNameWireLengthRule(54,56)
+ """
+ name = 'longenough.qnamewirelength.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ # too short, the query should be refused
+ name = 'short.qnamewirelength.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ # too long, the query should be refused
+ name = 'toolongtobevalid.qnamewirelength.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
insecure-formerr.example. 3600 IN NS ns1.insecure-formerr.example.
ns1.insecure-formerr.example. 3600 IN A {prefix}.2
+
+islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
+ns1.islandofsecurity.example. 3600 IN A {prefix}.9
""",
'secure.example': """
secure.example. 3600 IN SOA {soa}
cname.secure.example. 3600 IN CNAME host1.secure.example.
cname-to-insecure.secure.example. 3600 IN CNAME node1.insecure.example.
cname-to-bogus.secure.example. 3600 IN CNAME ted.bogus.example.
+cname-to-islandofsecurity.secure.example. 3600 IN CNAME node1.islandofsecurity.example.
host1.sub.secure.example. 3600 IN A 192.0.2.11
+;; See #4158
+sub2.secure.example. 3600 IN CNAME doesnotmatter.insecure.example.
+insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
+
*.wildcard.secure.example. 3600 IN A 192.0.2.10
*.cnamewildcard.secure.example. 3600 IN CNAME host1.secure.example.
ns1.bogus.example. 3600 IN A {prefix}.12
ted.bogus.example. 3600 IN A 192.0.2.1
bill.bogus.example. 3600 IN AAAA 2001:db8:12::3
+ """,
+ 'insecure.sub2.secure.example': """
+insecure.sub2.secure.example. 3600 IN SOA {soa}
+insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
+
+node1.insecure.sub2.secure.example. 3600 IN A 192.0.2.18
""",
'insecure.example': """
insecure.example. 3600 IN SOA {soa}
ns1.secure.optout.example. 3600 IN A {prefix}.15
node1.secure.optout.example. 3600 IN A 192.0.2.8
+ """,
+ 'islandofsecurity.example': """
+islandofsecurity.example. 3600 IN SOA {soa}
+islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
+ns1.islandofsecurity.example. 3600 IN A {prefix}.9
+
+node1.islandofsecurity.example. 3600 IN A 192.0.2.20
"""
}
Private-key-format: v1.2
Algorithm: 13 (ECDSAP256SHA256)
PrivateKey: xcNUxt1Knj14A00lKQFDboluiJyM2f7FxpgsQaQ3AQ4=
+ """,
+
+ 'islandofsecurity.example': """
+Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: o9F5iix8V68tnMcuOaM2Lt8XXhIIY//SgHIHEePk6cM=
"""
}
# go into the _zones's zonecontent
_auth_zones = {
'8': ['ROOT'],
- '9': ['secure.example'],
+ '9': ['secure.example', 'islandofsecurity.example'],
'10': ['example'],
'11': ['example'],
'12': ['bogus.example'],
- '13': ['insecure.example'],
+ '13': ['insecure.example', 'insecure.sub2.secure.example'],
'14': ['optout.example'],
'15': ['insecure.optout.example', 'secure.optout.example']
}
import os
import socket
-#import unittest
import dns
from recursortests import RecursorTest
expected = dns.rrset.from_text('ns1.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.10'.format(prefix=self._PREFIX))
res = self.sendUDPQuery(msg, 'process')
- self.assertMessageHasFlags(res, ['QR', 'RA', 'RD'], ['DO'])
+ self.assertMessageHasFlags(res, ['AD', 'QR', 'RA', 'RD'], ['DO'])
self.assertMatchingRRSIGInAnswer(res, expected)
def testValidate_Secure_DO(self):
expected = dns.rrset.from_text('ns1.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.10'.format(prefix=self._PREFIX))
res = self.sendUDPQuery(msg, 'validate')
- self.assertMessageHasFlags(res, ['QR', 'RA', 'RD'], ['DO'])
+ self.assertMessageHasFlags(res, ['AD', 'QR', 'RA', 'RD'], ['DO'])
self.assertMatchingRRSIGInAnswer(res, expected)
##
expected = dns.rrset.from_text('ns1.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.10'.format(prefix=self._PREFIX))
res = self.sendUDPQuery(msg, 'process')
- self.assertMessageHasFlags(res, ['QR', 'RA', 'RD', 'CD'], ['DO'])
+ self.assertMessageHasFlags(res, ['AD', 'QR', 'RA', 'RD', 'CD'], ['DO'])
self.assertMatchingRRSIGInAnswer(res, expected)
def testValidate_Secure_DOCD(self):
expected = dns.rrset.from_text('ns1.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.10'.format(prefix=self._PREFIX))
res = self.sendUDPQuery(msg, 'validate')
- self.assertMessageHasFlags(res, ['QR', 'RA', 'RD', 'CD'], ['DO'])
+ self.assertMessageHasFlags(res, ['AD', 'QR', 'RA', 'RD', 'CD'], ['DO'])
self.assertMatchingRRSIGInAnswer(res, expected)
##
expected = dns.rrset.from_text('ted.bogus.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1')
res = self.sendUDPQuery(msg, 'process')
- self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertMessageHasFlags(res, ['QR', 'RA', 'RD'], ['DO'])
- self.assertMatchingRRSIGInAnswer(res, expected)
+ self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+ self.assertAnswerEmpty(res)
def testValidate_Bogus_DO(self):
msg = self.getQueryForBogus('', 'DO')
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertRRsetInAnswer(res, expected)
+ def testCNAMEWithLowerEntries(self):
+ """
+ #4158, When chasing down for DS/DNSKEY and we find a CNAME, skip a level
+ """
+ expected = dns.rrset.from_text('node1.insecure.sub2.secure.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.18')
+
+ query = dns.message.make_query('node1.insecure.sub2.secure.example.', 'A')
+ query.flags |= dns.flags.AD
+ res = self.sendUDPQuery(query)
+
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertMessageHasFlags(res, ['QR', 'RA', 'RD'], ['DO'])
+ self.assertRRsetInAnswer(res, expected)
+
@classmethod
def startResponders(cls):
print("Launching responders..")
import dns
+import os
from recursortests import RecursorTest
class testSimple(RecursorTest):
_confdir = 'Simple'
- _config_template = """dnssec=validate"""
+ _config_template = """dnssec=validate
+auth-zones=authzone.example=configs/%s/authzone.zone""" % _confdir
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ authzonepath = os.path.join(confdir, 'authzone.zone')
+ with open(authzonepath, 'w') as authzone:
+ authzone.write("""$ORIGIN authzone.example.
+@ 3600 IN SOA {soa}
+@ 3600 IN A 192.0.2.88
+""".format(soa=cls._SOA))
+ super(testSimple, cls).generateRecursorConfig(confdir)
def testSOAs(self):
for zone in ['.', 'example.', 'secure.example.']:
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+
+ def testAuthZone(self):
+ query = dns.message.make_query('authzone.example', 'A', want_dnssec=True)
+
+ expectedA = dns.rrset.from_text('authzone.example.', 0, 'IN', 'A', '192.0.2.88')
+
+ res = self.sendUDPQuery(query)
+
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expectedA)
+
+ def testLocalhost(self):
+ queryA = dns.message.make_query('localhost', 'A', want_dnssec=True)
+ expectedA = dns.rrset.from_text('localhost.', 0, 'IN', 'A', '127.0.0.1')
+
+ queryPTR = dns.message.make_query('1.0.0.127.in-addr.arpa', 'PTR', want_dnssec=True)
+ expectedPTR = dns.rrset.from_text('1.0.0.127.in-addr.arpa.', 0, 'IN', 'PTR', 'localhost.')
+
+ resA = self.sendUDPQuery(queryA)
+ resPTR = self.sendUDPQuery(queryPTR)
+
+ self.assertRcodeEqual(resA, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(resA, expectedA)
+
+ self.assertRcodeEqual(resPTR, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(resPTR, expectedPTR)
+
+ def testIslandOfSecurity(self):
+ query = dns.message.make_query('cname-to-islandofsecurity.secure.example.', 'A', want_dnssec=True)
+
+ expectedCNAME = dns.rrset.from_text('cname-to-islandofsecurity.secure.example.', 0, 'IN', 'CNAME', 'node1.islandofsecurity.example.')
+ expectedA = dns.rrset.from_text('node1.islandofsecurity.example.', 0, 'IN', 'A', '192.0.2.20')
+
+ res = self.sendUDPQuery(query)
+
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expectedA)
+