This hook does not get the full DNSQuestion object, since filling out the fields
would require packet parsing, which is what we are trying to prevent with `ipfilter`.
-### `function gettag(remote, ednssubnet, local, qname, qtype)`
+### `function gettag(remote, ednssubnet, local, qname, qtype, ednsoptions)`
The `gettag` function is invoked when the Recursor attempts to discover in which
packetcache an answer is available.
setting dq.variable to `true`. In the latter case, repeated queries will pass
through the entire Lua script.
+`ednsoptions` is a table whose keys are EDNS option codes and values are
+`EDNSOptionView` objects, with the EDNS option content size in the `size` member
+and the content accessible as a NULL-safe string object via `getContent()`.
+This table is empty unless the `gettag-needs-edns-options` parameter is set.
+
### `function prerpz(dq)`
This hook is called before any filtering policy have been applied, making it
The DNSSEC notes from [`forward-zones`](#forward-zones) apply here as well.
+## `gettag-needs-edns-options`
+* Boolean
+* Default: no
+* Available since: 4.1.0
+
+If set, EDNS options in incoming queries are extracted and passed to the `gettag()`
+hook in the `ednsoptions` table.
+
## `hint-file`
* Path
return ENOENT;
}
+/* extract all EDNS0 options from a pointer on the beginning rdLen of the OPT RR */
+int getEDNSOptions(const char* optRR, const size_t len, std::map<uint16_t, EDNSOptionView>& options)
+{
+ assert(optRR != NULL);
+ size_t pos = 0;
+ if (len < DNS_RDLENGTH_SIZE)
+ return EINVAL;
+
+ const uint16_t rdLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+ size_t rdPos = 0;
+ pos += DNS_RDLENGTH_SIZE;
+ if ((pos + rdLen) > len) {
+ return EINVAL;
+ }
+
+ while(len >= (pos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE) &&
+ rdLen >= (rdPos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
+ const uint16_t optionCode = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+ pos += EDNS_OPTION_CODE_SIZE;
+ rdPos += EDNS_OPTION_CODE_SIZE;
+ const uint16_t optionLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+ pos += EDNS_OPTION_LENGTH_SIZE;
+ rdPos += EDNS_OPTION_LENGTH_SIZE;
+ if (optionLen > (rdLen - rdPos) || optionLen > (len - pos))
+ return EINVAL;
+
+ EDNSOptionView view;
+ view.content = optRR + pos;
+ view.size = optionLen;
+ options[optionCode] = view;
+
+ /* skip this option */
+ pos += optionLen;
+ rdPos += optionLen;
+ }
+
+ return 0;
+}
+
void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res)
{
const uint16_t ednsOptionCode = htons(optionCode);
/* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
int getEDNSOption(char* optRR, size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize);
+
+struct EDNSOptionView
+{
+ const char* content{nullptr};
+ uint16_t size{0};
+};
+
+/* extract all EDNS0 options from a pointer on the beginning rdLen of the OPT RR */
+int getEDNSOptions(const char* optRR, size_t len, std::map<uint16_t, EDNSOptionView>& options);
+
void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res);
#endif
d_lw->registerMember("type", &DNSRecord::d_type);
d_lw->registerMember("ttl", &DNSRecord::d_ttl);
d_lw->registerMember("place", &DNSRecord::d_place);
-
+
+ d_lw->registerMember("size", &EDNSOptionView::size);
+ d_lw->registerFunction<std::string(EDNSOptionView::*)()>("getContent", [](const EDNSOptionView& option) { return std::string(option.content, option.size); });
+
d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dr) { return dr.d_content->getZoneRepresentation(); });
d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dr) {
boost::optional<ComboAddress> ret;
return false; // don't block
}
-unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data)
+unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const std::map<uint16_t, EDNSOptionView>& ednsOptions)
{
if(d_gettag) {
- auto ret = d_gettag(remote, ednssubnet, local, qname, qtype);
+ auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions);
if (policyTags) {
const auto& tags = std::get<1>(ret);
#include "namespaces.hh"
#include "dnsrecords.hh"
#include "filterpo.hh"
+#include "ednsoptions.hh"
+
#include <unordered_map>
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
DNSName followupName;
};
- unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data);
+ unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const std::map<uint16_t, EDNSOptionView>&);
bool prerpz(DNSQuestion& dq, int& ret);
bool preresolve(DNSQuestion& dq, int& ret);
d_postresolve);
}
- typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject> >(ComboAddress, Netmask, ComboAddress, DNSName,
-uint16_t)> gettag_t;
+ typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const std::map<uint16_t, EDNSOptionView>&)> gettag_t;
gettag_t d_gettag; // public so you can query if we have this hooked
private:
static bool g_weDistributeQueries; // if true, only 1 thread listens on the incoming query sockets
static bool g_reusePort{false};
static bool g_useOneSocketPerThread;
+static bool g_gettagNeedsEDNSOptions{false};
std::unordered_set<DNSName> g_delegationOnly;
RecursorControlChannel s_rcc; // only active in thread 0
}
}
-static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet)
+static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet, std::map<uint16_t, EDNSOptionView>* options)
{
bool found = false;
const struct dnsheader* dh = (struct dnsheader*)question.c_str();
if(ntohs(dh->arcount) == 1 && questionLen > pos + 11) { // this code can extract one (1) EDNS Subnet option
/* OPT root label (1) followed by type (2) */
if(question.at(pos)==0 && question.at(pos+1)==0 && question.at(pos+2)==QType::OPT) {
- char* ecsStart = nullptr;
- size_t ecsLen = 0;
- int res = getEDNSOption((char*)question.c_str()+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
- if (res == 0 && ecsLen > 4) {
- EDNSSubnetOpts eso;
- if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
- *ednssubnet=eso;
- found = true;
+ if (!options) {
+ char* ecsStart = nullptr;
+ size_t ecsLen = 0;
+ int res = getEDNSOption((char*)question.c_str()+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
+ if (res == 0 && ecsLen > 4) {
+ EDNSSubnetOpts eso;
+ if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
+ *ednssubnet=eso;
+ found = true;
+ }
+ }
+ }
+ else {
+ int res = getEDNSOptions((char*)question.c_str()+pos+9, questionLen - pos - 9, *options);
+ if (res == 0) {
+ const auto& it = options->find(EDNSOptionCode::ECS);
+ if (it != options->end() && it->second.content != nullptr && it->second.size > 0) {
+ EDNSSubnetOpts eso;
+ if(getEDNSSubnetOptsFromString(it->second.content, it->second.size, &eso)) {
+ *ednssubnet=eso;
+ found = true;
+ }
+ }
}
}
}
if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) {
try {
+ std::map<uint16_t, EDNSOptionView> ednsOptions;
dc->d_ecsParsed = true;
- dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet);
+ dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
if(t_pdl->get() && (*t_pdl)->d_gettag) {
try {
- dc->d_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data);
+ dc->d_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions);
}
catch(std::exception& e) {
if(g_logCommonErrors)
if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) {
try {
- ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet);
+ std::map<uint16_t, EDNSOptionView> ednsOptions;
+ ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
qnameParsed = true;
ecsParsed = true;
if(t_pdl->get() && (*t_pdl)->d_gettag) {
try {
- ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data);
+ ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data, ednsOptions);
}
catch(std::exception& e) {
if(g_logCommonErrors)
g_numThreads = g_numWorkerThreads + g_weDistributeQueries;
g_maxMThreads = ::arg().asNum("max-mthreads");
+ g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
+
#ifdef SO_REUSEPORT
g_reusePort = ::arg().mustDo("reuseport");
#endif
::arg().setSwitch( "root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist")="yes";
::arg().setSwitch( "any-to-tcp","Answer ANY queries with tc=1, shunting to TCP" )="no";
::arg().setSwitch( "lowercase-outgoing","Force outgoing questions to lowercase")="no";
+ ::arg().setSwitch("gettag-needs-edns-options", "If EDNS Options should be extracted before calling the gettag() hook")="no";
::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1680";
::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size")="1680";
::arg().set("minimum-ttl-override", "Set under adverse conditions, a minimum TTL")="0";
dnsrecords.cc \
dnssecinfra.cc \
dnswriter.cc dnswriter.hh \
+ ednscookies.cc ednscookies.hh \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc ednssubnet.hh \
gettime.cc gettime.hh \
test-dnsname_cc.cc \
test-dnsparser_hh.cc \
test-dnsrecords_cc.cc \
+ test-ednsoptions_cc.cc \
test-iputils_hh.cc \
test-misc_hh.cc \
test-nmtree.cc \
--- /dev/null
+../ednscookies.cc
\ No newline at end of file
--- /dev/null
+../ednscookies.hh
\ No newline at end of file
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+#include <utility>
+
+#include "dnsname.hh"
+#include "dnswriter.hh"
+#include "ednscookies.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+#include "iputils.hh"
+
+/* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
+int getEDNSOption(char* optRR, size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize);
+
+BOOST_AUTO_TEST_SUITE(ednsoptions_cc)
+
+static void getRawQueryWithECSAndCookie(const DNSName& name, const Netmask& ecs, const std::string& clientCookie, const std::string& serverCookie, std::vector<uint8_t>& query)
+{
+ DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+ pw.commit();
+
+ EDNSCookiesOpt cookiesOpt;
+ cookiesOpt.client = clientCookie;
+ cookiesOpt.server = serverCookie;
+ string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = ecs;
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ DNSPacketWriter::optvect_t opts;
+ opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
+ opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
+ opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSOption) {
+ DNSName name("www.powerdns.com.");
+ Netmask ecs("127.0.0.1/32");
+ vector<uint8_t> query;
+
+ getRawQueryWithECSAndCookie(name, ecs, "deadbeef", "deadbeef", query);
+
+ const struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query.data());
+ size_t questionLen = query.size();
+ unsigned int consumed = 0;
+ DNSName dnsname = DNSName(reinterpret_cast<const char*>(query.data()), questionLen, sizeof(dnsheader), false, nullptr, nullptr, &consumed);
+
+ size_t pos = sizeof(dnsheader) + consumed + 4;
+ /* at least OPT root label (1), type (2), class (2) and ttl (4) + OPT RR rdlen (2) = 11 */
+ BOOST_REQUIRE_EQUAL(ntohs(dh->arcount), 1);
+ BOOST_REQUIRE(questionLen > pos + 11);
+ /* OPT root label (1) followed by type (2) */
+ BOOST_REQUIRE_EQUAL(query.at(pos), 0);
+ BOOST_REQUIRE(query.at(pos+2) == QType::OPT);
+
+ char* ecsStart = nullptr;
+ size_t ecsLen = 0;
+ int res = getEDNSOption(reinterpret_cast<char*>(query.data())+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
+ BOOST_CHECK_EQUAL(res, 0);
+
+ EDNSSubnetOpts eso;
+ BOOST_REQUIRE(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso));
+
+ BOOST_CHECK(eso.source == ecs);
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSOptions) {
+ DNSName name("www.powerdns.com.");
+ Netmask ecs("127.0.0.1/32");
+ vector<uint8_t> query;
+
+ getRawQueryWithECSAndCookie(name, ecs, "deadbeef", "deadbeef", query);
+
+ const struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query.data());
+ size_t questionLen = query.size();
+ unsigned int consumed = 0;
+ DNSName dnsname = DNSName(reinterpret_cast<const char*>(query.data()), questionLen, sizeof(dnsheader), false, nullptr, nullptr, &consumed);
+
+ size_t pos = sizeof(dnsheader) + consumed + 4;
+ /* at least OPT root label (1), type (2), class (2) and ttl (4) + OPT RR rdlen (2) = 11 */
+ BOOST_REQUIRE_EQUAL(ntohs(dh->arcount), 1);
+ BOOST_REQUIRE(questionLen > pos + 11);
+ /* OPT root label (1) followed by type (2) */
+ BOOST_REQUIRE_EQUAL(query.at(pos), 0);
+ BOOST_REQUIRE(query.at(pos+2) == QType::OPT);
+
+ std::map<uint16_t, EDNSOptionView> options;
+ int res = getEDNSOptions(reinterpret_cast<char*>(query.data())+pos+9, questionLen - pos - 9, options);
+ BOOST_REQUIRE_EQUAL(res, 0);
+
+ /* 3 EDNS options but two of them are EDNS Cookie, so we only keep one */
+ BOOST_CHECK_EQUAL(options.size(), 2);
+
+ auto it = options.find(EDNSOptionCode::ECS);
+ BOOST_REQUIRE(it != options.end());
+ BOOST_REQUIRE(it->second.content != nullptr);
+ BOOST_REQUIRE_GT(it->second.size, 0);
+
+ EDNSSubnetOpts eso;
+ BOOST_REQUIRE(getEDNSSubnetOptsFromString(it->second.content, it->second.size, &eso));
+ BOOST_CHECK(eso.source == ecs);
+
+ it = options.find(EDNSOptionCode::COOKIE);
+ BOOST_REQUIRE(it != options.end());
+ BOOST_REQUIRE(it->second.content != nullptr);
+ BOOST_REQUIRE_GT(it->second.size, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()