]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/test-packetcache_hh.cc
Merge pull request #11431 from jroessler-ox/docs-kskzskroll-update
[thirdparty/pdns.git] / pdns / test-packetcache_hh.cc
1 #ifndef BOOST_TEST_DYN_LINK
2 #define BOOST_TEST_DYN_LINK
3 #endif
4
5 #define BOOST_TEST_NO_MAIN
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 #include <boost/test/unit_test.hpp>
11
12 #include "dnswriter.hh"
13 #include "dnsrecords.hh"
14 #include "ednscookies.hh"
15 #include "ednssubnet.hh"
16 #include "packetcache.hh"
17
18 BOOST_AUTO_TEST_SUITE(packetcache_hh)
19
20 BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
21
22 /* auth version (ECS is not processed, we just hash the whole query except for the ID, while lowercasing the qname) */
23 const DNSName qname("www.powerdns.com.");
24 uint16_t qtype = QType::AAAA;
25 EDNSSubnetOpts opt;
26 DNSPacketWriter::optvect_t ednsOptions;
27 static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE };
28 static const std::unordered_set<uint16_t> noOptionsToSkip{ };
29
30 {
31 /* same query, different IDs */
32 vector<uint8_t> packet;
33 DNSPacketWriter pw1(packet, qname, qtype);
34 pw1.getHeader()->rd = true;
35 pw1.getHeader()->qr = false;
36 pw1.getHeader()->id = 0x42;
37 string spacket1((const char*)&packet[0], packet.size());
38 auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
39
40 packet.clear();
41 DNSPacketWriter pw2(packet, qname, qtype);
42 pw2.getHeader()->rd = true;
43 pw2.getHeader()->qr = false;
44 pw2.getHeader()->id = 0x84;
45 string spacket2((const char*)&packet[0], packet.size());
46 auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
47
48 BOOST_CHECK_EQUAL(hash1, hash2);
49 BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
50 }
51
52 {
53 /* same query, different IDs, different ECS, still hashes to the same value */
54 vector<uint8_t> packet;
55 DNSPacketWriter pw1(packet, qname, qtype);
56 pw1.getHeader()->rd = true;
57 pw1.getHeader()->qr = false;
58 pw1.getHeader()->id = 0x42;
59 opt.source = Netmask("10.0.59.220/32");
60 ednsOptions.clear();
61 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
62 pw1.addOpt(512, 0, 0, ednsOptions);
63 pw1.commit();
64
65 string spacket1((const char*)&packet[0], packet.size());
66 auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
67
68 packet.clear();
69 DNSPacketWriter pw2(packet, qname, qtype);
70 pw2.getHeader()->rd = true;
71 pw2.getHeader()->qr = false;
72 pw2.getHeader()->id = 0x84;
73 opt.source = Netmask("10.0.167.48/32");
74 ednsOptions.clear();
75 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
76 pw2.addOpt(512, 0, 0, ednsOptions);
77 pw2.commit();
78
79 string spacket2((const char*)&packet[0], packet.size());
80 auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
81
82 BOOST_CHECK_EQUAL(hash1, hash2);
83 /* the hash is the same but we should _not_ match */
84 BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
85
86 #if 0
87 /* to be able to compute a new collision if the hashing function is updated */
88 {
89 std::map<uint32_t, Netmask> colMap;
90 size_t collisions = 0;
91 size_t total = 0;
92
93 for (size_t idxA = 0; idxA < 256; idxA++) {
94 for (size_t idxB = 0; idxB < 256; idxB++) {
95 for (size_t idxC = 0; idxC < 256; idxC++) {
96 vector<uint8_t> secondQuery;
97 DNSPacketWriter pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
98 pwFQ.getHeader()->rd = 1;
99 pwFQ.getHeader()->qr = false;
100 pwFQ.getHeader()->id = 0x42;
101 opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
102 ednsOptions.clear();
103 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
104 pwFQ.addOpt(512, 0, 0, ednsOptions);
105 pwFQ.commit();
106 auto secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), optionsToSkip);
107 auto pair = colMap.emplace(secondKey, opt.source);
108 total++;
109 if (!pair.second) {
110 collisions++;
111 cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
112 goto done1;
113 }
114 }
115 }
116 }
117 done1:
118 cerr<<"collisions: "<<collisions<<endl;
119 cerr<<"total: "<<total<<endl;
120 }
121 #endif
122 }
123
124 {
125 /* same query but one has DNSSECOK, not the other, different IDs, different ECS, still hashes to the same value */
126 vector<uint8_t> packet;
127 DNSPacketWriter pw1(packet, qname, qtype);
128 pw1.getHeader()->rd = true;
129 pw1.getHeader()->qr = false;
130 pw1.getHeader()->id = 0x42;
131 opt.source = Netmask("10.0.41.6/32");
132 ednsOptions.clear();
133 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
134 pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
135 pw1.commit();
136
137 string spacket1((const char*)&packet[0], packet.size());
138 auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
139
140 packet.clear();
141 DNSPacketWriter pw2(packet, qname, qtype);
142 pw2.getHeader()->rd = true;
143 pw2.getHeader()->qr = false;
144 pw2.getHeader()->id = 0x84;
145 opt.source = Netmask("10.0.119.79/32");
146 ednsOptions.clear();
147 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
148 /* no EDNSOpts::DNSSECOK !! */
149 pw2.addOpt(512, 0, 0, ednsOptions);
150 pw2.commit();
151
152 string spacket2((const char*)&packet[0], packet.size());
153 auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
154
155 BOOST_CHECK_EQUAL(hash1, hash2);
156 /* the hash is the same but we should _not_ match */
157 BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
158 }
159
160 {
161 /* same query but different cookies, still hashes to the same value */
162 vector<uint8_t> packet;
163 DNSPacketWriter pw1(packet, qname, qtype);
164 pw1.getHeader()->rd = true;
165 pw1.getHeader()->qr = false;
166 pw1.getHeader()->id = 0x42;
167 opt.source = Netmask("192.0.2.1/32");
168 ednsOptions.clear();
169 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
170 EDNSCookiesOpt cookiesOpt(string("deadbeefdeadbeef"));
171 ednsOptions.emplace_back(EDNSOptionCode::COOKIE, cookiesOpt.makeOptString());
172 pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
173 pw1.commit();
174
175 string spacket1((const char*)&packet[0], packet.size());
176 auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
177
178 packet.clear();
179 DNSPacketWriter pw2(packet, qname, qtype);
180 pw2.getHeader()->rd = true;
181 pw2.getHeader()->qr = false;
182 pw2.getHeader()->id = 0x84;
183 opt.source = Netmask("192.0.2.1/32");
184 ednsOptions.clear();
185 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
186 cookiesOpt.makeFromString(string("deadbeefbadc0fee"));
187 ednsOptions.emplace_back(EDNSOptionCode::COOKIE, cookiesOpt.makeOptString());
188 pw2.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
189 pw2.commit();
190
191 string spacket2((const char*)&packet[0], packet.size());
192 auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
193
194 BOOST_CHECK_EQUAL(hash1, hash2);
195 /* the hash is the same but we should _not_ match */
196 BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, noOptionsToSkip));
197 /* but it does match if we skip cookies, though */
198 BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
199
200 #if 0
201 {
202 /* to be able to compute a new collision if the packet cache hashing code is updated */
203 std::map<uint32_t, Netmask> colMap;
204 size_t collisions = 0;
205 size_t total = 0;
206
207 for (size_t idxA = 0; idxA < 256; idxA++) {
208 for (size_t idxB = 0; idxB < 256; idxB++) {
209 for (size_t idxC = 0; idxC < 256; idxC++) {
210 vector<uint8_t> secondQuery;
211 DNSPacketWriter pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
212 pwFQ.getHeader()->rd = 1;
213 pwFQ.getHeader()->qr = false;
214 pwFQ.getHeader()->id = 0x42;
215 opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
216 ednsOptions.clear();
217 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
218 pwFQ.addOpt(512, 0, 32768, ednsOptions);
219 pwFQ.commit();
220 auto secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), optionsToSkip);
221 colMap.emplace(secondKey, opt.source);
222
223 secondQuery.clear();
224 DNSPacketWriter pwSQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
225 pwSQ.getHeader()->rd = 1;
226 pwSQ.getHeader()->qr = false;
227 pwSQ.getHeader()->id = 0x42;
228 opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
229 ednsOptions.clear();
230 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
231 pwSQ.addOpt(512, 0, 0, ednsOptions);
232 pwSQ.commit();
233 secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), optionsToSkip);
234
235 total++;
236 if (colMap.count(secondKey)) {
237 collisions++;
238 cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
239 goto done2;
240 }
241 }
242 }
243 }
244 done2:
245 cerr<<"collisions: "<<collisions<<endl;
246 cerr<<"total: "<<total<<endl;
247 }
248 #endif
249 }
250 }
251
252 BOOST_AUTO_TEST_CASE(test_PacketCacheRecSimple) {
253
254 const DNSName qname("www.powerdns.com.");
255 uint16_t qtype = QType::AAAA;
256 EDNSSubnetOpts opt;
257 DNSPacketWriter::optvect_t ednsOptions;
258 static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
259
260 {
261 vector<uint8_t> packet;
262 DNSPacketWriter pw1(packet, qname, qtype);
263 pw1.getHeader()->rd = true;
264 pw1.getHeader()->qr = false;
265 pw1.getHeader()->id = 0x42;
266 pw1.addOpt(512, 0, 0);
267 pw1.commit();
268
269 string spacket1((const char*)&packet[0], packet.size());
270 /* set the RD length to a large value */
271 unsigned char* ptr = reinterpret_cast<unsigned char*>(&spacket1.at(sizeof(dnsheader) + qname.wirelength() + /* qtype and qclass */ 4 + /* OPT root label (1), type (2), class (2) and ttl (4) */ 9));
272 *ptr = 255;
273 *(ptr + 1) = 255;
274 /* truncate the end of the OPT header to try to trigger an out of bounds read */
275 spacket1.resize(spacket1.size() - 6);
276 BOOST_CHECK_NO_THROW(PacketCache::canHashPacket(spacket1, optionsToSkip));
277 }
278 }
279
280 BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
281
282 /* rec version (ECS is processed, we hash the whole query except for the ID and the ECS value, while lowercasing the qname) */
283 const DNSName qname("www.powerdns.com.");
284 uint16_t qtype = QType::AAAA;
285 EDNSSubnetOpts opt;
286 DNSPacketWriter::optvect_t ednsOptions;
287 static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
288
289 {
290 /* same query, different IDs */
291 vector<uint8_t> packet;
292 DNSPacketWriter pw1(packet, qname, qtype);
293 pw1.getHeader()->rd = true;
294 pw1.getHeader()->qr = false;
295 pw1.getHeader()->id = 0x42;
296 string spacket1((const char*)&packet[0], packet.size());
297 auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
298
299 packet.clear();
300 DNSPacketWriter pw2(packet, qname, qtype);
301 pw2.getHeader()->rd = true;
302 pw2.getHeader()->qr = false;
303 pw2.getHeader()->id = 0x84;
304 string spacket2((const char*)&packet[0], packet.size());
305 auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
306
307 BOOST_CHECK_EQUAL(hash1, hash2);
308 BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
309 }
310
311 {
312 /* same query, different IDs, different ECS, still hashes to the same value */
313 vector<uint8_t> packet;
314 DNSPacketWriter pw1(packet, qname, qtype);
315 pw1.getHeader()->rd = true;
316 pw1.getHeader()->qr = false;
317 pw1.getHeader()->id = 0x42;
318 opt.source = Netmask("10.0.18.199/32");
319 ednsOptions.clear();
320 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
321 pw1.addOpt(512, 0, 0, ednsOptions);
322 pw1.commit();
323
324 string spacket1((const char*)&packet[0], packet.size());
325 auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
326
327 packet.clear();
328 DNSPacketWriter pw2(packet, qname, qtype);
329 pw2.getHeader()->rd = true;
330 pw2.getHeader()->qr = false;
331 pw2.getHeader()->id = 0x84;
332 opt.source = Netmask("10.0.131.66/32");
333 ednsOptions.clear();
334 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
335 pw2.addOpt(512, 0, 0, ednsOptions);
336 pw2.commit();
337
338 string spacket2((const char*)&packet[0], packet.size());
339 auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
340
341 BOOST_CHECK_EQUAL(hash1, hash2);
342 /* the hash is the same and we don't hash the ECS so we should match */
343 BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
344 }
345
346 {
347 /* same query but different cookies, still hashes to the same value */
348 vector<uint8_t> packet;
349 DNSPacketWriter pw1(packet, qname, qtype);
350 pw1.getHeader()->rd = true;
351 pw1.getHeader()->qr = false;
352 pw1.getHeader()->id = 0x42;
353 opt.source = Netmask("192.0.2.1/32");
354 ednsOptions.clear();
355 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
356 EDNSCookiesOpt cookiesOpt(string("deadbeefdead\x11\xee\x00\x00").c_str(), 16);
357 ednsOptions.emplace_back(EDNSOptionCode::COOKIE, cookiesOpt.makeOptString());
358 pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
359 pw1.commit();
360
361 string spacket1((const char*)&packet[0], packet.size());
362 auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
363
364 packet.clear();
365 DNSPacketWriter pw2(packet, qname, qtype);
366 pw2.getHeader()->rd = true;
367 pw2.getHeader()->qr = false;
368 pw2.getHeader()->id = 0x84;
369 opt.source = Netmask("192.0.2.1/32");
370 ednsOptions.clear();
371 ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
372 cookiesOpt.makeFromString(string("deadbeefdead\x67\x44\x00\x00").c_str(), 16);
373 ednsOptions.emplace_back(EDNSOptionCode::COOKIE, cookiesOpt.makeOptString());
374 pw2.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
375 pw2.commit();
376
377 string spacket2((const char*)&packet[0], packet.size());
378 auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
379
380 BOOST_CHECK_EQUAL(hash1, hash2);
381 /* the hash is the same but we should _not_ match, even though we skip the ECS part, because the cookies are different */
382 static const std::unordered_set<uint16_t> skipECSOnly{ EDNSOptionCode::ECS };
383 BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, skipECSOnly));
384
385 /* we do match if we skip the cookie as well */
386 BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
387 }
388 }
389
390 BOOST_AUTO_TEST_SUITE_END()