* addDisableValidationRule(DNS rule)
* addDomainBlock(domain)
* addDomainSpoof(domain, IPv4[, IPv6])
+ * addDomainCNAMESpoof(domain, CNAME)
* addLuaAction(DNS rule, lua function)
* addNoRecurseRule(DNS rule)
* addPoolRule(DNS rule, destination pool)
* `QPSPoolAction()`: set the packet into the specified pool only if it does not exceed the specified QPS limits
* `QPSAction()`: drop these packets if the QPS limits are exceeded
* `RCodeAction()`: reply immediatly by turning the query into a response with the specified rcode
- * `SpoofAction()`: forge a response with the specified IPv4 (for an A query). If you specify both an IPv4 and an IPv6, IPv4 will be used for A and IPv6 for an AAAA.
+ * `SpoofAction()`: forge a response with the specified IPv4 (for an A query). If you specify both an IPv4 and an IPv6, IPv4 will be used for A and IPv6 for an AAAA
+ * `SpoofCNAMEAction()`: forge a response with the specified CNAME value
* `TCAction()`: create answer to query with TC and RD bits set, to move to TCP/IP
* Specialist rule generators
* addAnyTCRule(): generate TC=1 answers to ANY queries, moving them to TCP
* addDomainSpoof(domain, ip[, ip6]): generate answers for A queries using the ip parameter. If ip6 is supplied, generate answers for AAAA queries too
+ * addDomainCNAMESpoof(domain, cname): generate CNAME answers for queries using the specified value
* addDisableValidationRule(domain): set the CD flags to 1 for all queries matching the specified domain
* addNoRecurseRule(domain): clear the RD flag for all queries matching the specified domain
* setDNSSECPool(): move queries requesting DNSSEC processing to this pool
class LuaAction : public DNSAction
{
public:
- typedef std::function<std::tuple<int, string>(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, int len)> func_t;
+ typedef std::function<std::tuple<int, string>(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t len, uint16_t bufferSize)> func_t;
LuaAction(LuaAction::func_t func) : d_func(func)
{}
- Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
- auto ret = d_func(remote, qname, qtype, dh, len);
+ auto ret = d_func(remote, qname, qtype, dh, len, bufferSize);
if(ruleresult)
*ruleresult=std::get<1>(ret);
return (Action)std::get<0>(ret);
return std::shared_ptr<DNSAction>(new SpoofAction(ComboAddress(a)));
});
+ g_lua.writeFunction("SpoofCNAMEAction", [](const string& a) {
+ return std::shared_ptr<DNSAction>(new SpoofAction(a));
+ });
+
g_lua.writeFunction("addDomainSpoof", [](const std::string& domain, const std::string& ip, boost::optional<string> ip6) {
setLuaSideEffect();
SuffixMatchNode smn;
});
+ g_lua.writeFunction("addDomainCNAMESpoof", [](const std::string& domain, const std::string& cname) {
+ setLuaSideEffect();
+ SuffixMatchNode smn;
+ try
+ {
+ smn.add(DNSName(domain));
+ }
+ catch(std::exception& e) {
+ g_outputBuffer="Error parsing parameters: "+string(e.what());
+ return;
+ }
+ g_rulactions.modify([&smn,&cname](decltype(g_rulactions)::value_type& rulactions) {
+ rulactions.push_back({
+ std::make_shared<SuffixMatchNodeRule>(smn),
+ std::make_shared<SpoofAction>(cname) });
+ });
+ });
g_lua.writeFunction("DropAction", []() {
return std::shared_ptr<DNSAction>(new DropAction);
break;
}
- char queryBuffer[qlen];
+ /* if the query is small, allocate a bit more
+ memory to be able to spoof the content,
+ or to add ECS without allocating a new buffer */
+ size_t querySize = qlen <= 4096 ? qlen + 512 : qlen;
+ char queryBuffer[querySize];
const char* query = queryBuffer;
uint16_t queryLen = qlen;
- size_t querySize = qlen;
readn2WithTimeout(ci.fd, queryBuffer, queryLen, g_tcpRecvTimeout);
#ifdef HAVE_DNSCRYPT
std::shared_ptr<DnsCryptQuery> dnsCryptQuery = 0;
DNSAction::Action action=DNSAction::Action::None;
for(const auto& lr : *localRulactions) {
if(lr.first->matches(ci.remote, qname, qtype, dh, queryLen)) {
- action=(*lr.second)(ci.remote, qname, qtype, dh, queryLen, &ruleresult);
+ action=(*lr.second)(ci.remote, qname, qtype, dh, queryLen, querySize, &ruleresult);
if(action != DNSAction::Action::None) {
lr.first->d_matches++;
break;
for(const auto& lr : *localRulactions) {
if(lr.first->matches(remote, qname, qtype, dh, len)) {
- action=(*lr.second)(remote, qname, qtype, dh, len, &ruleresult);
+ action=(*lr.second)(remote, qname, qtype, dh, len, querySize, &ruleresult);
if(action != DNSAction::Action::None) {
lr.first->d_matches++;
break;
{
public:
enum class Action { Drop, Nxdomain, Spoof, Allow, HeaderModify, Pool, Delay, None};
- virtual Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const =0;
+ virtual Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const =0;
virtual string toString() const = 0;
};
class DropAction : public DNSAction
{
public:
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
return Action::Drop;
}
public:
QPSAction(int limit) : d_qps(limit, limit)
{}
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
if(d_qps.check())
return Action::Allow;
public:
DelayAction(int msec) : d_msec(msec)
{}
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
*ruleresult=std::to_string(d_msec);
return Action::Delay;
{
public:
PoolAction(const std::string& pool) : d_pool(pool) {}
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
*ruleresult=d_pool;
return Action::Pool;
{
public:
QPSPoolAction(unsigned int limit, const std::string& pool) : d_qps(limit, limit), d_pool(pool) {}
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
if(d_qps.check()) {
*ruleresult=d_pool;
{
public:
RCodeAction(int rcode) : d_rcode(rcode) {}
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
dh->rcode = d_rcode;
dh->qr = true; // for good measure
class TCAction : public DNSAction
{
public:
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
dh->tc = true;
dh->qr = true; // for good measure
public:
SpoofAction(const ComboAddress& a) : d_a(a) { d_aaaa.sin4.sin_family = 0;}
SpoofAction(const ComboAddress& a, const ComboAddress& aaaa) : d_a(a), d_aaaa(aaaa) {}
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ SpoofAction(const string& cname): d_cname(cname) { d_a.sin4.sin_family = 0; d_aaaa.sin4.sin_family = 0; }
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
- if((qtype == QType::A && d_a.sin4.sin_family == 0) ||
- (qtype == QType::AAAA && d_aaaa.sin4.sin_family == 0) || (qtype != QType::A && qtype != QType::AAAA))
+ if(d_cname.empty() &&
+ ((qtype == QType::A && d_a.sin4.sin_family == 0) ||
+ (qtype == QType::AAAA && d_aaaa.sin4.sin_family == 0) || (qtype != QType::A && qtype != QType::AAAA)))
return Action::None;
+ unsigned int consumed=0;
+ uint8_t rdatalen = 0;
+ if (!d_cname.empty()) {
+ qtype = QType::CNAME;
+ rdatalen = d_cname.wirelength();
+ } else {
+ rdatalen = qtype == QType::A ? 4 : 16;
+ }
+
+ const unsigned char recordstart[]={0xc0, 0x0c, // compressed name
+ 0, (unsigned char) qtype,
+ 0, QClass::IN, // IN
+ 0, 0, 0, 60, // TTL
+ 0, rdatalen};
+
+ DNSName ignore((char*)dh, len, sizeof(dnsheader), false, 0, 0, &consumed);
+
+ if (bufferSize < (sizeof(dnsheader) + consumed + 4 + sizeof(recordstart) + rdatalen)) {
+ return Action::None;
+ }
+
dh->qr = true; // for good measure
dh->ra = dh->rd; // for good measure
dh->ad = false;
dh->ancount = htons(1);
- dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
- unsigned int consumed=0;
-
- DNSName ignore((char*)dh, len, sizeof(dnsheader), false, 0, 0, &consumed);
+ dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
char* dest = ((char*)dh) +sizeof(dnsheader) + consumed + 4;
- uint8_t addrlen = qtype == QType::A ? 4 : 16;
-
- const unsigned char recordstart[]={0xc0, 0x0c, // compressed name
- 0, (unsigned char) qtype,
- 0, 1, // IN
- 0, 0, 0, 60, // TTL
- 0, addrlen};
memcpy(dest, recordstart, sizeof(recordstart));
if(qtype==QType::A)
memcpy(dest+sizeof(recordstart), &d_a.sin4.sin_addr.s_addr, 4);
- else
+ else if (qtype==QType::AAAA)
memcpy(dest+sizeof(recordstart), &d_aaaa.sin6.sin6_addr.s6_addr, 16);
- len = (dest + sizeof(recordstart) + addrlen) - (char*)dh;
+ else if (qtype==QType::CNAME) {
+ string wireData = d_cname.toDNSString();
+ memcpy(dest+sizeof(recordstart), wireData.c_str(), wireData.length());
+ }
+ len = (dest + sizeof(recordstart) + rdatalen) - (char*)dh;
return Action::HeaderModify;
}
string toString() const override
{
string ret;
- if(d_a.sin4.sin_family)
- ret="spoof in "+d_a.toString();
- if(d_aaaa.sin6.sin6_family) {
+ if(!d_cname.empty()) {
if(!ret.empty()) ret += ", ";
- ret+="spoof in "+d_aaaa.toString();
+ ret+="spoof in "+d_cname.toString();
+ } else {
+ if(d_a.sin4.sin_family)
+ ret="spoof in "+d_a.toString();
+ if(d_aaaa.sin6.sin6_family) {
+ if(!ret.empty()) ret += ", ";
+ ret+="spoof in "+d_aaaa.toString();
+ }
}
return ret;
}
private:
ComboAddress d_a;
ComboAddress d_aaaa;
+ DNSName d_cname;
};
class NoRecurseAction : public DNSAction
{
public:
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
dh->rd = false;
return Action::HeaderModify;
if(d_fp)
fclose(d_fp);
}
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
if(!d_fp) {
vinfolog("Packet from %s for %s %s with id %d", remote.toStringWithPort(), qname.toString(), QType(qtype).getName(), dh->id);
class DisableValidationAction : public DNSAction
{
public:
- DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+ DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
{
dh->cd = true;
return Action::HeaderModify;
else:
# unexpected query, or health check
response = dns.message.make_response(request)
- rrset = dns.rrset.from_text(request.question[0].name,
- 3600,
- request.question[0].rdclass,
- request.question[0].rdtype,
- '127.0.0.1')
- response.answer.append(rrset)
+ if request.question[0].rdclass == dns.rdataclass.IN:
+ if request.question[0].rdtype == dns.rdatatype.A:
+ rrset = dns.rrset.from_text(request.question[0].name,
+ 3600,
+ request.question[0].rdclass,
+ request.question[0].rdtype,
+ '127.0.0.1')
+ elif request.question[0].rdtype == dns.rdatatype.AAAA:
+ rrset = dns.rrset.from_text(request.question[0].name,
+ 3600,
+ request.question[0].rdclass,
+ request.question[0].rdtype,
+ '::1')
+ if rrset:
+ response.answer.append(rrset)
+
sock.sendto(response.to_wire(), addr)
sock.close()
else:
# unexpected query, or health check
response = dns.message.make_response(request)
- rrset = dns.rrset.from_text(request.question[0].name,
- 3600,
- request.question[0].rdclass,
- request.question[0].rdtype,
- '127.0.0.1')
- response.answer.append(rrset)
+ if request.question[0].rdclass == dns.rdataclass.IN:
+ if request.question[0].rdtype == dns.rdatatype.A:
+ rrset = dns.rrset.from_text(request.question[0].name,
+ 3600,
+ request.question[0].rdclass,
+ request.question[0].rdtype,
+ '127.0.0.1')
+ elif request.question[0].rdtype == dns.rdatatype.AAAA:
+ rrset = dns.rrset.from_text(request.question[0].name,
+ 3600,
+ request.question[0].rdclass,
+ request.question[0].rdtype,
+ '::1')
+ if rrset:
+ response.answer.append(rrset)
wire = response.to_wire()
conn.send(struct.pack("!H", len(wire)))
_config_template = """
addDomainSpoof("spoof.tests.powerdns.com.", "192.0.2.1", "2001:DB8::1")
+ addDomainCNAMESpoof("cnamespoof.tests.powerdns.com.", "cname.tests.powerdns.com.")
newServer{address="127.0.0.1:%s"}
"""
receivedResponse.id = expectedResponse.id
self.assertEquals(expectedResponse, receivedResponse)
+ def testSpoofCNAME(self):
+ """
+ Send an A query for "cnamespoof.tests.powerdns.com.",
+ check that dnsdist sends a spoofed result.
+ """
+ name = 'cnamespoof.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.CNAME,
+ 'cname.tests.powerdns.com.')
+ expectedResponse.answer.append(rrset)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=2.0)
+ self.assertTrue(receivedResponse)
+ receivedResponse.id = expectedResponse.id
+ self.assertEquals(expectedResponse, receivedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=2.0)
+ self.assertTrue(receivedResponse)
+ receivedResponse.id = expectedResponse.id
+ self.assertEquals(expectedResponse, receivedResponse)
class TestAdvancedPoolRouting(DNSDistTest):
self.assertEquals(query, receivedQuery)
self.assertEquals(response, receivedResponse)
- print(TestAdvancedRoundRobinLB._responsesCounter)
total = 0
for key in TestAdvancedRoundRobinLB._responsesCounter:
value = TestAdvancedRoundRobinLB._responsesCounter[key]