From: Remi Gacogne Date: Mon, 18 Nov 2019 16:14:04 +0000 (+0100) Subject: dnsdist: Support setting the value of AA, AD and RA when spoofing X-Git-Tag: auth-4.3.0-beta1~26^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=955b9377a29cc06541c895beb2f02a0607e438d6;p=thirdparty%2Fpdns.git dnsdist: Support setting the value of AA, AD and RA when spoofing --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 76b99967f5..6c0f78205d 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -569,8 +569,8 @@ const std::vector g_consoleKeywords{ { "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"}, { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"}, { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"}, - { "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" }, - { "SpoofCNAMEAction", true, "cname", "Forge a response with the specified CNAME value" }, + { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" }, + { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" }, { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" }, { "TagAction", true, "name, value", "set the tag named 'name' to the given value" }, { "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" }, diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index 0c3ae18a07..a60bd1f77b 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -444,8 +444,21 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu char* dest = ((char*)dq->dh) + dq->len; dq->dh->qr = true; // for good measure - dq->dh->ra = dq->dh->rd; // for good measure - dq->dh->ad = false; + if (d_setAA) { + dq->dh->aa = *d_setAA; + } + if (d_setAD) { + dq->dh->ad = *d_setAD; + } + else { + dq->dh->ad = false; + } + if (d_setRA) { + dq->dh->ra = *d_setRA; + } + else { + dq->dh->ra = dq->dh->rd; // for good measure + } dq->dh->ancount = 0; dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it @@ -1244,6 +1257,23 @@ static void addAction(GlobalStateHolder > *someRulActions, const luadn }); } +typedef std::unordered_map > spoofparams_t; + +static void parseSpoofConfig(boost::optional vars, boost::optional& setAA, boost::optional& setAD, boost::optional& setRA) +{ + if (vars) { + if (vars->count("aa")) { + setAA = boost::get((*vars)["aa"]); + } + if (vars->count("ad")) { + setAD = boost::get((*vars)["ad"]); + } + if (vars->count("ra")) { + setRA = boost::get((*vars)["ra"]); + } + } +} + void setupLuaActions() { g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr action, boost::optional params) { @@ -1336,7 +1366,7 @@ void setupLuaActions() return std::shared_ptr(new QPSPoolAction(limit, a)); }); - g_lua.writeFunction("SpoofAction", [](boost::variant>> inp, boost::optional b ) { + g_lua.writeFunction("SpoofAction", [](boost::variant>> inp, boost::optional b, boost::optional vars ) { vector addrs; if(auto s = boost::get(&inp)) addrs.push_back(ComboAddress(*s)); @@ -1345,13 +1375,46 @@ void setupLuaActions() for(const auto& a: v) addrs.push_back(ComboAddress(a.second)); } - if(b) + if(b) { addrs.push_back(ComboAddress(*b)); - return std::shared_ptr(new SpoofAction(addrs)); + } + + auto ret = std::shared_ptr(new SpoofAction(addrs)); + boost::optional setAA = boost::none; + boost::optional setAD = boost::none; + boost::optional setRA = boost::none; + parseSpoofConfig(vars, setAA, setAD, setRA); + auto sa = std::dynamic_pointer_cast(ret); + if (setAA) { + sa->setAA(*setAA); + } + if (setAD) { + sa->setAD(*setAD); + } + if (setRA) { + sa->setRA(*setRA); + } + return ret; }); - g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a) { - return std::shared_ptr(new SpoofAction(a)); + g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional vars) { + auto ret = std::shared_ptr(new SpoofAction(a)); + boost::optional setAA = boost::none; + boost::optional setAD = boost::none; + boost::optional setRA = boost::none; + parseSpoofConfig(vars, setAA, setAD, setRA); + auto sa = std::dynamic_pointer_cast(ret); + if (setAA) { + sa->setAA(*setAA); + } + if (setAD) { + sa->setAD(*setAD); + } + if (setRA) { + sa->setRA(*setRA); + } + return ret; + }); g_lua.writeFunction("DropAction", []() { diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index 827d6e1cfb..89f49c0ee9 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -72,9 +72,27 @@ public: } return ret; } + + void setAA(bool aa) + { + d_setAA = aa; + } + + void setAD(bool ad) + { + d_setAD = ad; + } + + void setRA(bool ra) + { + d_setRA = ra; + } private: std::vector d_addrs; DNSName d_cname; + boost::optional d_setAA{boost::none}; + boost::optional d_setAD{boost::none}; + boost::optional d_setRA{boost::none}; }; typedef boost::variant>, std::shared_ptr, DNSName, vector > > luadnsrule_t; diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 5dfd81549b..96af451cf7 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -1098,6 +1098,9 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s case DNSAction::Action::Truncate: dq.dh->tc = true; dq.dh->qr = true; + dq.dh->ra = dq.dh->rd; + dq.dh->aa = false; + dq.dh->ad = false; return true; break; case DNSAction::Action::HeaderModify: @@ -1185,6 +1188,9 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru vinfolog("Query from %s truncated because of dynamic block", dq.remote->toStringWithPort()); dq.dh->tc = true; dq.dh->qr = true; + dq.dh->ra = dq.dh->rd; + dq.dh->aa = false; + dq.dh->ad = false; return true; } else { @@ -1240,6 +1246,9 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru vinfolog("Query from %s for %s truncated because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString()); dq.dh->tc = true; dq.dh->qr = true; + dq.dh->ra = dq.dh->rd; + dq.dh->aa = false; + dq.dh->ad = false; return true; } else { diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index c1d1d10a0a..9a2d68aa31 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -1175,20 +1175,40 @@ The following actions exist. :param string message: The message to include -.. function:: SpoofAction(ip[, ip[...]]) - SpoofAction(ips) +.. function:: SpoofAction(ip [, options]) + SpoofAction(ips [, options]) + + .. versionchanged:: 1.5.0 + Added the optional parameter ``options``. Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses. If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in. :param string ip: An IPv4 and/or IPv6 address to spoof :param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof + :param table options: A table with key: value pairs with options. + + Options: + + * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. + * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. + * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query. -.. function:: SpoofCNAMEAction(cname) +.. function:: SpoofCNAMEAction(cname [, options]) + + .. versionchanged:: 1.5.0 + Added the optional parameter ``options``. Forge a response with the specified CNAME value. :param string cname: The name to respond with + :param table options: A table with key: value pairs with options. + + Options: + + * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. + * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. + * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query. .. function:: TagAction(name, value) diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index bb309b7638..3d9fb5716f 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -426,6 +426,8 @@ class TestAdvancedTruncateAnyAndTCP(DNSDistTest): """ name = 'anytruncatetcp.advanced.tests.powerdns.com.' query = dns.message.make_query(name, 'ANY', 'IN') + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD response = dns.message.make_response(query) rrset = dns.rrset.from_text(name, @@ -1239,6 +1241,8 @@ class TestAdvancedLuaTruncated(DNSDistTest): """ name = 'tc.advanced.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN') + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD response = dns.message.make_response(query) rrset = dns.rrset.from_text(name, 3600, diff --git a/regression-tests.dnsdist/test_Basics.py b/regression-tests.dnsdist/test_Basics.py index 24cbc549f0..79efaa0fc4 100644 --- a/regression-tests.dnsdist/test_Basics.py +++ b/regression-tests.dnsdist/test_Basics.py @@ -90,6 +90,8 @@ class TestBasics(DNSDistTest): """ name = 'any.tests.powerdns.com.' query = dns.message.make_query(name, 'ANY', 'IN') + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD expectedResponse = dns.message.make_response(query) expectedResponse.flags |= dns.flags.TC diff --git a/regression-tests.dnsdist/test_DynBlocks.py b/regression-tests.dnsdist/test_DynBlocks.py index 542eb218b9..fad4a22426 100644 --- a/regression-tests.dnsdist/test_DynBlocks.py +++ b/regression-tests.dnsdist/test_DynBlocks.py @@ -732,6 +732,8 @@ class TestDynBlockQPSActionTruncated(DNSDistTest): """ name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN') + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD response = dns.message.make_response(query) rrset = dns.rrset.from_text(name, 60, diff --git a/regression-tests.dnsdist/test_EDNSSelfGenerated.py b/regression-tests.dnsdist/test_EDNSSelfGenerated.py index fdaeec32a0..25984ecfcf 100644 --- a/regression-tests.dnsdist/test_EDNSSelfGenerated.py +++ b/regression-tests.dnsdist/test_EDNSSelfGenerated.py @@ -43,6 +43,8 @@ class TestEDNSSelfGenerated(DNSDistTest): name = 'no-edns.tc.edns-self.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN') + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD expectedResponse = dns.message.make_response(query) expectedResponse.flags |= dns.flags.TC @@ -95,6 +97,8 @@ class TestEDNSSelfGenerated(DNSDistTest): name = 'edns-no-do.tc.edns-self.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False) + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD expectedResponse = dns.message.make_response(query, our_payload=1042) expectedResponse.flags |= dns.flags.TC @@ -153,6 +157,8 @@ class TestEDNSSelfGenerated(DNSDistTest): name = 'edns-do.tc.edns-self.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True) + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD expectedResponse = dns.message.make_response(query, our_payload=1042) expectedResponse.flags |= dns.flags.TC @@ -212,6 +218,8 @@ class TestEDNSSelfGenerated(DNSDistTest): name = 'edns-options.tc.edns-self.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True) + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD expectedResponse = dns.message.make_response(query, our_payload=1042) expectedResponse.flags |= dns.flags.TC @@ -294,6 +302,8 @@ class TestEDNSSelfGeneratedDisabled(DNSDistTest): name = 'edns-no-do.tc.edns-self-disabled.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False) + # dnsdist sets RA = RD for TC responses + query.flags &= ~dns.flags.RD expectedResponse = dns.message.make_response(query) expectedResponse.flags |= dns.flags.TC