Solaris Notes
-------------
-Use a recent gcc. OpenCSW is a good source, as is Solaris 11 IPS.
+Use a recent gcc (and other build tools), possibly from Solaris 11 IPS.
If you encounter problems with the Solaris make, gmake is advised.
* Module name: geoip
* Launch name: ``geoip``
-This backend allows visitors to be sent to a server closer to them, with
+This backend (which is a.k.a. the YAML backend) allows visitors to be sent to a server closer to them, with
no appreciable delay, as would otherwise be incurred with a protocol
level redirect. Additionally, the Geo Backend can be used to provide
service over several clusters, any of which can be taken out of use
To compile the backend, you need libyaml-cpp 0.5 or later and libgeoip.
-You must have geoip database available. As of writing, on debian/ubuntu
-systems, you can use apt-get install geoip-database to get one, and the
+You must have a geoip database available. As of this writing, on debian/ubuntu
+systems, you can use ``apt-get install geoip-database`` to get one, and the
backend is configured to use the location where these files are
installed as source. On other systems you might need to alter the
-database-file and database-file6 attribute. If you don't need ipv4 or
+``database-file`` and ``database-file6`` attribute. If you don't need ipv4 or
ipv6 support, set the respective setting to "". Leaving it unset leaves
-it pointing to default location, preventing the software from starting
+it pointing to a default location, preventing the software from starting
up.
Since v4.2.0 libgeoip is optional. You can use also libmaxminddb, but
------------------------
These are the configuration file parameters that are available for the
-GeoIP backend. geoip-zones-files is the only thing you must set, if the
-defaults suite you.
+GeoIP backend. ``geoip-zones-files`` is the only thing you must set, if the
+defaults suit you.
.. _setting-geoip-database-files:
For MMDB files, see `MaxMind's getting started guide <https://github.com/maxmind/getting-started-with-mmdb>`__.
-Since v4.2.0, database type is determined by file suffix, or you can use new syntax.
+Since v4.2.0, database type is determined by file suffix, or you can use the new syntax.
New syntax is ``[driver:]path[;options]``.
Drivers and options
Zonefile format
---------------
-Zone configuration file uses YAML syntax. Here is simple example. Note
+Zone configuration files use YAML syntax. Here is simple example. Note
that the ``‐`` before certain keys is part of the syntax.
.. code-block:: yaml
Changelogs for 4.0.x
====================
+PowerDNS Authoritative Server 4.0.9
+-----------------------------------
+
+Released 1st of August 2019
+
+This release contains the updated PostgreSQL schema for PowerDNS Security Advisory :doc:`2019-06 <../security-advisories/powerdns-advisory-2019-06>` (CVE-2019-10203).
+
+Upgrading is not enough - you need to manually apply the schema change: ``ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;``
+
PowerDNS Authoritative Server 4.0.8
-----------------------------------
Changelogs for 4.1.x
====================
+.. changelog::
+ :version: 4.1.11
+ :released: August 1st 2019
+
+ This release contains the updated PostgreSQL schema for PowerDNS Security Advisory :doc:`2019-06 <../security-advisories/powerdns-advisory-2019-06>` (CVE-2019-10203).
+
+ Upgrading is not enough - you need to manually apply the schema change: ``ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;``
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8144
+
+ Update PostgreSQL schema for 2019-06.
+
.. changelog::
:version: 4.1.10
:released: June 21st 2019
^^^^^^^^^^^^^^^
Number of entries in the metadata cache
+.. _stat-open-tcp-connections:
+
+open-tcp-connections
+~~~~~~~~~~~~~~~~~~~~
+Number of currently open TCP connections
+
.. _stat-overload-drops:
overload-drops
-@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019071601 10800 3600 604800 10800
+@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019080101 10800 3600 604800 10800
@ 3600 IN NS pdns-public-ns1.powerdns.com.
@ 3600 IN NS pdns-public-ns2.powerdns.com.
@ 3600 IN NS tmpdns.powerdns.com.
auth-4.0.6.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
auth-4.0.7.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2018-03.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2018-05.html"
auth-4.0.8.security-status 60 IN TXT "1 OK"
+auth-4.0.9.security-status 60 IN TXT "1 OK"
auth-4.1.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
auth-4.1.0-rc2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
auth-4.1.0-rc3.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
auth-4.1.8.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-04.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-05.html"
auth-4.1.9.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-04.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-05.html"
auth-4.1.10.security-status 60 IN TXT "1 OK"
+auth-4.1.11.security-status 60 IN TXT "1 OK"
auth-4.2.0-alpha1.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
auth-4.2.0-beta1.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
auth-4.2.0-rc1.security-status 60 IN TXT "1 OK"
--- /dev/null
+PowerDNS Security Advisory 2019-06: Denial of service via crafted zone records
+==============================================================================
+
+- CVE: CVE-2019-10203
+- Date: July 30th, 2019
+- Affects: PowerDNS Authoritative 4.0.0 and up, when using the gpgsql (PostgreSQL) backend
+- Not affected: 4.2.0, 4.1.11, 4.0.9
+- Severity: Low
+- Impact: Denial of Service
+- Exploit: This problem can be triggered via crafted records
+- Risk of system compromise: No
+- Solution: Update the database schema
+- Workaround: run the process inside the guardian or inside a supervisor
+
+An issue has been found in PowerDNS Authoritative Server allowing an
+authorized user to cause the server to exit by inserting a crafted record in a
+MASTER type zone under their control. The issue is due to the fact that the
+Authoritative Server will exit when it tries to store the notified serial in
+the PostgreSQL database, if this serial cannot be represented in 31 bits.
+
+This issue has been assigned CVE-2019-10203.
+
+PowerDNS Authoritative up to and including 4.1.10 is affected. Please note
+that at the time of writing, PowerDNS Authoritative 3.4 and below are no
+longer supported, as described in
+https://doc.powerdns.com/authoritative/appendices/EOL.html.
+
+To fix the issue, run the following command against your PostgreSQL pdns
+database: `ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE
+WHEN notified_serial >= 0 THEN notified_serial::bigint END;`. No software
+changes are required.
+
+We would like to thank Klaus Darilion for finding and subsequently reporting
+this issue!
- Superslave operation is no longer enabled by default, use :ref:`setting-superslave` to enable. This setting was called ``supermaster`` in some 4.2.0 prereleases.
- The gsqlite3 backend, and the DNSSEC database for the BIND backend, have a new journal-mode setting. This setting defaults to `WAL <https://www.sqlite.org/wal.html>`_; older versions of PowerDNS did not set the journal mode, which means they used the SQLite default of DELETE.
+- Autoserial support has been removed. The ``change_date`` column has been removed from the ``domains`` table in all gsql backends, but leaving it in is harmless.
+- The :doc:`Generic PostgreSQL backend <backends/generic-postgresql>` schema has changed: the ``notified_serial`` column type in the ``domains`` table has been changed from ``INT DEFAULT NULL`` to ``BIGINT DEFAULT NULL``: ``ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;``
4.1.0 to 4.1.1
--------------
+EXTRA_DIST = \
+ LICENSE
+
noinst_LTLIBRARIES = libipcrypt.la
libipcrypt_la_SOURCES = \
ALTER TABLE records DROP COLUMN change_date;
+ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type VARCHAR(6) NOT NULL,
- notified_serial INT DEFAULT NULL,
+ notified_serial BIGINT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);
if(!(d_fp=fdopen(d_fd2[0],"r")))
throw PDNSException("Unable to associate a file pointer with pipe: "+stringerror());
if( d_timeout)
- setbuf(d_fp,0); // no buffering please, confuses select
+ setbuf(d_fp,0); // no buffering please, confuses poll
}
else if(!d_pid) { // child
signal(SIGCHLD, SIG_DFL); // silence a warning from perl
receive.clear();
if(d_timeout) {
- struct timeval tv;
- tv.tv_sec=d_timeout/1000;
- tv.tv_usec=(d_timeout % 1000) * 1000;
-
- fd_set rds;
- FD_ZERO(&rds);
- FD_SET(fileno(d_fp),&rds);
- int ret=select(fileno(d_fp)+1,&rds,0,0,&tv);
+ int ret = waitForData(fileno(d_fp), 0, d_timeout * 1000);
if(ret<0)
throw PDNSException("Error waiting on data from coprocess: "+stringerror());
if(!ret)
if(!(d_fp=std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(d_fd2[0],"r"), fclose)))
throw PDNSException("Unable to associate a file pointer with pipe: "+stringerror());
if (d_timeout)
- setbuf(d_fp.get(),0); // no buffering please, confuses select
+ setbuf(d_fp.get(),0); // no buffering please, confuses poll
}
else if(!d_pid) { // child
signal(SIGCHLD, SIG_DFL); // silence a warning from perl
while(1) {
receive.clear();
if(d_timeout) {
- struct timeval tv;
- tv.tv_sec = d_timeout/1000;
- tv.tv_usec = (d_timeout % 1000) * 1000;
- fd_set rds;
- FD_ZERO(&rds);
- FD_SET(fileno(d_fp.get()),&rds);
- int ret=select(fileno(d_fp.get())+1,&rds,0,0,&tv);
+ int ret=waitForData(fileno(d_fp.get()), 0, d_timeout * 1000);
if(ret<0)
throw PDNSException("Error waiting on data from coprocess: "+stringerror());
if(!ret)
}
+static uint64_t getTCPConnectionCount(const std::string& str)
+{
+ return TN->numTCPConnections();
+}
+
static uint64_t getQCount(const std::string& str)
try
{
S.declare("tcp6-queries","Number of IPv6 TCP queries received");
S.declare("tcp6-answers","Number of IPv6 answers sent out over TCP");
-
+
+ S.declare("open-tcp-connections","Number of currently open TCP connections", getTCPConnectionCount);;
S.declare("qsize-q","Number of questions waiting for database attention", getQCount);
}
}
else
- cout << g_outputBuffer;
+ cout << g_outputBuffer << std::flush;
if(!getLuaNoSideEffect())
feedConfigDelta(line);
}
{ "clearDynBlocks", true, "", "clear all dynamic blocks" },
{ "clearQueryCounters", true, "", "clears the query counter buffer" },
{ "clearRules", true, "", "remove all current rules" },
+ { "ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action" },
{ "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
{ "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
{ "delta", true, "", "shows all commands entered that changed the configuration" },
return false;
}
size_t p = optStart + 9;
- uint16_t rdLen = (0x100*packet.at(p) + packet.at(p+1));
+ uint16_t rdLen = (0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1)));
p += sizeof(rdLen);
if (rdLen > (optLen - optRecordMinimumSize)) {
return false;
size_t rdEnd = p + rdLen;
while ((p + 4) <= rdEnd) {
- const uint16_t optionCode = 0x100*packet.at(p) + packet.at(p+1);
+ const uint16_t optionCode = 0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1));
p += sizeof(optionCode);
- const uint16_t optionLen = 0x100*packet.at(p) + packet.at(p+1);
+ const uint16_t optionLen = 0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1));
p += sizeof(optionLen);
if ((p + optionLen) > rdEnd) {
std::string d_value;
};
+class ContinueAction : public DNSAction
+{
+public:
+ ContinueAction(std::shared_ptr<DNSAction>& action): d_action(action)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+ {
+ if (d_action) {
+ /* call the action */
+ auto action = (*d_action)(dq, ruleresult);
+ bool drop = false;
+ /* apply the changes if needed (pool selection, flags, etc */
+ processRulesResult(action, *dq, *ruleresult, drop);
+ }
+
+ /* but ignore the resulting action no matter what */
+ return Action::None;
+ }
+
+ std::string toString() const override
+ {
+ if (d_action) {
+ return "continue after: " + (d_action ? d_action->toString() : "");
+ }
+ else {
+ return "no op";
+ }
+ }
+
+private:
+ std::shared_ptr<DNSAction> d_action;
+};
+
template<typename T, typename ActionT>
static void addAction(GlobalStateHolder<vector<T> > *someRulActions, luadnsrule_t var, std::shared_ptr<ActionT> action, boost::optional<luaruleparams_t> params) {
setLuaSideEffect();
g_lua.writeFunction("TagResponseAction", [](std::string tag, std::string value) {
return std::shared_ptr<DNSResponseAction>(new TagResponseAction(tag, value));
});
+
+ g_lua.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
+ return std::shared_ptr<DNSAction>(new ContinueAction(action));
+ });
}
g_noLuaSideEffect = boost::logic::indeterminate;
}
-typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> > > > localbind_t;
+typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> >, std::map<std::string,std::string> > > localbind_t;
static void parseLocalBindVars(boost::optional<localbind_t> vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus)
{
if (vars->count("serverTokens")) {
frontend->d_serverTokens = boost::get<const string>((*vars)["serverTokens"]);
}
+ if (vars->count("customResponseHeaders")) {
+ for (auto const& headerMap : boost::get<std::map<std::string,std::string>>((*vars)["customResponseHeaders"])) {
+ std::pair<std::string,std::string> headerResponse = std::make_pair(headerMap.first, headerMap.second);
+ frontend->d_customResponseHeaders.push_back(headerResponse);
+ }
+ }
}
g_dohlocals.push_back(frontend);
auto cs = std::unique_ptr<ClientState>(new ClientState(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus));
static void handleResponse(std::shared_ptr<IncomingTCPConnectionState>& state, struct timeval& now)
{
- if (state->d_responseSize < sizeof(dnsheader)) {
+ if (state->d_responseSize < sizeof(dnsheader) || !state->d_ds) {
return;
}
state->d_currentPos = 0;
state->d_querySentTime = now;
iostate = IOState::NeedRead;
- if (!state->d_isXFR) {
+ if (!state->d_isXFR && !state->d_outstanding) {
/* don't bother with the outstanding count for XFR queries */
++state->d_ds->outstanding;
state->d_outstanding = true;
if (state->d_downstreamConnection && state->d_downstreamConnection->isFresh()) {
++state->d_downstreamFailures;
}
- if (state->d_outstanding && state->d_ds != nullptr) {
- --state->d_ds->outstanding;
+
+ if (state->d_outstanding) {
state->d_outstanding = false;
+
+ if (state->d_ds != nullptr) {
+ --state->d_ds->outstanding;
+ }
}
/* remove this FD from the IO multiplexer */
iostate = IOState::Done;
}
}
-static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, string& poolname, const struct timespec& now)
+bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop)
+{
+ switch(action) {
+ case DNSAction::Action::Allow:
+ return true;
+ break;
+ case DNSAction::Action::Drop:
+ ++g_stats.ruleDrop;
+ drop = true;
+ return true;
+ break;
+ case DNSAction::Action::Nxdomain:
+ dq.dh->rcode = RCode::NXDomain;
+ dq.dh->qr=true;
+ ++g_stats.ruleNXDomain;
+ return true;
+ break;
+ case DNSAction::Action::Refused:
+ dq.dh->rcode = RCode::Refused;
+ dq.dh->qr=true;
+ ++g_stats.ruleRefused;
+ return true;
+ break;
+ case DNSAction::Action::ServFail:
+ dq.dh->rcode = RCode::ServFail;
+ dq.dh->qr=true;
+ ++g_stats.ruleServFail;
+ return true;
+ break;
+ case DNSAction::Action::Spoof:
+ spoofResponseFromString(dq, ruleresult);
+ return true;
+ break;
+ case DNSAction::Action::Truncate:
+ dq.dh->tc = true;
+ dq.dh->qr = true;
+ return true;
+ break;
+ case DNSAction::Action::HeaderModify:
+ return true;
+ break;
+ case DNSAction::Action::Pool:
+ dq.poolname=ruleresult;
+ return true;
+ break;
+ case DNSAction::Action::NoRecurse:
+ dq.dh->rd = false;
+ return true;
+ break;
+ /* non-terminal actions follow */
+ case DNSAction::Action::Delay:
+ dq.delayMsec = static_cast<int>(pdns_stou(ruleresult)); // sorry
+ break;
+ case DNSAction::Action::None:
+ /* fall-through */
+ case DNSAction::Action::NoOp:
+ break;
+ }
+
+ /* false means that we don't stop the processing */
+ return false;
+}
+
+
+static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const struct timespec& now)
{
g_rings.insertQuery(now, *dq.remote, *dq.qname, dq.qtype, dq.len, *dq.dh);
DNSAction::Action action=DNSAction::Action::None;
string ruleresult;
+ bool drop = false;
for(const auto& lr : *holders.rulactions) {
if(lr.d_rule->matches(&dq)) {
lr.d_rule->d_matches++;
action=(*lr.d_action)(&dq, &ruleresult);
-
- switch(action) {
- case DNSAction::Action::Allow:
- return true;
- break;
- case DNSAction::Action::Drop:
- ++g_stats.ruleDrop;
- return false;
- break;
- case DNSAction::Action::Nxdomain:
- dq.dh->rcode = RCode::NXDomain;
- dq.dh->qr=true;
- ++g_stats.ruleNXDomain;
- return true;
- break;
- case DNSAction::Action::Refused:
- dq.dh->rcode = RCode::Refused;
- dq.dh->qr=true;
- ++g_stats.ruleRefused;
- return true;
- break;
- case DNSAction::Action::ServFail:
- dq.dh->rcode = RCode::ServFail;
- dq.dh->qr=true;
- ++g_stats.ruleServFail;
- return true;
- break;
- case DNSAction::Action::Spoof:
- spoofResponseFromString(dq, ruleresult);
- return true;
- break;
- case DNSAction::Action::Truncate:
- dq.dh->tc = true;
- dq.dh->qr = true;
- return true;
- break;
- case DNSAction::Action::HeaderModify:
- return true;
- break;
- case DNSAction::Action::Pool:
- poolname=ruleresult;
- return true;
- break;
- /* non-terminal actions follow */
- case DNSAction::Action::Delay:
- dq.delayMsec = static_cast<int>(pdns_stou(ruleresult)); // sorry
- break;
- case DNSAction::Action::None:
- /* fall-through */
- case DNSAction::Action::NoOp:
- break;
- case DNSAction::Action::NoRecurse:
- dq.dh->rd = false;
- return true;
+ if (processRulesResult(action, dq, ruleresult, drop)) {
break;
}
}
}
+ if (drop) {
+ return false;
+ }
+
return true;
}
struct timespec now;
gettime(&now);
- string poolname;
-
- if (!applyRulesToQuery(holders, dq, poolname, now)) {
+ if (!applyRulesToQuery(holders, dq, now)) {
return ProcessQueryResult::Drop;
}
return ProcessQueryResult::SendAnswer;
}
- std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, poolname);
+ std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dq.poolname);
dq.packetCache = serverPool->packetCache;
auto policy = *(holders.policy);
if (serverPool->policy != nullptr) {
Netmask ecs;
boost::optional<Netmask> subnet;
std::string sni; /* Server Name Indication, if any (DoT or DoH) */
+ std::string poolname;
const DNSName* qname{nullptr};
const ComboAddress* local{nullptr};
const ComboAddress* remote{nullptr};
bool responseContentMatches(const char* response, const uint16_t responseLen, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& remote, unsigned int& consumed);
bool processResponse(char** response, uint16_t* responseLen, size_t* responseSize, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, size_t addRoom, std::vector<uint8_t>& rewrittenResponse, bool muted);
+bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop);
bool checkQueryHeaders(const struct dnsheader* dh);
* ``ciphers``: str - The TLS ciphers to use, in OpenSSL format. Ciphers for TLS 1.3 must be specified via ``ciphersTLS13``.
* ``ciphersTLS13``: str - The TLS ciphers to use for TLS 1.3, in OpenSSL format.
* ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "h2o/dnsdist".
+ * ``customResponseHeaders={}``: table - Set custom HTTP header(s) returned by dnsdist.
.. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
Let these packets go through.
+.. function:: ContinueAction(action)
+
+ .. versionadded:: 1.4.0
+
+ Execute the specified action and override its return with None, making it possible to continue the processing.
+ Subsequent rules are processed after this action.
+
+ :param int action: Any other action
+
.. function:: DelayAction(milliseconds)
Delay the response by the specified amount of milliseconds (UDP-only).
Send the the current query to a remote logger as a :doc:`dnstap <reference/dnstap>` message.
``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
+ Subsequent rules are processed after this action.
:param string identity: Server identity to store in the dnstap message
:param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
Send the the current response to a remote logger as a :doc:`dnstap <reference/dnstap>` message.
``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
+ Subsequent rules are processed after this action.
:param string identity: Server identity to store in the dnstap message
:param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
``ipEncryptKey`` optional key added to the options table.
Send the content of this query to a remote logger via Protocol Buffer.
- ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes
+ ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
+ Subsequent rules are processed after this action.
:param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
:param string alterFunction: Name of a function to modify the contents of the logs before sending
``ipEncryptKey`` optional key added to the options table.
Send the content of this response to a remote logger via Protocol Buffer.
- ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes
+ ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
``includeCNAME`` indicates whether CNAME records inside the response should be parsed and exported.
- The default is to only exports A and AAAA records
+ The default is to only exports A and AAAA records.
+ Subsequent rules are processed after this action.
:param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
:param string alterFunction: Name of a function to modify the contents of the logs before sending
.. versionadded:: 1.3.0
Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
+ Subsequent rules are processed after this action.
:param string name: The name of the tag to set
:param string value: The value of the tag
.. versionadded:: 1.3.0
Associate a tag named ``name`` with a value of ``value`` to this response.
+ Subsequent rules are processed after this action.
:param string name: The name of the tag to set
:param string value: The value of the tag
return 0;
}
+ constexpr int overwrite_if_exists = 1;
+ constexpr int maybe_token = 1;
+ for (auto const& headerPair : dsc->df->d_customResponseHeaders) {
+ h2o_set_header_by_str(&req->pool, &req->res.headers, headerPair.first.c_str(), headerPair.first.size(), maybe_token, headerPair.second.c_str(), headerPair.second.size(), overwrite_if_exists);
+ }
+
if(auto tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
if(!strcmp(tlsversion, "TLSv1.0"))
++dsc->df->d_tls10queries;
std::string d_ciphers;
std::string d_ciphers13;
std::string d_serverTokens{"h2o/dnsdist"};
+ std::vector<std::pair<std::string, std::string>> d_customResponseHeaders;
ComboAddress d_local;
uint32_t d_idleTimeout{30}; // HTTP idle timeout in seconds
d_lw->registerFunction("getRawLabels", &DNSName::getRawLabels);
d_lw->registerFunction<unsigned int(DNSName::*)()>("countLabels", [](const DNSName& name) { return name.countLabels(); });
d_lw->registerFunction<size_t(DNSName::*)()>("wireLength", [](const DNSName& name) { return name.wirelength(); });
+ d_lw->registerFunction<size_t(DNSName::*)()>("wirelength", [](const DNSName& name) { return name.wirelength(); });
d_lw->registerFunction<bool(DNSName::*)(const std::string&)>("equal", [](const DNSName& lhs, const std::string& rhs) { return lhs==DNSName(rhs); });
d_lw->registerEqFunction(&DNSName::operator==);
d_lw->registerToStringFunction<string(DNSName::*)()>([](const DNSName&dn ) { return dn.toString(); });
cout<<"Note: test-schema will try to create the zone, but it will not remove it."<<endl;
cout<<"Please clean up after this."<<endl;
cout<<endl;
+ cout<<"If this test reports an error and aborts, please check your database schema."<<endl;
cout<<"Constructing UeberBackend"<<endl;
UeberBackend B("default");
cout<<"Picking first backend - if this is not what you want, edit launch line!"<<endl;
if(db->get(rrthrowaway)) // should not touch rr but don't assume anything
{
cout<<"Expected one record, got multiple, aborting"<<endl;
- return;
+ exit(EXIT_FAILURE);
}
int size=rrget.content.size();
if(size != 302)
{
cout<<"Expected 302 bytes, got "<<size<<", aborting"<<endl;
- return;
+ exit(EXIT_FAILURE);
}
}
cout<<"[+] content field is over 255 bytes"<<endl;
if(before != DNSName("_underscore")+zone)
{
cout<<"before is wrong, got '"<<before.toString()<<"', expected '_underscore."<<zone.toString()<<"', aborting"<<endl;
- return;
+ exit(EXIT_FAILURE);
}
if(after != zone)
{
cout<<"after is wrong, got '"<<after.toString()<<"', expected '"<<zone.toString()<<"', aborting"<<endl;
- return;
+ exit(EXIT_FAILURE);
}
cout<<"[+] ordername sorting is correct for names starting with _"<<endl;
+ cout<<"Setting low notified serial"<<endl;
+ db->setNotified(di.id, 500);
+ db->getDomainInfo(zone, di);
+ if(di.notified_serial != 500) {
+ cout<<"[-] Set serial 500, got back "<<di.notified_serial<<", aborting"<<endl;
+ exit(EXIT_FAILURE);
+ }
+ cout<<"Setting serial that needs 32 bits"<<endl;
+ try {
+ db->setNotified(di.id, 2147484148);
+ } catch(const PDNSException &pe) {
+ cout<<"While setting serial, got error: "<<pe.reason<<endl;
+ cout<<"aborting"<<endl;
+ exit(EXIT_FAILURE);
+ }
+ db->getDomainInfo(zone, di);
+ if(di.notified_serial != 2147484148) {
+ cout<<"[-] Set serial 2147484148, got back "<<di.notified_serial<<", aborting"<<endl;
+ exit(EXIT_FAILURE);
+ } else {
+ cout<<"[+] Big serials work correctly"<<endl;
+ }
cout<<endl;
cout<<"End of tests, please remove "<<zone<<" from domains+records"<<endl;
}
Maintenance callback
--------------------
-Starting with version 4.1.4 of the recursor, it is possible to define a `maintenance()` callback function that will be called periodically.
-This function expects no argument and doesn't return any value
+Starting with version 4.2.0 of the recursor, it is possible to define a `maintenance()` callback function that will be called periodically.
+This function expects no argument and doesn't return any value.
.. code-block:: Lua
``lua-maintenance-interval``
----------------------------
-.. versionadded:: 4.1.4
+.. versionadded:: 4.2.0
- Integer
- Default: 1
.. versionadded:: 4.2.0
- String
-- Default: "cache-bytes, packetcache-bytes, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
+- Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
These statistics can still be retrieved individually by specifically asking for it.
.. versionadded:: 4.2.0
- String
-- Default: "cache-bytes, packetcache-bytes, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
+- Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
A list of comma-separated statistic names, that are prevented from being exported via carbon for performance reasons.
.. versionadded:: 4.2.0
- String
-- Default: "cache-bytes, packetcache-bytes, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
+- Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
These statistics can still be retrieved individually.
.. versionadded:: 4.2.0
- String
-- Default: "cache-bytes, packetcache-bytes, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
+- Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
A list of comma-separated statistic names, that are prevented from being exported via SNMP, for performance reasons.
pthread_mutex_t TCPNameserver::s_plock = PTHREAD_MUTEX_INITIALIZER;
Semaphore *TCPNameserver::d_connectionroom_sem;
+unsigned int TCPNameserver::d_maxTCPConnections = 0;
PacketHandler *TCPNameserver::s_P;
NetmaskGroup TCPNameserver::d_ng;
size_t TCPNameserver::d_maxTransactionsPerConn;
// sem_init(&d_connectionroom_sem,0,::arg().asNum("max-tcp-connections"));
d_connectionroom_sem = new Semaphore( ::arg().asNum( "max-tcp-connections" ));
+ d_maxTCPConnections = ::arg().asNum( "max-tcp-connections" );
d_tid=0;
vector<string>locals;
stringtok(locals,::arg()["local-address"]," ,");
}
+unsigned int TCPNameserver::numTCPConnections()
+{
+ int room;
+ d_connectionroom_sem->getValue( &room);
+ return d_maxTCPConnections - room;
+}
TCPNameserver();
~TCPNameserver();
void go();
+ unsigned int numTCPConnections();
private:
static void sendPacket(std::shared_ptr<DNSPacket> p, int outsock);
static PacketHandler *s_P;
pthread_t d_tid;
static Semaphore *d_connectionroom_sem;
+ static unsigned int d_maxTCPConnections;
static NetmaskGroup d_ng;
static size_t d_maxTransactionsPerConn;
static size_t d_maxConnectionsPerClient;
ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
const size_t sizeOfECSContent = ecsOptionStr.size();
+ const size_t sizeOfECSOption = /* option code */ 2 + /* option length */ 2 + sizeOfECSContent;
EDNSCookiesOpt cookiesOpt;
cookiesOpt.client = string("deadbeef");
cookiesOpt.server = string("deadbeef");
query.resize(query.size() - 1);
BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
}
+
+ {
+ /* valid EDNS, one 65002 after an ECS */
+ vector<uint8_t> query;
+ DNSPacketWriter pw(query, qname, qtype, qclass, 0);
+ DNSPacketWriter::optvect_t opts;
+ opts.push_back(make_pair(EDNSOptionCode::ECS, ecsOptionStr));
+ opts.push_back(make_pair(65535, cookiesOptionStr));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+
+ bool found = locateEDNSOption(query, 65535, &optContentStart, &optContentLen);
+ BOOST_CHECK_EQUAL(found, true);
+ if (found == true) {
+ BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfECSOption + /* option code */ 2 + /* option length */ 2);
+ BOOST_CHECK_EQUAL(optContentLen, cookiesOptionStr.size());
+ }
+
+ /* truncated packet */
+ query.resize(query.size() - 1);
+ BOOST_CHECK_THROW(locateEDNSOption(query, 65002, &optContentStart, &optContentLen), std::range_error);
+ }
}
BOOST_AUTO_TEST_SUITE_END();
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertTrue(receivedResponse)
self.assertEquals(expectedResponse, receivedResponse)
+
+class TestAdvancedContinueAction(DNSDistTest):
+
+ _config_template = """
+ newServer{address="127.0.0.1:%s", pool="mypool"}
+ addAction("nocontinue.continue-action.advanced.tests.powerdns.com.", PoolAction("mypool"))
+ addAction("continue.continue-action.advanced.tests.powerdns.com.", ContinueAction(PoolAction("mypool")))
+ addAction(AllRule(), DisableValidationAction())
+ """
+
+ def testNoContinue(self):
+ """
+ Advanced: Query routed to pool, PoolAction should be terminal
+ """
+
+ name = 'nocontinue.continue-action.advanced.tests.powerdns.com.'
+
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedQuery = dns.message.make_query(name, 'A', 'IN')
+
+ response = dns.message.make_response(query)
+ expectedResponse = dns.message.make_response(query)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ self.assertEquals(receivedQuery, expectedQuery)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ def testNoContinue(self):
+ """
+ Advanced: Query routed to pool, ContinueAction() should not stop the processing
+ """
+
+ name = 'continue.continue-action.advanced.tests.powerdns.com.'
+
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedQuery = dns.message.make_query(name, 'A', 'IN')
+ expectedQuery.flags |= dns.flags.CD
+
+ response = dns.message.make_response(query)
+ expectedResponse = dns.message.make_response(query)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ expectedQuery.id = receivedQuery.id
+ self.assertEquals(receivedQuery, expectedQuery)
+ print(receivedResponse)
+ print(expectedResponse)
+ self.assertEquals(receivedResponse, expectedResponse)
raw = response.to_wire()
# first label length of this rrset is at 12 (dnsheader) + length(qname) + 2 (leading label length + trailing 0) + 2 (qtype) + 2 (qclass)
offset = 12 + len(str(request.question[0].name)) + 2 + 2 + 2
- altered = raw[:offset] + chr(255).encode() + raw[offset+1:]
+ altered = raw[:offset] + b'\xff' + raw[offset+1:]
return altered
class TestBrokenAnswerECS(DNSDistTest):
from dnsdisttests import DNSDistTest
import pycurl
-
+from io import BytesIO
#from hyper import HTTP20Connection
#from hyper.ssl_compat import SSLContext, PROTOCOL_TLSv1_2
def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, customHeaders=[]):
url = cls.getDOHGetURL(baseurl, query, rawQuery)
conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
+ response_headers = BytesIO()
#conn.setopt(pycurl.VERBOSE, True)
conn.setopt(pycurl.URL, url)
conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.SSL_VERIFYHOST, 2)
conn.setopt(pycurl.HTTPHEADER, customHeaders)
+ conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
if caFile:
conn.setopt(pycurl.CAINFO, caFile)
receivedQuery = None
message = None
+ cls._response_headers = ''
data = conn.perform_rb()
rcode = conn.getinfo(pycurl.RESPONSE_CODE)
if rcode == 200:
if useQueue and not cls._fromResponderQueue.empty():
receivedQuery = cls._fromResponderQueue.get(True, timeout)
+ cls._response_headers = response_headers.getvalue()
return (receivedQuery, message)
@classmethod
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
_dohServerPort = 8443
- _serverName = 'tls.tests.dnsdist.org'
+ _customResponseHeader1 = 'access-control-allow-origin: *'
+ _customResponseHeader2 = 'user-agent: derp'
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp"}})
addAction("drop.doh.tests.powerdns.com.", DropAction())
addAction("refused.doh.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
self.assertTrue(receivedResponse)
receivedQuery.id = expectedQuery.id
self.assertEquals(expectedQuery, receivedQuery)
+ self.assertTrue((self._customResponseHeader1) in self._response_headers.decode())
+ self.assertTrue((self._customResponseHeader2) in self._response_headers.decode())
self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
self.assertEquals(response, receivedResponse)
incoming-notifications=0
key-cache-size=0
meta-cache-size=3
+open-tcp-connections=0
overload-drops=0
packetcache-size=4
qsize-q=0