static const uint16_t ECSSourcePrefixV4 = 24;
static const uint16_t ECSSourcePrefixV6 = 56;
-static void validateQuery(const char * packet, size_t packetSize, bool hasEdns=true, bool hasXPF=false, uint16_t additionals=0)
+static void validateQuery(const char * packet, size_t packetSize, bool hasEdns=true, bool hasXPF=false, uint16_t additionals=0, uint16_t answers=0, uint16_t authorities=0)
{
MOADNSParser mdp(true, packet, packetSize);
BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, answers);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, authorities);
uint16_t expectedARCount = additionals + (hasEdns ? 1U : 0U) + (hasXPF ? 1U : 0U);
BOOST_CHECK_EQUAL(mdp.d_header.arcount, expectedARCount);
}
validateQuery(reinterpret_cast<char*>(query.data()), len, true, false, 1);
}
+BOOST_AUTO_TEST_CASE(replaceECSAfterAN) {
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ vector<uint8_t> query;
+ DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::ANSWER, true);
+ pw.commit();
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, 8);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ DNSPacketWriter::optvect_t opts;
+ opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+ uint16_t len = query.size();
+
+ /* large enough packet */
+ char packet[1500];
+ memcpy(packet, query.data(), query.size());
+
+ unsigned int consumed = 0;
+ uint16_t qtype;
+ DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+ BOOST_CHECK((size_t) len > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, len, true, false, 0, 1, 0);
+ validateECS(packet, len, remote);
+
+ /* not large enough packet */
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ len = query.size();
+ qname = DNSName(reinterpret_cast<char*>(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+ BOOST_CHECK_EQUAL((size_t) len, query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(reinterpret_cast<char*>(query.data()), len, true, false, 0, 1, 0);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSAfterAuth) {
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ vector<uint8_t> query;
+ DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::AUTHORITY, true);
+ pw.commit();
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, 8);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ DNSPacketWriter::optvect_t opts;
+ opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+ uint16_t len = query.size();
+
+ /* large enough packet */
+ char packet[1500];
+ memcpy(packet, query.data(), query.size());
+
+ unsigned int consumed = 0;
+ uint16_t qtype;
+ DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+ BOOST_CHECK((size_t) len > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, len, true, false, 0, 0, 1);
+ validateECS(packet, len, remote);
+
+ /* not large enough packet */
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ len = query.size();
+ qname = DNSName(reinterpret_cast<char*>(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+ BOOST_CHECK_EQUAL((size_t) len, query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(reinterpret_cast<char*>(query.data()), len, true, false, 0, 0, 1);
+}
+
BOOST_AUTO_TEST_CASE(replaceECSBetweenTwoRecords) {
bool ednsAdded = false;
bool ecsAdded = false;
validateQuery(reinterpret_cast<char*>(query.data()), len, true, false);
}
+
BOOST_AUTO_TEST_CASE(removeEDNSWhenFirst) {
DNSName name("www.powerdns.com.");
self.checkQueryEDNSWithECS(expectedQuery, receivedQuery, 2)
self.checkResponseEDNSWithECS(expectedResponse, receivedResponse, 2)
+ def testWithAnswerThenECS(self):
+ """
+ ECS: Record in answer followed by an existing EDNS with ECS
+
+ Send a query with a record in the answer section, EDNS and an existing ECS value.
+ Check that the query received by the responder
+ has a valid ECS value and that the response
+ received from dnsdist contains an EDNS pseudo-RR.
+ """
+ name = 'record-in-an-withecs.ecs.tests.powerdns.com.'
+ ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 24)
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ rewrittenEcso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,ecso,eco])
+ query.answer.append(rrset)
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,eco,rewrittenEcso])
+ expectedQuery.answer.append(rrset)
+
+ response = dns.message.make_response(expectedQuery)
+ response.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+ response.answer.append(rrset)
+ response.additional.append(rrset)
+ expectedResponse.answer.append(rrset)
+ expectedResponse.additional.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.checkQueryEDNSWithECS(expectedQuery, receivedQuery, 2)
+ self.checkResponseEDNSWithECS(expectedResponse, receivedResponse, 2)
+
+ def testWithAuthThenECS(self):
+ """
+ ECS: Record in authority followed by an existing EDNS with ECS
+
+ Send a query with a record in the authority section, EDNS and an existing ECS value.
+ Check that the query received by the responder
+ has a valid ECS value and that the response
+ received from dnsdist contains an EDNS pseudo-RR.
+ """
+ name = 'record-in-an-withecs.ecs.tests.powerdns.com.'
+ ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 24)
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ rewrittenEcso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,ecso,eco])
+ query.authority.append(rrset)
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,eco,rewrittenEcso])
+ expectedQuery.authority.append(rrset)
+
+ response = dns.message.make_response(expectedQuery)
+ response.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+ response.answer.append(rrset)
+ response.additional.append(rrset)
+ expectedResponse.answer.append(rrset)
+ expectedResponse.additional.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.checkQueryEDNSWithECS(expectedQuery, receivedQuery, 2)
+ self.checkResponseEDNSWithECS(expectedResponse, receivedResponse, 2)
+
def testWithEDNSNoECSFollowedByAnother(self):
"""
ECS: Existing EDNS without ECS, followed by another record