doLatencyStats(incomingProtocol, udiff);
}
-static void handleResponseForUDPClient(InternalQueryState& ids, PacketBuffer& response, const std::shared_ptr<DownstreamState>& backend, bool isAsync, bool selfGenerated)
+static void handleResponseTC4UDPClient(uint16_t udpPayloadSize, PacketBuffer& response, DNSResponse& dnsResponse)
{
- DNSResponse dnsResponse(ids, response, backend);
-
- if (ids.udpPayloadSize > 0 && response.size() > ids.udpPayloadSize) {
- vinfolog("Got a response of size %d while the initial UDP payload size was %d, truncating", response.size(), ids.udpPayloadSize);
+ if (udpPayloadSize == 0) {
+ uint16_t zValue = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(response.data()), response.size(), &udpPayloadSize, &zValue);
+ if (udpPayloadSize < 512) {
+ udpPayloadSize = 512;
+ }
+ }
+ if (response.size() > udpPayloadSize) {
+ vinfolog("Got a response of size %d while the initial UDP payload size was %d, truncating", response.size(), udpPayloadSize);
truncateTC(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength(), dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses);
dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
header.tc = true;
else if (dnsResponse.getHeader()->tc && dnsdist::configuration::getCurrentRuntimeConfiguration().d_truncateTC) {
truncateTC(response, dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength(), dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses);
}
+}
+
+static void handleResponseForUDPClient(InternalQueryState& ids, PacketBuffer& response, const std::shared_ptr<DownstreamState>& backend, bool isAsync, bool selfGenerated)
+{
+ DNSResponse dnsResponse(ids, response, backend);
+
+ handleResponseTC4UDPClient(ids.udpPayloadSize, response, dnsResponse);
/* when the answer is encrypted in place, we need to get a copy
of the original header before encryption to fill the ring buffer */
dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
}
+ // save UDP payload size from origin query
+ uint16_t udpPayloadSize = 0;
+ uint16_t zValue = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue);
+ if (udpPayloadSize < 512) {
+ udpPayloadSize = 512;
+ }
+
std::shared_ptr<DownstreamState> backend{nullptr};
auto result = processQuery(dnsQuestion, backend);
}
#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
#endif /* DISABLE_RECVMMSG */
+ /* ensure payload size is not exceeded */
+ DNSResponse dnsResponse(ids, query, nullptr);
+ handleResponseTC4UDPClient(udpPayloadSize, query, dnsResponse);
/* we use dest, always, because we don't want to use the listening address to send a response since it could be 0.0.0.0 */
sendUDPResponse(clientState.udpFD, query, dnsQuestion.ids.delayMsec, dest, remote);
import clientsubnetoption
import cookiesoption
import requests
+import random
+import string
from dnsdisttests import DNSDistTest, pickAvailablePort
class TestCaching(DNSDistTest):
sender = getattr(self, method)
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertEqual(receivedResponse, response)
+
+class TestCachingSkipAR(DNSDistTest):
+
+ _verboseMode = True
+ _testServerPort = pickAvailablePort()
+ _webTimeout = 2.0
+ _webServerPort = pickAvailablePort()
+ _webServerAPIKey = 'apisecret'
+ _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
+ _config_params = ['_webServerPort', '_webServerAPIKeyHashed', '_testServerPort']
+ _config_template = """
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({apiKey="%s"})
+ pc = newPacketCache(100, {maxTTL=86400, minTTL=1, skipHashingAR=true})
+ getPool(""):setCache(pc)
+ newServer{address="127.0.0.1:%d"}
+ """
+
+ def getPoolMetric(self, poolID, metricName):
+ headers = {'x-api-key': self._webServerAPIKey}
+ url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost'
+ r = requests.get(url, headers=headers, timeout=self._webTimeout)
+ self.assertTrue(r)
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue(r.json())
+ content = r.json()
+ self.assertIn('pools', content)
+ pools = content['pools']
+ self.assertGreater(len(pools), poolID)
+ pool = pools[poolID]
+ return int(pool[metricName])
+
+ def testCacheSkipAR(self):
+ """
+ Cache: Testing ``skipHashingAR`` parameter for caching
+
+ dnsdist is configured to cache entries with ``skipHashingAR`` turned on,
+ cache entry will be used even with/without AR section or different payload
+ size is used.
+ """
+ # testing with and without EDNS0 payload size
+ name1 = 'cached.cache.tests.powerdns.com.'
+ query1 = dns.message.make_query(name1, 'AAAA', 'IN')
+ query1_1 = dns.message.make_query(name1, 'AAAA', 'IN', payload=600)
+ response1 = dns.message.make_response(query1)
+ rrset1 = dns.rrset.from_text(name1,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response1.answer.append(rrset1)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query1, response1)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 0)
+ receivedQuery.id = query1.id
+ self.assertEqual(query1, receivedQuery)
+ self.assertEqual(receivedResponse, response1)
+
+ # same query shall hit cache
+ (_, receivedResponse) = self.sendUDPQuery(query1, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 1)
+ self.assertEqual(receivedResponse, response1)
+
+ # query1_1 shall also hit cache even it inlcudes Opt RR for payload size
+ (_, receivedResponse) = self.sendUDPQuery(query1_1, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 2)
+ self.assertEqual(len(receivedResponse.answer), 1)
+ self.assertEqual(receivedResponse.answer[0], rrset1)
+
+ # testing for large sized cache entry
+ name2 = 'bigcached.cache.tests.powerdns.com.'
+ cookieBytes = ''.join(random.choices(string.hexdigits, k=8)).lower().encode()
+ myCookie = dns.edns.CookieOption(client=cookieBytes, server=b'')
+ query2 = dns.message.make_query(name2, 'AAAA', 'IN', payload=4096)
+ query2_1 = dns.message.make_query(name2, 'AAAA', 'IN', payload=2000)
+ query2_2 = dns.message.make_query(name2, 'AAAA', 'IN', payload=1500, options=[myCookie])
+ query2_3 = dns.message.make_query(name2, 'AAAA', 'IN', payload=800)
+
+ response2 = dns.message.make_response(query2)
+ v6addr_list = []
+ for i in range(1,41):
+ v6addr_list.append(f'fe80:fe80:fe80:fe80::{i}')
+ rrset2 = dns.rrset.from_text_list(name2,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ v6addr_list)
+ response2.answer.append(rrset2) # reponse > 40x(16+10)=1040 bytes
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query2, response2)
+ self.assertTrue(receivedQuery)
+ self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 2)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query2.id
+ self.assertEqual(query2, receivedQuery)
+ self.assertEqual(receivedResponse, response2)
+
+ # same query shall hit cache
+ (_, receivedResponse) = self.sendUDPQuery(query2, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 3)
+ self.assertEqual(receivedResponse, response2)
+
+ # query2_1 shall also hit cache even it has a different payload size
+ (_, receivedResponse) = self.sendUDPQuery(query2_1, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 4)
+ self.assertEqual(len(receivedResponse.answer), 1)
+ self.assertEqual(receivedResponse.answer[0], rrset2)
+
+ # query2_2 shall also hit cache even it has a different payload size and EDNS COOKIE
+ (_, receivedResponse) = self.sendUDPQuery(query2_2, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 5)
+ self.assertEqual(len(receivedResponse.answer), 1)
+ self.assertEqual(receivedResponse.answer[0], rrset2)
+
+ # query2_3 shall also hit cache but truncated since payload size is not enough
+ (_, receivedResponse) = self.sendUDPQuery(query2_3, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 6)
+ self.assertEqual(len(receivedResponse.answer), 0)
+ self.assertEqual(receivedResponse.flags & dns.flags.TC, dns.flags.TC)