Skip to content

Commit

Permalink
Merge pull request #8117 from rgacogne/dnsdist-continue-action
Browse files Browse the repository at this point in the history
dnsdist: Implement ContinueAction()
  • Loading branch information
rgacogne committed Aug 5, 2019
2 parents bf599d8 + 2f2cbf1 commit 7fc7689
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 62 deletions.
1 change: 1 addition & 0 deletions pdns/dnsdist-console.cc
Expand Up @@ -364,6 +364,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
{ "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" },
Expand Down
39 changes: 39 additions & 0 deletions pdns/dnsdist-lua-actions.cc
Expand Up @@ -1041,6 +1041,41 @@ class TagResponseAction : public DNSResponseAction
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();
Expand Down Expand Up @@ -1340,4 +1375,8 @@ void setupLuaActions()
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));
});
}
132 changes: 73 additions & 59 deletions pdns/dnsdist.cc
Expand Up @@ -1036,7 +1036,71 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent)
}
}

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);

Expand Down Expand Up @@ -1171,69 +1235,21 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, string& po

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;
}

Expand Down Expand Up @@ -1415,9 +1431,7 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
struct timespec now;
gettime(&now);

string poolname;

if (!applyRulesToQuery(holders, dq, poolname, now)) {
if (!applyRulesToQuery(holders, dq, now)) {
return ProcessQueryResult::Drop;
}

Expand All @@ -1432,7 +1446,7 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
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) {
Expand Down
2 changes: 2 additions & 0 deletions pdns/dnsdist.hh
Expand Up @@ -76,6 +76,7 @@ struct DNSQuestion
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};
Expand Down Expand Up @@ -1152,6 +1153,7 @@ void resetLuaSideEffect(); // reset to indeterminate state

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);

Expand Down
21 changes: 18 additions & 3 deletions pdns/dnsdistdist/docs/rules-actions.rst
Expand Up @@ -852,6 +852,15 @@ The following actions exist.

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).
Expand Down Expand Up @@ -881,6 +890,7 @@ The following actions exist.

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
Expand All @@ -892,6 +902,7 @@ The following actions exist.

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
Expand Down Expand Up @@ -1014,7 +1025,8 @@ The following actions exist.
``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
Expand All @@ -1034,9 +1046,10 @@ The following actions exist.
``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
Expand Down Expand Up @@ -1099,6 +1112,7 @@ The following actions exist.
.. 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
Expand All @@ -1108,6 +1122,7 @@ The following actions exist.
.. 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
Expand Down
51 changes: 51 additions & 0 deletions regression-tests.dnsdist/test_Advanced.py
Expand Up @@ -1773,3 +1773,54 @@ def testClearThenSetRules(self):
(_, 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)

0 comments on commit 7fc7689

Please sign in to comment.