Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dnsdist: add EDNSOptionRule #6803

Merged
merged 3 commits into from Aug 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions pdns/dnsdist-console.cc
Expand Up @@ -404,6 +404,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
{ "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
{ "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
{ "ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)" },
{ "EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present" },
{ "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
{ "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
{ "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
Expand Down
31 changes: 31 additions & 0 deletions pdns/dnsdist-ecs.cc
Expand Up @@ -427,6 +427,37 @@ int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optio
return 0;
}

bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind)
{
/* we need at least:
root label (1), type (2), class (2), ttl (4) + rdlen (2)*/
if (optLen < 11) {
return false;
}
size_t p = optStart + 9;
uint16_t rdLen = (0x100*packet.at(p) + packet.at(p+1));
p += sizeof(rdLen);
if (11 + rdLen > optLen) {
return false;
}

size_t rdEnd = p + rdLen;
while ((p + 4) <= rdEnd) {
const uint16_t optionCode = 0x100*packet.at(p) + packet.at(p+1);
p += sizeof(optionCode);
const uint16_t optionLen = 0x100*packet.at(p) + packet.at(p+1);
p += sizeof(optionLen);
if ((p + optionLen) > rdEnd) {
return false;
}
if (optionCode == optionCodeToFind) {
return true;
}
p += optionLen;
}
return false;
}

int rewriteResponseWithoutEDNSOption(const std::string& initialPacket, const uint16_t optionCodeToSkip, vector<uint8_t>& newContent)
{
assert(initialPacket.size() >= sizeof(dnsheader));
Expand Down
1 change: 1 addition & 0 deletions pdns/dnsdist-ecs.hh
Expand Up @@ -28,3 +28,4 @@ void generateOptRR(const std::string& optRData, string& res);
int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove);
int rewriteResponseWithoutEDNSOption(const std::string& initialPacket, const uint16_t optionCodeToSkip, vector<uint8_t>& newContent);
int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optRDLen, size_t * remaining);
bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind);
4 changes: 4 additions & 0 deletions pdns/dnsdist-lua-rules.cc
Expand Up @@ -417,6 +417,10 @@ void setupLuaRules()
return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
});

g_lua.writeFunction("EDNSOptionRule", [](uint16_t optcode) {
return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
});

g_lua.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
showRules(&g_rulactions, vars);
});
Expand Down
15 changes: 15 additions & 0 deletions pdns/dnsdist-lua-vars.cc
Expand Up @@ -20,6 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "dnsdist.hh"
#include "ednsoptions.hh"

void setupLuaVars()
{
Expand Down Expand Up @@ -69,6 +70,20 @@ void setupLuaVars()
{"Additional",3 }
});

g_lua.writeVariable("EDNSOptionCode", std::unordered_map<string,int>{
{"NSID", EDNSOptionCode::NSID },
{"DAU", EDNSOptionCode::DAU },
{"DHU", EDNSOptionCode::DHU },
{"N3U", EDNSOptionCode::N3U },
{"ECS", EDNSOptionCode::ECS },
{"EXPIRE", EDNSOptionCode::EXPIRE },
{"COOKIE", EDNSOptionCode::COOKIE },
{"TCPKEEPALIVE", EDNSOptionCode::TCPKEEPALIVE },
{"PADDING", EDNSOptionCode::PADDING },
{"CHAIN", EDNSOptionCode::CHAIN },
{"KEYTAG", EDNSOptionCode::KEYTAG }
});

vector<pair<string, int> > rcodes = {{"NOERROR", RCode::NoError },
{"FORMERR", RCode::FormErr },
{"SERVFAIL", RCode::ServFail },
Expand Down
39 changes: 39 additions & 0 deletions pdns/dnsdistdist/dnsdist-rules.hh
Expand Up @@ -882,6 +882,45 @@ private:
uint8_t d_extrcode; // upper bits in EDNS0 record
};

class EDNSOptionRule : public DNSRule
{
public:
EDNSOptionRule(uint16_t optcode) : d_optcode(optcode)
{
}
bool matches(const DNSQuestion* dq) const override
{
uint16_t optStart;
size_t optLen = 0;
bool last = false;
const char * packet = reinterpret_cast<const char*>(dq->dh);
std::string packetStr(packet, dq->len);
int res = locateEDNSOptRR(packetStr, &optStart, &optLen, &last);
if (res != 0) {
// no EDNS OPT RR
return false;
}

// root label (1), type (2), class (2), ttl (4) + rdlen (2)
if (optLen < 11) {
return false;
}

if (optStart < dq->len && packetStr.at(optStart) != 0) {
// OPT RR Name != '.'
return false;
}

return isEDNSOptionInOpt(packetStr, optStart, optLen, d_optcode);
}
string toString() const override
{
return "ednsoptcode=="+std::to_string(d_optcode);
}
private:
uint16_t d_optcode;
};

class RDRule : public DNSRule
{
public:
Expand Down
20 changes: 20 additions & 0 deletions pdns/dnsdistdist/docs/reference/constants.rst
Expand Up @@ -58,6 +58,26 @@ RCodes below and including ``BADVERS`` are extended RCodes that can only be matc

Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6


.. _EDNSOptionCode:

EDNSOptionCode
--------------

- ``EDNSOptionCode.DHU``
- ``EDNSOptionCode.ECS``
- ``EDNSOptionCode.N3U``
- ``EDNSOptionCode.DAU``
- ``EDNSOptionCode.TCPKEEPALIVE``
- ``EDNSOptionCode.COOKIE``
- ``EDNSOptionCode.PADDING``
- ``EDNSOptionCode.KEYTAG``
- ``EDNSOptionCode.NSID``
- ``EDNSOptionCode.CHAIN``
- ``EDNSOptionCode.EXPIRE``

Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11

.. _DNSSection:

DNS Section
Expand Down
7 changes: 7 additions & 0 deletions pdns/dnsdistdist/docs/rules-actions.rst
Expand Up @@ -638,6 +638,13 @@ These ``DNSRule``\ s be one of the following items:

:param int rcode: The RCODE to match on

.. function:: EDNSOptionRule(optcode)

.. versionadded:: 1.4.0

Matches queries or responses with the specified EDNS option present.
``optcode`` is specified as an integer, or a constant such as `EDNSOptionCode.ECS`.

.. function:: RDRule()

.. versionadded:: 1.2.0
Expand Down
54 changes: 54 additions & 0 deletions regression-tests.dnsdist/test_Advanced.py
Expand Up @@ -5,6 +5,7 @@
import string
import time
import dns
import clientsubnetoption
from dnsdisttests import DNSDistTest

class TestAdvancedAllow(DNSDistTest):
Expand Down Expand Up @@ -1622,3 +1623,56 @@ def testTempFailureTTLBinding(self):
receivedQuery.id = query.id
self.assertEquals(query, receivedQuery)
self.assertEquals(receivedResponse, response)

class TestAdvancedEDNSOptionRule(DNSDistTest):

_config_template = """
newServer{address="127.0.0.1:%s"}
addAction(EDNSOptionRule(EDNSOptionCode.ECS), DropAction())
"""

def testDropped(self):
"""
A question with ECS is dropped
"""

name = 'ednsoptionrule.advanced.tests.powerdns.com.'

ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512)

(_, receivedResponse) = self.sendUDPQuery(query, response=None)
self.assertEquals(receivedResponse, None)
(_, receivedResponse) = self.sendTCPQuery(query, response=None)
self.assertEquals(receivedResponse, None)

def testReplied(self):
"""
A question without ECS is answered
"""

name = 'ednsoptionrule.advanced.tests.powerdns.com.'

# both with EDNS
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[], payload=512)
response = dns.message.make_response(query)

(receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
self.assertTrue(receivedQuery)
self.assertTrue(receivedResponse)

receivedQuery.id = query.id
self.assertEquals(query, receivedQuery)
self.assertEquals(receivedResponse, response)

# and with no EDNS at all
query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
response = dns.message.make_response(query)

(receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
self.assertTrue(receivedQuery)
self.assertTrue(receivedResponse)

receivedQuery.id = query.id
self.assertEquals(query, receivedQuery)
self.assertEquals(receivedResponse, response)