query.reserve(dq->size);
query.assign((char*) dq->dh, len);
- if (!handleEDNSClientSubnet((char*) query.c_str(), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, *dq->remote, dq->ecsOverride, dq->ecsPrefixLength)) {
+ if (!handleEDNSClientSubnet(const_cast<char*>(query.c_str()), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, dq->ecsSet ? dq->ecs.getNetwork() : *dq->remote, dq->ecsOverride, dq->ecsSet ? dq->ecs.getBits() : dq->ecsPrefixLength)) {
return DNSAction::Action::None;
}
}
};
+class SetECSAction : public DNSAction
+{
+public:
+ SetECSAction(Netmask v4): d_v4(v4), d_hasV6(false)
+ {
+ }
+
+ SetECSAction(Netmask v4, Netmask v6): d_v4(v4), d_v6(v6), d_hasV6(true)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ {
+ dq->ecsSet = true;
+
+ if (d_hasV6) {
+ dq->ecs = dq->remote->isIPv4() ? d_v4 : d_v6;
+ }
+ else {
+ dq->ecs = d_v4;
+ }
+
+ return Action::None;
+ }
+
+ string toString() const override
+ {
+ string result = "set ECS to " + d_v4.toString();
+ if (d_hasV6) {
+ result += " / " + d_v6.toString();
+ }
+ return result;
+ }
+
+private:
+ Netmask d_v4;
+ Netmask d_v6;
+ bool d_hasV6;
+};
+
+
class DnstapLogAction : public DNSAction, public boost::noncopyable
{
public:
return std::shared_ptr<DNSAction>(new DisableECSAction());
});
+ g_lua.writeFunction("SetECSAction", [](const std::string v4, boost::optional<std::string> v6) {
+ if (v6) {
+ return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4), Netmask(*v6)));
+ }
+ return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4)));
+ });
+
g_lua.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
#ifdef HAVE_NET_SNMP
return std::shared_ptr<DNSAction>(new SNMPTrapAction(reason ? *reason : ""));
if (dq.useECS && ((ds && ds->useECS) || (!ds && serverPool->getECS()))) {
uint16_t newLen = dq.len;
- if (!handleEDNSClientSubnet(query, dq.size, consumed, &newLen, &ednsAdded, &ecsAdded, ci.remote, dq.ecsOverride, dq.ecsPrefixLength)) {
+ if (!handleEDNSClientSubnet(query, dq.size, consumed, &newLen, &ednsAdded, &ecsAdded, dq.ecsSet ? dq.ecs.getNetwork() : ci.remote, dq.ecsOverride, dq.ecsSet ? dq.ecs.getBits() : dq.ecsPrefixLength)) {
vinfolog("Dropping query from %s because we couldn't insert the ECS value", ci.remote.toStringWithPort());
goto drop;
}
bool ednsAdded = false;
bool ecsAdded = false;
if (dq.useECS && ((ss && ss->useECS) || (!ss && serverPool->getECS()))) {
- if (!handleEDNSClientSubnet(query, dq.size, consumed, &dq.len, &(ednsAdded), &(ecsAdded), remote, dq.ecsOverride, dq.ecsPrefixLength)) {
+ if (!handleEDNSClientSubnet(query, dq.size, consumed, &dq.len, &(ednsAdded), &(ecsAdded), dq.ecsSet ? dq.ecs.getNetwork() : remote, dq.ecsOverride, dq.ecsSet ? dq.ecs.getBits() : dq.ecsPrefixLength)) {
vinfolog("Dropping query from %s because we couldn't insert the ECS value", remote.toStringWithPort());
return;
}
#ifdef HAVE_PROTOBUF
boost::optional<boost::uuids::uuid> uniqueId;
#endif
+ Netmask ecs;
const DNSName* qname;
const uint16_t qtype;
const uint16_t qclass;
bool ecsOverride;
bool useECS{true};
bool addXPF{true};
+ bool ecsSet{false};
};
struct DNSResponse : DNSQuestion
:param string alterFunction: Name of a function to modify the contents of the logs before sending
:param bool includeCNAME: Whether or not to parse and export CNAMEs. Default false
+.. function:: SetECSAction(v4 [, v6])
+
+ .. versionadded:: 1.3.1
+
+ Set the ECS prefix and prefix length sent to backends to an arbitrary value.
+ If both IPv4 and IPv6 masks are supplied the IPv4 one will be used for IPv4 clients
+ and the IPv6 one for IPv6 clients. Otherwise the first mask is used for both, and
+ can actually be an IPv6 mask.
+ Subsequent rules are processed after this rule.
+
+ :param string v4: The IPv4 netmask, for example "192.0.2.1/32"
+ :param string v6: The IPv6 netmask, if any
+
.. function:: SkipCacheAction()
Don't lookup the cache for this query, don't store the answer.
ComboAddress src=eso.source.getNetwork();
src.truncate(esow.sourceMask);
-
+
if(family == htons(1))
ret.append((const char*) &src.sin4.sin_addr.s_addr, octetsout);
else
receivedQuery.id = expectedQuery.id
self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
self.checkResponseNoEDNS(expectedResponse, receivedResponse)
+
+class TestECSPrefixSetByRule(DNSDistTest):
+ """
+ dnsdist is configured to set the EDNS0 Client Subnet
+ option for incoming queries to the actual source IP,
+ but we override it for some queries via SetECSAction().
+ """
+
+ _config_template = """
+ setECSOverride(false)
+ setECSSourcePrefixV4(32)
+ setECSSourcePrefixV6(128)
+ newServer{address="127.0.0.1:%s", useClientSubnet=true}
+ addAction(makeRule("setecsaction.ecsrules.tests.powerdns.com."), SetECSAction("192.0.2.1/32"))
+ """
+
+ def testWithRegularECS(self):
+ """
+ ECS Prefix: not set
+ """
+ name = 'notsetecsaction.ecsrules.tests.powerdns.com.'
+ ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 32)
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512)
+ response = dns.message.make_response(query)
+ response.use_edns(edns=True, payload=4096, options=[ecso])
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
+ self.checkResponseNoEDNS(expectedResponse, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
+ self.checkResponseNoEDNS(expectedResponse, receivedResponse)
+
+ def testWithECSSetByRule(self):
+ """
+ ECS Prefix: set with SetECSAction
+ """
+ name = 'setecsaction.ecsrules.tests.powerdns.com.'
+ ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512)
+ response = dns.message.make_response(expectedQuery)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
+ self.checkResponseNoEDNS(expectedResponse, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
+ self.checkResponseNoEDNS(expectedResponse, receivedResponse)