From e95b2a7cf94478c567ac052d019c0fb6bb6ffabf Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Thu, 12 Nov 2020 14:53:47 +0100 Subject: [PATCH] rec: Add Lua bindings, regression tests for Extended DNS Errors --- pdns/lua-recursor4-ffi.hh | 3 + pdns/lua-recursor4.cc | 42 +++- pdns/lua-recursor4.hh | 4 +- pdns/pdns_recursor.cc | 123 +++++---- pdns/recursordist/test-ednsoptions_cc.cc | 2 +- .../cookiesoption.py | 99 +------- .../extendederrors.py | 89 +++++++ .../test_ExtendedErrors.py | 234 ++++++++++++++++++ 8 files changed, 441 insertions(+), 155 deletions(-) mode change 100644 => 120000 regression-tests.recursor-dnssec/cookiesoption.py create mode 100644 regression-tests.recursor-dnssec/extendederrors.py create mode 100644 regression-tests.recursor-dnssec/test_ExtendedErrors.py diff --git a/pdns/lua-recursor4-ffi.hh b/pdns/lua-recursor4-ffi.hh index 7051b7c549..3fd240904b 100644 --- a/pdns/lua-recursor4-ffi.hh +++ b/pdns/lua-recursor4-ffi.hh @@ -76,6 +76,9 @@ extern "C" { void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode) __attribute__ ((visibility ("default"))); void pdns_ffi_param_set_follow_cname_records(pdns_ffi_param_t* ref, bool follow) __attribute__ ((visibility ("default"))); + void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code) __attribute__ ((visibility ("default"))); + void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra) __attribute__ ((visibility ("default"))); + /* returns true if the record was correctly added, false if something went wrong. Passing a NULL pointer to 'name' will result in the qname being used for the record owner name. */ bool pdns_ffi_param_add_record(pdns_ffi_param_t *ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentSize, pdns_record_place_t place) __attribute__ ((visibility ("default"))); diff --git a/pdns/lua-recursor4.cc b/pdns/lua-recursor4.cc index f53d4a86fd..b86e30f185 100644 --- a/pdns/lua-recursor4.cc +++ b/pdns/lua-recursor4.cc @@ -173,11 +173,34 @@ void RecursorLua4::postPrepareContext() d_lw->registerMember("followupPrefix", &DNSQuestion::followupPrefix); d_lw->registerMember("followupName", &DNSQuestion::followupName); d_lw->registerMember("data", &DNSQuestion::data); + d_lw->registerMember("extendedErrorCode", [](const DNSQuestion& dq) -> uint16_t { + if (dq.extendedErrorCode && *dq.extendedErrorCode) { + return *(*dq.extendedErrorCode); + } + return 0; + }, + [](DNSQuestion& dq, uint16_t newCode) { + if (dq.extendedErrorCode) { + *dq.extendedErrorCode = newCode; + } + }); + d_lw->registerMember("extendedErrorExtra", [](const DNSQuestion& dq) -> std::string { + if (dq.extendedErrorExtra) { + return *dq.extendedErrorExtra; + } + return ""; + }, + [](DNSQuestion& dq, const std::string& newExtra) { + if (dq.extendedErrorExtra) { + *dq.extendedErrorExtra = newExtra; + } + }); d_lw->registerMember("udpQuery", &DNSQuestion::udpQuery); d_lw->registerMember("udpAnswer", &DNSQuestion::udpAnswer); d_lw->registerMember("udpQueryDest", &DNSQuestion::udpQueryDest); d_lw->registerMember("udpCallback", &DNSQuestion::udpCallback); d_lw->registerMember("appliedPolicy", &DNSQuestion::appliedPolicy); + d_lw->registerMember("policyName", [](const DNSFilterEngine::Policy& pol) -> std::string { return pol.getName(); @@ -219,6 +242,7 @@ void RecursorLua4::postPrepareContext() d_lw->registerFunction("getProxyProtocolValues", &DNSQuestion::getProxyProtocolValues); d_lw->registerFunction("getEDNSFlags", &DNSQuestion::getEDNSFlags); d_lw->registerFunction("getEDNSFlag", &DNSQuestion::getEDNSFlag); + d_lw->registerMember("name", &DNSRecord::d_name); d_lw->registerMember("type", &DNSRecord::d_type); d_lw->registerMember("ttl", &DNSRecord::d_ttl); @@ -578,7 +602,7 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn struct pdns_ffi_param { public: - pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::unordered_set& policyTags_, std::vector& records_, const EDNSOptionViewMap& ednsOptions_, const std::vector& proxyProtocolValues_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, std::string& routingTag_, boost::optional& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), proxyProtocolValues(proxyProtocolValues_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), routingTag(routingTag_), rcode(rcode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), qtype(qtype_), tcp(tcp_) + pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::unordered_set& policyTags_, std::vector& records_, const EDNSOptionViewMap& ednsOptions_, const std::vector& proxyProtocolValues_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, std::string& routingTag_, boost::optional& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_, boost::optional& extendedErrorCode_, std::string& extendedErrorExtra_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), proxyProtocolValues(proxyProtocolValues_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), routingTag(routingTag_), extendedErrorExtra(extendedErrorExtra_), rcode(rcode_), extendedErrorCode(extendedErrorCode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), qtype(qtype_), tcp(tcp_) { } @@ -601,7 +625,9 @@ public: std::string& deviceId; std::string& deviceName; std::string& routingTag; + std::string& extendedErrorExtra; boost::optional& rcode; + boost::optional& extendedErrorCode; uint32_t& ttlCap; bool& variable; bool& logQuery; @@ -613,10 +639,10 @@ public: bool tcp; }; -unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set* policyTags, std::vector& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const +unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set* policyTags, std::vector& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords, boost::optional& extendedErrorCode, std::string& extendedErrorExtra) const { if (d_gettag_ffi) { - pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, tcp, logQuery, logResponse, followCNAMERecords); + pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, tcp, logQuery, logResponse, followCNAMERecords, extendedErrorCode, extendedErrorExtra); auto ret = d_gettag_ffi(¶m); if (ret) { @@ -930,6 +956,16 @@ void pdns_ffi_param_set_follow_cname_records(pdns_ffi_param_t* ref, bool follow) ref->followCNAMERecords = follow; } +void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code) +{ + ref->extendedErrorCode = code; +} + +void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra) +{ + ref->extendedErrorExtra = std::string(extra, len); +} + bool pdns_ffi_param_add_record(pdns_ffi_param_t *ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentSize, pdns_record_place_t place) { try { diff --git a/pdns/lua-recursor4.hh b/pdns/lua-recursor4.hh index 3b911eed18..1bdd02b10f 100644 --- a/pdns/lua-recursor4.hh +++ b/pdns/lua-recursor4.hh @@ -78,6 +78,8 @@ public: std::unordered_set* policyTags{nullptr}; const std::vector* proxyProtocolValues{nullptr}; std::unordered_map* discardedPolicies{nullptr}; + std::string* extendedErrorExtra{nullptr}; + boost::optional* extendedErrorCode{nullptr}; std::string requestorId; std::string deviceId; std::string deviceName; @@ -130,7 +132,7 @@ public: }; unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector& proxyProtocolValues) const; - unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set* policyTags, std::vector& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const; + unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set* policyTags, std::vector& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords, boost::optional& extendedErrorCode, std::string& extendedErrorExtra) const; void maintenance() const; bool prerpz(DNSQuestion& dq, int& ret) const; diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index 8aaa1d3a96..e9589c2bd3 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -344,6 +344,8 @@ struct DNSComboWriter { LuaContext::LuaObject d_data; EDNSSubnetOpts d_ednssubnet; shared_ptr d_tcpConnection; + boost::optional d_extendedErrorCode{boost::none}; + string d_extendedErrorExtra; boost::optional d_rcode{boost::none}; int d_socket{-1}; unsigned int d_tag{0}; @@ -1526,6 +1528,8 @@ static void startDoResolve(void *p) dq.deviceName = dc->d_deviceName; #endif dq.proxyProtocolValues = &dc->d_proxyProtocolValues; + dq.extendedErrorCode = &dc->d_extendedErrorCode; + dq.extendedErrorExtra = &dc->d_extendedErrorExtra; if(ednsExtRCode != 0) { goto sendit; @@ -1858,62 +1862,73 @@ static void startDoResolve(void *p) if (haveEDNS) { auto state = sr.getValidationState(); - if (s_addExtendedDNSErrors && vStateIsBogus(state) && pw.size() < maxanswersize && (maxanswersize - pw.size()) > (2 + 2 + 2)) { + if (s_addExtendedDNSErrors && (dc->d_extendedErrorCode || vStateIsBogus(state))) { EDNSExtendedError::code code; + std::string extra; - switch (state) { - case vState::BogusNoValidDNSKEY: - code = EDNSExtendedError::code::DNSKEYMissing; - break; - case vState::BogusInvalidDenial: - code = EDNSExtendedError::code::NSECMissing; - break; - case vState::BogusUnableToGetDSs: - code = EDNSExtendedError::code::DNSSECBogus; - break; - case vState::BogusUnableToGetDNSKEYs: - code = EDNSExtendedError::code::DNSKEYMissing; - break; - case vState::BogusSelfSignedDS: - code = EDNSExtendedError::code::DNSSECBogus; - break; - case vState::BogusNoRRSIG: - code = EDNSExtendedError::code::RRSIGsMissing; - break; - case vState::BogusNoValidRRSIG: - code = EDNSExtendedError::code::DNSSECBogus; - break; - case vState::BogusMissingNegativeIndication: - code = EDNSExtendedError::code::NSECMissing; - break; - case vState::BogusSignatureNotYetValid: - code = EDNSExtendedError::code::SignatureNotYetValid; - break; - case vState::BogusSignatureExpired: - code = EDNSExtendedError::code::SignatureExpired; - break; - case vState::BogusUnsupportedDNSKEYAlgo: - code = EDNSExtendedError::code::UnsupportedDNSKEYAlgorithm; - break; - case vState::BogusUnsupportedDSDigestType: - code = EDNSExtendedError::code::UnsupportedDSDigestType; - break; - case vState::BogusNoZoneKeyBitSet: - code = EDNSExtendedError::code::NoZoneKeyBitSet; - break; - case vState::BogusRevokedDNSKEY: - code = EDNSExtendedError::code::DNSSECBogus; - break; - case vState::BogusInvalidDNSKEYProtocol: - code = EDNSExtendedError::code::DNSSECBogus; - break; - default: - throw std::runtime_error("Bogus validation state not handled: " + vStateToString(state)); + if (dc->d_extendedErrorCode) { + code = static_cast(*dc->d_extendedErrorCode); + extra = std::move(dc->d_extendedErrorExtra); + } + else { + switch (state) { + case vState::BogusNoValidDNSKEY: + code = EDNSExtendedError::code::DNSKEYMissing; + break; + case vState::BogusInvalidDenial: + code = EDNSExtendedError::code::NSECMissing; + break; + case vState::BogusUnableToGetDSs: + code = EDNSExtendedError::code::DNSSECBogus; + break; + case vState::BogusUnableToGetDNSKEYs: + code = EDNSExtendedError::code::DNSKEYMissing; + break; + case vState::BogusSelfSignedDS: + code = EDNSExtendedError::code::DNSSECBogus; + break; + case vState::BogusNoRRSIG: + code = EDNSExtendedError::code::RRSIGsMissing; + break; + case vState::BogusNoValidRRSIG: + code = EDNSExtendedError::code::DNSSECBogus; + break; + case vState::BogusMissingNegativeIndication: + code = EDNSExtendedError::code::NSECMissing; + break; + case vState::BogusSignatureNotYetValid: + code = EDNSExtendedError::code::SignatureNotYetValid; + break; + case vState::BogusSignatureExpired: + code = EDNSExtendedError::code::SignatureExpired; + break; + case vState::BogusUnsupportedDNSKEYAlgo: + code = EDNSExtendedError::code::UnsupportedDNSKEYAlgorithm; + break; + case vState::BogusUnsupportedDSDigestType: + code = EDNSExtendedError::code::UnsupportedDSDigestType; + break; + case vState::BogusNoZoneKeyBitSet: + code = EDNSExtendedError::code::NoZoneKeyBitSet; + break; + case vState::BogusRevokedDNSKEY: + code = EDNSExtendedError::code::DNSSECBogus; + break; + case vState::BogusInvalidDNSKEYProtocol: + code = EDNSExtendedError::code::DNSSECBogus; + break; + default: + throw std::runtime_error("Bogus validation state not handled: " + vStateToString(state)); + } } EDNSExtendedError eee; eee.infoCode = static_cast(code); - returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee))); + eee.extraText = std::move(extra); + + if (pw.size() < maxanswersize && (maxanswersize - pw.size()) >= (2 + 2 + 2 + eee.extraText.size())) { + returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee))); + } } /* we try to add the EDNS OPT RR even for truncated answers, @@ -2474,7 +2489,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) if(t_pdl) { try { if (t_pdl->d_gettag_ffi) { - dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_records, dc->d_data, ednsOptions, true, dc->d_proxyProtocolValues, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_rcode, dc->d_ttlCap, dc->d_variable, logQuery, dc->d_logResponse, dc->d_followCNAMERecords); + dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_records, dc->d_data, ednsOptions, true, dc->d_proxyProtocolValues, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_rcode, dc->d_ttlCap, dc->d_variable, logQuery, dc->d_logResponse, dc->d_followCNAMERecords, dc->d_extendedErrorCode, dc->d_extendedErrorExtra); } else if (t_pdl->d_gettag) { dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_proxyProtocolValues); @@ -2700,7 +2715,9 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr bool ecsFound = false; bool ecsParsed = false; std::vector records; + std::string extendedErrorExtra; boost::optional rcode = boost::none; + boost::optional extendedErrorCode{boost::none}; uint32_t ttlCap = std::numeric_limits::max(); bool variable = false; bool followCNAMEs = false; @@ -2739,7 +2756,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr if(t_pdl) { try { if (t_pdl->d_gettag_ffi) { - ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, records, data, ednsOptions, false, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, logQuery, logResponse, followCNAMEs); + ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, records, data, ednsOptions, false, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, logQuery, logResponse, followCNAMEs, extendedErrorCode, extendedErrorExtra); } else if (t_pdl->d_gettag) { ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName, routingTag, proxyProtocolValues); @@ -2910,6 +2927,8 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr #endif dc->d_proxyProtocolValues = std::move(proxyProtocolValues); dc->d_routingTag = std::move(routingTag); + dc->d_extendedErrorCode = extendedErrorCode; + dc->d_extendedErrorExtra = std::move(extendedErrorExtra); MT->makeThread(startDoResolve, (void*) dc.release()); // deletes dc return 0; diff --git a/pdns/recursordist/test-ednsoptions_cc.cc b/pdns/recursordist/test-ednsoptions_cc.cc index 433cb0b4c7..20cd19481c 100644 --- a/pdns/recursordist/test-ednsoptions_cc.cc +++ b/pdns/recursordist/test-ednsoptions_cc.cc @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(test_makeEDNSExtendedErrorOptString) std::string extra; /* the size of an EDNS option is limited to 2^16-1, and in this case the code already adds 2 bytes */ - extra.resize(65535); + extra.resize(65534); BOOST_CHECK_THROW(checkExtendedErrorOptionValidity(EDNSExtendedError::code::Other, extra), std::runtime_error); EDNSExtendedError parsed; diff --git a/regression-tests.recursor-dnssec/cookiesoption.py b/regression-tests.recursor-dnssec/cookiesoption.py deleted file mode 100644 index d977e1030d..0000000000 --- a/regression-tests.recursor-dnssec/cookiesoption.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python2 - -import dns -import dns.edns -import dns.flags -import dns.message -import dns.query - -class CookiesOption(dns.edns.Option): - """Implementation of draft-ietf-dnsop-cookies-09. - """ - - def __init__(self, client, server): - super(CookiesOption, self).__init__(10) - - if len(client) != 8: - raise Exception('invalid client cookie length') - - if server is not None and len(server) != 0 and (len(server) < 8 or len(server) > 32): - raise Exception('invalid server cookie length') - - self.client = client - self.server = server - - def to_wire(self, file=None): - """Create EDNS packet as defined in draft-ietf-dnsop-cookies-09.""" - - if self.server and len(self.server) > 0: - data = self.client + self.server - else: - data = self.client - - if file: - file.write(data) - else: - return data - - def from_wire(cls, otype, wire, current, olen): - """Read EDNS packet as defined in draft-ietf-dnsop-cookies-09. - - Returns: - An instance of CookiesOption based on the EDNS packet - """ - - data = wire[current:current + olen] - if len(data) != 8 and (len(data) < 16 or len(data) > 40): - raise Exception('Invalid EDNS Cookies option') - - client = data[:8] - if len(data) > 8: - server = data[8:] - else: - server = None - - return cls(client, server) - - from_wire = classmethod(from_wire) - - # needed in 2.0.0 - @classmethod - def from_wire_parser(cls, otype, parser): - data = parser.get_remaining() - - if len(data) != 8 and (len(data) < 16 or len(data) > 40): - raise Exception('Invalid EDNS Cookies option') - - client = data[:8] - if len(data) > 8: - server = data[8:] - else: - server = None - - return cls(client, server) - - def __repr__(self): - return '%s(%s, %s)' % ( - self.__class__.__name__, - self.client, - self.server - ) - - def to_text(self): - return self.__repr__() - - def __eq__(self, other): - if not isinstance(other, CookiesOption): - return False - if self.client != other.client: - return False - if self.server != other.server: - return False - return True - - def __ne__(self, other): - return not self.__eq__(other) - - -dns.edns._type_to_class[0x000A] = CookiesOption diff --git a/regression-tests.recursor-dnssec/cookiesoption.py b/regression-tests.recursor-dnssec/cookiesoption.py new file mode 120000 index 0000000000..11e592d1d8 --- /dev/null +++ b/regression-tests.recursor-dnssec/cookiesoption.py @@ -0,0 +1 @@ +../regression-tests.dnsdist/cookiesoption.py \ No newline at end of file diff --git a/regression-tests.recursor-dnssec/extendederrors.py b/regression-tests.recursor-dnssec/extendederrors.py new file mode 100644 index 0000000000..29b68535e0 --- /dev/null +++ b/regression-tests.recursor-dnssec/extendederrors.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +import struct + +import dns +import dns.edns +import dns.flags +import dns.message +import dns.query + +class ExtendedErrorOption(dns.edns.Option): + """Implementation of rfc8914 + """ + + def __init__(self, code, extra): + super(ExtendedErrorOption, self).__init__(15) + + self.code = code + self.extra = extra + + def to_wire(self, file=None): + """Create EDNS packet.""" + + data = struct.pack('!H', self.code) + data = data + self.extra + if file: + file.write(data) + else: + return data + + def from_wire(cls, otype, wire, current, olen): + """Read EDNS packet. + + Returns: + An instance of ExtendedErrorOption based on the EDNS packet + """ + + if olen < 2: + raise Exception('Invalid EDNS Extended Error option') + + (code,) = struct.unpack('!H', wire[current:current+2]) + if olen > 2: + extra = wire[current + 2:current + olen] + else: + extra = b'' + + return cls(code, extra) + + from_wire = classmethod(from_wire) + + # needed in 2.0.0 + @classmethod + def from_wire_parser(cls, otype, parser): + data = parser.get_remaining() + + if len(data) < 2: + raise Exception('Invalid EDNS Extended Error option') + + (code,) = struct.unpack('!H', data[0:2]) + if len(data) > 2: + extra = data[2:] + else: + extra = b'' + + return cls(code, extra) + + def __repr__(self): + return '%s(%d, %s)' % ( + self.__class__.__name__, + self.code, + self.extra + ) + + def to_text(self): + return self.__repr__() + + def __eq__(self, other): + if not isinstance(other, ExtendedErrorOption): + return False + if self.code != other.code: + return False + if self.extra != other.extra: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + +dns.edns._type_to_class[0x000F] = ExtendedErrorOption diff --git a/regression-tests.recursor-dnssec/test_ExtendedErrors.py b/regression-tests.recursor-dnssec/test_ExtendedErrors.py new file mode 100644 index 0000000000..d14672069f --- /dev/null +++ b/regression-tests.recursor-dnssec/test_ExtendedErrors.py @@ -0,0 +1,234 @@ +import dns +import os +import extendederrors + +from recursortests import RecursorTest + +class ExtendedErrorsRecursorTest(RecursorTest): + + _confdir = 'ExtendedErrors' + _config_template_default = """ +dnssec=validate +daemon=no +trace=yes +packetcache-ttl=0 +packetcache-servfail-ttl=0 +max-cache-ttl=15 +threads=1 +loglevel=9 +disable-syslog=yes +log-common-errors=yes +""" + _config_template = """ + extended-errors=yes + """ + _lua_dns_script_file = """ + function preresolve(dq) + if dq.qname == newDN('fromlua.extended.') then + dq.extendedErrorCode = 10 + dq.extendedErrorExtra = "Extra text from Lua!" + return true + end + if dq.qname == newDN('toolarge.extended.') then + dq:addRecord(pdns.TXT, '%s', pdns.place.ANSWER) + dq.extendedErrorCode = 10 + dq.extendedErrorExtra = "Extra text from Lua!" + return true + end + return false + end + + local ffi = require("ffi") + + ffi.cdef[[ + typedef struct pdns_ffi_param pdns_ffi_param_t; + + const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref) __attribute__ ((visibility ("default"))); + void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode) __attribute__ ((visibility ("default"))); + void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code) __attribute__ ((visibility ("default"))); + void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra); + ]] + + function gettag_ffi(obj) + local qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj)) + if qname == 'fromluaffi.extended' then + ffi.C.pdns_ffi_param_set_rcode(obj, 0) + ffi.C.pdns_ffi_param_set_extended_error_code(obj, 10) + local extra = 'Extra text from Lua FFI!' + ffi.C.pdns_ffi_param_set_extended_error_extra(obj, #extra, extra) + end + return {} + end + """ % ('A'*427) + + _roothints = None + + @classmethod + def setUpClass(cls): + + # we don't need all the auth stuff + cls.setUpSockets() + cls.startResponders() + + confdir = os.path.join('configs', cls._confdir) + cls.createConfigDir(confdir) + + cls.generateRecursorConfig(confdir) + cls.startRecursor(confdir, cls._recursorPort) + + @classmethod + def tearDownClass(cls): + cls.tearDownRecursor() + + @classmethod + def generateRecursorConfig(cls, confdir): + super(ExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir) + + def testNotIncepted(self): + qname = 'signotincepted.bad-dnssec.wb.sidnlabs.nl.' + query = dns.message.make_query(qname, 'A', want_dnssec=True) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + res = sender(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.SERVFAIL) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 1) + self.assertEqual(res.options[0].otype, 15) + self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(8, b'')) + + def testExpired(self): + qname = 'sigexpired.bad-dnssec.wb.sidnlabs.nl.' + query = dns.message.make_query(qname, 'A', want_dnssec=True) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + res = sender(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.SERVFAIL) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 1) + self.assertEqual(res.options[0].otype, 15) + self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(7, b'')) + + def testBogus(self): + qname = 'unknownalgorithm.bad-dnssec.wb.sidnlabs.nl.' + query = dns.message.make_query(qname, 'A', want_dnssec=True) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + res = sender(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.SERVFAIL) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 1) + self.assertEqual(res.options[0].otype, 15) + self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(6, b'')) + + def testMissingRRSIG(self): + qname = 'brokendnssec.net.' + query = dns.message.make_query(qname, 'A', want_dnssec=True) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + res = sender(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.SERVFAIL) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 1) + self.assertEqual(res.options[0].otype, 15) + self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b'')) + + def testFromLua(self): + qname = 'fromlua.extended.' + query = dns.message.make_query(qname, 'A', want_dnssec=True) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + res = sender(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 1) + self.assertEqual(res.options[0].otype, 15) + self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b'Extra text from Lua!')) + + def testFromLuaFFI(self): + qname = 'fromluaffi.extended.' + query = dns.message.make_query(qname, 'A', want_dnssec=True) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + res = sender(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 1) + self.assertEqual(res.options[0].otype, 15) + self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b'Extra text from Lua FFI!')) + + def testTooLarge(self): + qname = 'toolarge.extended.' + query = dns.message.make_query(qname, 'A', want_dnssec=True, payload=512) + + # should not have the Extended Option since the packet is too large already + res = self.sendUDPQuery(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertEquals(len(res.answer), 1) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 0) + + res = self.sendTCPQuery(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertEquals(len(res.answer), 1) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 1) + self.assertEqual(res.options[0].otype, 15) + self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b'Extra text from Lua!')) + +class NoExtendedErrorsRecursorTest(RecursorTest): + + _confdir = 'ExtendedErrorsDisabled' + _config_template_default = """ +dnssec=validate +daemon=no +trace=yes +packetcache-ttl=0 +packetcache-servfail-ttl=0 +max-cache-ttl=15 +threads=1 +loglevel=9 +disable-syslog=yes +log-common-errors=yes +""" + _config_template = """ + extended-errors=no + """ + _roothints = None + + @classmethod + def setUpClass(cls): + + # we don't need all the auth stuff + cls.setUpSockets() + cls.startResponders() + + confdir = os.path.join('configs', cls._confdir) + cls.createConfigDir(confdir) + + cls.generateRecursorConfig(confdir) + cls.startRecursor(confdir, cls._recursorPort) + + @classmethod + def tearDownClass(cls): + cls.tearDownRecursor() + + @classmethod + def generateRecursorConfig(cls, confdir): + super(NoExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir) + + def testNotIncepted(self): + qname = 'signotincepted.bad-dnssec.wb.sidnlabs.nl.' + query = dns.message.make_query(qname, 'A', want_dnssec=True) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + res = sender(query, timeout=5.0) + self.assertRcodeEqual(res, dns.rcode.SERVFAIL) + self.assertEqual(res.edns, 0) + self.assertEqual(len(res.options), 0) -- 2.47.2