]>
Commit | Line | Data |
---|---|---|
1c2d079d | 1 | #ifndef BOOST_TEST_DYN_LINK |
a401a9be | 2 | #define BOOST_TEST_DYN_LINK |
1c2d079d FM |
3 | #endif |
4 | ||
a401a9be RG |
5 | #include <boost/test/unit_test.hpp> |
6 | ||
7 | #include "aggressive_nsec.hh" | |
8 | #include "test-syncres_cc.hh" | |
9 | ||
10 | BOOST_AUTO_TEST_SUITE(aggressive_nsec_cc) | |
11 | ||
171376f1 OM |
12 | BOOST_AUTO_TEST_CASE(test_small_coverering_nsec3) |
13 | { | |
14 | AggressiveNSECCache::s_maxNSEC3CommonPrefix = 1; | |
15 | ||
2b586ad0 OM |
16 | const std::vector<std::tuple<string, string, uint8_t, bool>> table = { |
17 | {"gujhshp2lhmnpoo9qde4blg4gq3hgl99", "gujhshp2lhmnpoo9qde4blg4gq3hgl9a", 157, true}, | |
18 | {"gujhshp2lhmnpoo9qde4blg4gq3hgl99", "gujhshp2lhmnpoo9qde4blg4gq3hgl9a", 158, false}, | |
19 | {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "vujhshp2lhmnpoo9qde4blg4gq3hgl9a", 0, false}, | |
20 | {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "7ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, true}, | |
21 | {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "7ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 2, false}, | |
22 | {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "fujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, false}, | |
23 | {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, false}, | |
3f6fb380 OM |
24 | {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 0, false}, |
25 | {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 1, false}, | |
26 | {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 157, false}, | |
171376f1 OM |
27 | }; |
28 | ||
2b586ad0 | 29 | for (const auto& [owner, next, boundary, result] : table) { |
171376f1 OM |
30 | AggressiveNSECCache::s_maxNSEC3CommonPrefix = boundary; |
31 | BOOST_CHECK_EQUAL(AggressiveNSECCache::isSmallCoveringNSEC3(DNSName(owner), fromBase32Hex(next)), result); | |
32 | } | |
33 | } | |
34 | ||
5a5c53b8 RG |
35 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain) |
36 | { | |
37 | std::unique_ptr<SyncRes> sr; | |
38 | initSR(sr, true); | |
edcf680e | 39 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); |
5a5c53b8 RG |
40 | |
41 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
42 | ||
43 | primeHints(); | |
44 | /* we first ask b.powerdns.com., get a NXD, then check that the aggressive | |
45 | NSEC cache will use the NSEC (a -> h) to prove that g.powerdns.com. does not exist | |
46 | either */ | |
47 | const DNSName target1("b.powerdns.com."); | |
48 | const DNSName target2("g.powerdns.com."); | |
49 | testkeysset_t keys; | |
50 | ||
51 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
52 | luaconfsCopy.dsAnchors.clear(); | |
53 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
54 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
55 | ||
56 | g_luaconfs.setState(luaconfsCopy); | |
57 | ||
58 | size_t queriesCount = 0; | |
59 | ||
8b428a6b | 60 | sr->setAsyncCallback([target1, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
5a5c53b8 RG |
61 | queriesCount++; |
62 | ||
63 | if (type == QType::DS || type == QType::DNSKEY) { | |
64 | if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) { | |
65 | /* no cut, NSEC, name does not exist (the generic version will generate an exact NSEC for the target, which we don't want) */ | |
66 | setLWResult(res, RCode::NoError, true, false, true); | |
67 | /* no data */ | |
68 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
69 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
70 | /* no record for this name */ | |
71 | addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("h.powerdns.com."), {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records); | |
72 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
73 | /* no wildcard either */ | |
74 | addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("a.powerdns.com."), {QType::AAAA, QType::RRSIG}, 600, res->d_records); | |
75 | addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); | |
76 | return LWResult::Result::Success; | |
77 | } | |
78 | else if (domain == DNSName("com.")) { | |
79 | /* no cut */ | |
80 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
81 | } | |
82 | else if (domain == DNSName("powerdns.com.")) { | |
83 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); | |
84 | } | |
85 | else { | |
86 | /* cut */ | |
87 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
88 | } | |
89 | } | |
90 | else { | |
91 | if (isRootServer(ip)) { | |
92 | setLWResult(res, 0, false, false, true); | |
93 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
94 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
95 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
96 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
97 | return LWResult::Result::Success; | |
98 | } | |
99 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
100 | if (domain == target1) { | |
101 | setLWResult(res, RCode::NXDomain, true, false, true); | |
102 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
103 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
104 | /* no record for this name */ | |
105 | addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("h.powerdns.com."), {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records); | |
106 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
107 | /* no wildcard either */ | |
108 | addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("a.powerdns.com."), {QType::AAAA, QType::RRSIG}, 600, res->d_records); | |
109 | addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); | |
110 | return LWResult::Result::Success; | |
111 | } | |
112 | } | |
113 | } | |
114 | ||
115 | return LWResult::Result::Timeout; | |
116 | }); | |
117 | ||
118 | vector<DNSRecord> ret; | |
119 | int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret); | |
120 | BOOST_CHECK_EQUAL(res, RCode::NXDomain); | |
121 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
122 | BOOST_REQUIRE_EQUAL(ret.size(), 6U); | |
7c1fe83b | 123 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
124 | |
125 | ret.clear(); | |
126 | res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret); | |
127 | BOOST_CHECK_EQUAL(res, RCode::NXDomain); | |
128 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
129 | BOOST_REQUIRE_EQUAL(ret.size(), 6U); | |
7c1fe83b | 130 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
131 | } |
132 | ||
133 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata) | |
134 | { | |
135 | std::unique_ptr<SyncRes> sr; | |
136 | initSR(sr, true); | |
edcf680e | 137 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); |
5a5c53b8 RG |
138 | |
139 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
140 | ||
141 | primeHints(); | |
142 | /* we first ask a.powerdns.com. | A, get a NODATA, then check that the aggressive | |
143 | NSEC cache will use the NSEC to prove that the AAAA does not exist either */ | |
144 | const DNSName target("a.powerdns.com."); | |
145 | testkeysset_t keys; | |
146 | ||
147 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
148 | luaconfsCopy.dsAnchors.clear(); | |
149 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
150 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
151 | ||
152 | g_luaconfs.setState(luaconfsCopy); | |
153 | ||
154 | size_t queriesCount = 0; | |
155 | ||
8b428a6b | 156 | sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
5a5c53b8 RG |
157 | queriesCount++; |
158 | ||
159 | if (type == QType::DS || type == QType::DNSKEY) { | |
160 | if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) { | |
161 | /* no cut, NSEC */ | |
162 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false); | |
163 | } | |
164 | else if (domain == DNSName("com.")) { | |
165 | /* no cut */ | |
166 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
167 | } | |
168 | else if (domain == DNSName("powerdns.com.")) { | |
169 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); | |
170 | } | |
171 | else { | |
172 | /* cut */ | |
173 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
174 | } | |
175 | } | |
176 | else { | |
177 | if (isRootServer(ip)) { | |
178 | setLWResult(res, 0, false, false, true); | |
179 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
180 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
181 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
182 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
183 | return LWResult::Result::Success; | |
184 | } | |
185 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
186 | if (domain == target && type == QType::A) { | |
187 | setLWResult(res, RCode::NoError, true, false, true); | |
188 | /* no data */ | |
189 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
190 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
191 | /* no record for this name */ | |
192 | /* exact match */ | |
193 | addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), {QType::TXT, QType::RRSIG}, 600, res->d_records); | |
194 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
195 | /* no need for wildcard in that case */ | |
196 | return LWResult::Result::Success; | |
197 | } | |
198 | } | |
199 | } | |
200 | ||
201 | return LWResult::Result::Timeout; | |
202 | }); | |
203 | ||
204 | vector<DNSRecord> ret; | |
205 | int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); | |
206 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
207 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
208 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
7c1fe83b | 209 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
210 | |
211 | ret.clear(); | |
212 | res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret); | |
213 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
214 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
215 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
7c1fe83b | 216 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
217 | } |
218 | ||
219 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata_wildcard) | |
220 | { | |
221 | std::unique_ptr<SyncRes> sr; | |
222 | initSR(sr, true); | |
edcf680e | 223 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); |
5a5c53b8 RG |
224 | |
225 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
226 | ||
227 | primeHints(); | |
228 | /* we first ask a.powerdns.com. | A, get a NODATA (no exact match but there is a wildcard match), | |
229 | then check that the aggressive NSEC cache will use the NSEC to prove that the AAAA does not exist either */ | |
230 | const DNSName target("a.powerdns.com."); | |
231 | testkeysset_t keys; | |
232 | ||
233 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
234 | luaconfsCopy.dsAnchors.clear(); | |
235 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
236 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
237 | ||
238 | g_luaconfs.setState(luaconfsCopy); | |
239 | ||
240 | size_t queriesCount = 0; | |
241 | ||
8b428a6b | 242 | sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
5a5c53b8 RG |
243 | queriesCount++; |
244 | ||
245 | if (type == QType::DS || type == QType::DNSKEY) { | |
246 | if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) { | |
247 | /* no cut, NSEC, name does not exist but there is a wildcard (the generic version will generate an exact NSEC for the target, which we don't want) */ | |
248 | setLWResult(res, RCode::NoError, true, false, true); | |
249 | /* no data */ | |
250 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
251 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
252 | /* the name does not exist, a wildcard applies but does not have this type */ | |
253 | addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::TXT, QType::RRSIG}, 600, res->d_records); | |
254 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com")); | |
255 | return LWResult::Result::Success; | |
256 | } | |
257 | else if (domain == DNSName("com.")) { | |
258 | /* no cut */ | |
259 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
260 | } | |
261 | else if (domain == DNSName("powerdns.com.")) { | |
262 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); | |
263 | } | |
264 | else { | |
265 | /* cut */ | |
266 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
267 | } | |
268 | } | |
269 | else { | |
270 | if (isRootServer(ip)) { | |
271 | setLWResult(res, 0, false, false, true); | |
272 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
273 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
274 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
275 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
276 | return LWResult::Result::Success; | |
277 | } | |
278 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
279 | if (domain == target && type == QType::A) { | |
280 | setLWResult(res, RCode::NoError, true, false, true); | |
281 | /* no data */ | |
282 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
283 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
284 | /* the name does not exist, a wildcard applies but does not have this type */ | |
285 | addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::TXT, QType::RRSIG}, 600, res->d_records); | |
286 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com")); | |
287 | return LWResult::Result::Success; | |
288 | } | |
289 | } | |
290 | } | |
291 | ||
292 | return LWResult::Result::Timeout; | |
293 | }); | |
294 | ||
295 | vector<DNSRecord> ret; | |
296 | int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); | |
297 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
298 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
299 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
7c1fe83b | 300 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
301 | |
302 | ret.clear(); | |
303 | res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret); | |
304 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
305 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
306 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
7c1fe83b | 307 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
308 | } |
309 | ||
d270600b RG |
310 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor) |
311 | { | |
312 | std::unique_ptr<SyncRes> sr; | |
313 | initSR(sr, true); | |
314 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); | |
315 | ||
316 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
317 | ||
318 | primeHints(); | |
319 | /* powerdns.com is signed, sub.powerdns.com. is not. | |
320 | We first get a query for sub.powerdns.com. which leads to an ancestor NSEC covering sub.powerdns.com.|DS to be inserted | |
321 | into the aggressive cache, check that we don't mistakenly use that later to prove that something else below that name | |
322 | doesn't exist either. */ | |
323 | const DNSName target("sub.powerdns.com."); | |
324 | testkeysset_t keys; | |
325 | ||
326 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
327 | luaconfsCopy.dsAnchors.clear(); | |
328 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
329 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
330 | ||
331 | g_luaconfs.setState(luaconfsCopy); | |
332 | ||
333 | size_t queriesCount = 0; | |
334 | ||
8b428a6b | 335 | sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
d270600b RG |
336 | queriesCount++; |
337 | ||
338 | if (type == QType::DS || type == QType::DNSKEY) { | |
339 | if (domain == DNSName("com.")) { | |
340 | /* no cut */ | |
341 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
342 | } | |
343 | else { | |
344 | /* cut */ | |
345 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
346 | } | |
347 | } | |
348 | else { | |
349 | if (isRootServer(ip)) { | |
350 | setLWResult(res, 0, false, false, true); | |
351 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
352 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
353 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
354 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
355 | return LWResult::Result::Success; | |
356 | } | |
357 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
358 | if (domain.isPartOf(DNSName("sub.powerdns.com."))) { | |
359 | setLWResult(res, 0, false, false, true); | |
360 | addRecordToLW(res, "sub.powerdns.com.", QType::NS, "ns.sub.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); | |
361 | /* proof that the DS doesn't exist follows */ | |
362 | /* NSEC ancestor for sub.powerdns.com */ | |
363 | addNSECRecordToLW(DNSName("sub.powerdns.com."), DNSName("sub1.powerdns.com"), {QType::NS}, 600, res->d_records); | |
364 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
365 | addRecordToLW(res, "ns.sub.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); | |
366 | return LWResult::Result::Success; | |
367 | } | |
368 | else if (domain == DNSName("sub16.powerdns.com.")) { | |
369 | setLWResult(res, RCode::NXDomain, true, false, true); | |
370 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
371 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
372 | addNSECRecordToLW(DNSName("sub15.powerdns.com."), DNSName("sub17.powerdns.com."), {QType::A}, 600, res->d_records); | |
373 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
374 | /* then the wildcard *.powerdns.com | |
375 | next covers the wildcard *.sub.powerdns.com | |
376 | */ | |
377 | addNSECRecordToLW(DNSName(").powerdns.com"), DNSName("+.sub.powerdns.com"), {QType::TXT, QType::RRSIG}, 600, res->d_records); | |
378 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
379 | return LWResult::Result::Success; | |
380 | } | |
381 | } | |
382 | else if (ip == ComboAddress("192.0.2.2:53")) { | |
383 | if (domain == target && type == QType::A) { | |
384 | setLWResult(res, RCode::NoError, true, false, true); | |
385 | addRecordToLW(res, DNSName("sub.powerdns.com."), QType::A, "192.0.2.42"); | |
386 | return LWResult::Result::Success; | |
387 | } | |
388 | else if (domain == DNSName("4.sub.powerdns.com.") && type == QType::A) { | |
389 | setLWResult(res, RCode::NoError, true, false, true); | |
390 | addRecordToLW(res, DNSName("4.sub.powerdns.com."), QType::A, "192.0.2.84"); | |
391 | return LWResult::Result::Success; | |
392 | } | |
393 | } | |
394 | } | |
395 | ||
396 | return LWResult::Result::Timeout; | |
397 | }); | |
398 | ||
399 | vector<DNSRecord> ret; | |
400 | int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); | |
401 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
402 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure); | |
403 | BOOST_REQUIRE_EQUAL(ret.size(), 1U); | |
404 | BOOST_CHECK_EQUAL(queriesCount, 5U); | |
405 | ||
406 | /* now we query sub16.powerdns.com, to get a NSEC covering the wildcard for *.sub.powerdns.com */ | |
407 | ret.clear(); | |
408 | res = sr->beginResolve(DNSName("sub16.powerdns.com."), QType(QType::A), QClass::IN, ret); | |
409 | BOOST_CHECK_EQUAL(res, RCode::NXDomain); | |
410 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
411 | BOOST_REQUIRE_EQUAL(ret.size(), 6U); | |
412 | BOOST_CHECK_EQUAL(queriesCount, 6U); | |
413 | ||
b48a269a | 414 | /* now we query other2.sub.powerdns.com, we should NOT be able to use the NSECs we have |
d270600b RG |
415 | to prove that the name does not exist */ |
416 | ret.clear(); | |
1eed7f45 | 417 | res = sr->beginResolve(DNSName("4.sub.powerdns.com"), QType(QType::DS), QClass::IN, ret); |
d270600b RG |
418 | BOOST_CHECK_EQUAL(res, RCode::NoError); |
419 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure); | |
420 | BOOST_REQUIRE_EQUAL(ret.size(), 1U); | |
421 | BOOST_CHECK_EQUAL(queriesCount, 8U); | |
422 | } | |
423 | ||
5a5c53b8 RG |
424 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis) |
425 | { | |
426 | std::unique_ptr<SyncRes> sr; | |
427 | initSR(sr, true); | |
edcf680e | 428 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); |
5a5c53b8 RG |
429 | |
430 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
431 | ||
432 | primeHints(); | |
8a64a821 RG |
433 | /* we first ask a.powerdns.com. | A, get an answer synthesized from the wildcard. |
434 | We can use it yet because we need the SOA, so let's request a non-existing type | |
5a5c53b8 RG |
435 | then check that the aggressive NSEC cache will use the wildcard to synthesize an answer |
436 | for b.powerdns.com */ | |
437 | const DNSName target("a.powerdns.com."); | |
438 | testkeysset_t keys; | |
439 | ||
440 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
441 | luaconfsCopy.dsAnchors.clear(); | |
442 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
443 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
444 | ||
445 | g_luaconfs.setState(luaconfsCopy); | |
446 | ||
447 | size_t queriesCount = 0; | |
448 | ||
8b428a6b | 449 | sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
5a5c53b8 RG |
450 | queriesCount++; |
451 | ||
452 | if (type == QType::DS || type == QType::DNSKEY) { | |
453 | if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) { | |
454 | /* no cut, NSEC, name does not exist but there is a wildcard (the generic version will generate an exact NSEC for the target, which we don't want) */ | |
455 | setLWResult(res, RCode::NoError, true, false, true); | |
456 | /* no data */ | |
457 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
458 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
459 | /* the name does not exist, a wildcard applies and have the requested type but no DS */ | |
460 | addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::A, QType::RRSIG}, 600, res->d_records); | |
461 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com")); | |
462 | return LWResult::Result::Success; | |
463 | } | |
464 | else if (domain == DNSName("com.")) { | |
465 | /* no cut */ | |
466 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
467 | } | |
468 | else if (domain == DNSName("powerdns.com.")) { | |
469 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); | |
470 | } | |
471 | else { | |
472 | /* cut */ | |
473 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
474 | } | |
475 | } | |
476 | else { | |
477 | if (isRootServer(ip)) { | |
478 | setLWResult(res, 0, false, false, true); | |
479 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
480 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
481 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
482 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
483 | return LWResult::Result::Success; | |
484 | } | |
485 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
8a64a821 RG |
486 | if (type == QType::A) { |
487 | setLWResult(res, RCode::NoError, true, false, true); | |
488 | addRecordToLW(res, domain, QType::A, "192.0.2.1"); | |
489 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com")); | |
490 | /* the name does not exist, a wildcard applies and has the requested type */ | |
491 | addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::A, QType::RRSIG}, 600, res->d_records); | |
492 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com")); | |
493 | return LWResult::Result::Success; | |
494 | } | |
495 | else if (type == QType::TXT) { | |
496 | setLWResult(res, RCode::NoError, true, false, true); | |
497 | /* the name does not exist, a wildcard applies but does not have the requested type */ | |
498 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY); | |
499 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
500 | addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::A, QType::RRSIG}, 600, res->d_records); | |
501 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com")); | |
502 | return LWResult::Result::Success; | |
503 | } | |
5a5c53b8 RG |
504 | } |
505 | } | |
506 | ||
507 | return LWResult::Result::Timeout; | |
508 | }); | |
509 | ||
510 | vector<DNSRecord> ret; | |
511 | int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); | |
512 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
513 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
514 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
515 | BOOST_CHECK_EQUAL(ret.at(0).d_name, target); | |
62ad1bd1 | 516 | BOOST_CHECK_EQUAL(ret.at(0).d_type, QType(QType::A).getCode()); |
7c1fe83b | 517 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 | 518 | |
8a64a821 RG |
519 | /* request the TXT to get the SOA */ |
520 | ret.clear(); | |
521 | res = sr->beginResolve(target, QType(QType::TXT), QClass::IN, ret); | |
522 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
523 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
524 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
525 | BOOST_CHECK_EQUAL(ret.at(0).d_name, DNSName("powerdns.com.")); | |
526 | BOOST_CHECK_EQUAL(ret.at(0).d_type, QType(QType::SOA).getCode()); | |
527 | BOOST_CHECK_EQUAL(queriesCount, 5U); | |
528 | ||
5a5c53b8 RG |
529 | ret.clear(); |
530 | res = sr->beginResolve(DNSName("b.powerdns.com."), QType(QType::A), QClass::IN, ret); | |
531 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
532 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
533 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
534 | BOOST_CHECK_EQUAL(ret.at(0).d_name, DNSName("b.powerdns.com.")); | |
62ad1bd1 | 535 | BOOST_CHECK_EQUAL(ret.at(0).d_type, QType(QType::A).getCode()); |
8a64a821 | 536 | BOOST_CHECK_EQUAL(queriesCount, 5U); |
5a5c53b8 RG |
537 | } |
538 | ||
a401a9be RG |
539 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain) |
540 | { | |
541 | std::unique_ptr<SyncRes> sr; | |
542 | initSR(sr, true); | |
dcf64a6e | 543 | AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159; |
edcf680e | 544 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); |
a401a9be RG |
545 | |
546 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
547 | ||
548 | primeHints(); | |
5a5c53b8 RG |
549 | /* we are lucky enough that our hashes will cover g.powerdns.com. as well, |
550 | so we first ask b.powerdns.com., get a NXD, then check that the aggressive | |
551 | NSEC cache will use the NSEC3 to prove that g.powerdns.com. does not exist | |
552 | either */ | |
a401a9be RG |
553 | const DNSName target1("b.powerdns.com."); |
554 | const DNSName target2("g.powerdns.com."); | |
555 | testkeysset_t keys; | |
556 | ||
557 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
558 | luaconfsCopy.dsAnchors.clear(); | |
559 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
560 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
561 | ||
562 | g_luaconfs.setState(luaconfsCopy); | |
563 | ||
564 | size_t queriesCount = 0; | |
565 | ||
8b428a6b | 566 | sr->setAsyncCallback([target1, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
a401a9be RG |
567 | queriesCount++; |
568 | ||
569 | if (type == QType::DS || type == QType::DNSKEY) { | |
570 | if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) { | |
571 | /* no cut, NSEC3 */ | |
fdc58f0c RG |
572 | setLWResult(res, RCode::NXDomain, true, false, true); |
573 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
574 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
575 | /* no record for this name */ | |
576 | /* first the closest encloser */ | |
577 | addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records); | |
578 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
579 | /* then the next closer */ | |
580 | addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records); | |
581 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
582 | /* no wildcard */ | |
583 | addNSEC3NarrowRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), {QType::AAAA, QType::RRSIG}, 600, res->d_records); | |
584 | addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); | |
585 | return LWResult::Result::Success; | |
a401a9be RG |
586 | } |
587 | else if (domain == DNSName("com.")) { | |
588 | /* no cut */ | |
589 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
590 | } | |
591 | else if (domain == DNSName("powerdns.com.")) { | |
592 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); | |
593 | } | |
594 | else { | |
595 | /* cut */ | |
596 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
597 | } | |
598 | } | |
599 | else { | |
600 | if (isRootServer(ip)) { | |
601 | setLWResult(res, 0, false, false, true); | |
602 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
603 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
604 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
605 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
606 | return LWResult::Result::Success; | |
607 | } | |
608 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
609 | if (domain == target1) { | |
610 | setLWResult(res, RCode::NXDomain, true, false, true); | |
a401a9be RG |
611 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); |
612 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
613 | /* no record for this name */ | |
614 | /* first the closest encloser */ | |
5a5c53b8 | 615 | addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records); |
a401a9be RG |
616 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
617 | /* then the next closer */ | |
5a5c53b8 | 618 | addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records); |
a401a9be RG |
619 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
620 | /* no wildcard */ | |
5a5c53b8 | 621 | addNSEC3NarrowRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), {QType::AAAA, QType::RRSIG}, 600, res->d_records); |
a401a9be RG |
622 | addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); |
623 | return LWResult::Result::Success; | |
624 | } | |
625 | } | |
626 | } | |
627 | ||
628 | return LWResult::Result::Timeout; | |
629 | }); | |
630 | ||
631 | vector<DNSRecord> ret; | |
632 | int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret); | |
633 | BOOST_CHECK_EQUAL(res, RCode::NXDomain); | |
634 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
635 | BOOST_REQUIRE_EQUAL(ret.size(), 8U); | |
7c1fe83b | 636 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
a401a9be RG |
637 | |
638 | ret.clear(); | |
639 | res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret); | |
640 | BOOST_CHECK_EQUAL(res, RCode::NXDomain); | |
641 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
642 | BOOST_REQUIRE_EQUAL(ret.size(), 8U); | |
7c1fe83b | 643 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
a401a9be RG |
644 | } |
645 | ||
a401a9be RG |
646 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata) |
647 | { | |
648 | std::unique_ptr<SyncRes> sr; | |
649 | initSR(sr, true); | |
edcf680e | 650 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); |
a401a9be RG |
651 | |
652 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
653 | ||
654 | primeHints(); | |
5a5c53b8 RG |
655 | /* we first ask a.powerdns.com. | A, get a NODATA, then check that the aggressive |
656 | NSEC cache will use the NSEC3 to prove that the AAAA does not exist either */ | |
657 | const DNSName target("a.powerdns.com."); | |
a401a9be RG |
658 | testkeysset_t keys; |
659 | ||
660 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
661 | luaconfsCopy.dsAnchors.clear(); | |
662 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
663 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
664 | ||
665 | g_luaconfs.setState(luaconfsCopy); | |
666 | ||
667 | size_t queriesCount = 0; | |
668 | ||
8b428a6b | 669 | sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
a401a9be RG |
670 | queriesCount++; |
671 | ||
672 | if (type == QType::DS || type == QType::DNSKEY) { | |
673 | if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) { | |
674 | /* no cut, NSEC3 */ | |
675 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false, boost::none, true); | |
676 | } | |
677 | else if (domain == DNSName("com.")) { | |
678 | /* no cut */ | |
5a5c53b8 RG |
679 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); |
680 | } | |
681 | else if (domain == DNSName("powerdns.com.")) { | |
682 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); | |
a401a9be RG |
683 | } |
684 | else { | |
685 | /* cut */ | |
686 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
687 | } | |
688 | } | |
689 | else { | |
690 | if (isRootServer(ip)) { | |
691 | setLWResult(res, 0, false, false, true); | |
692 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
693 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
694 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
695 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
696 | return LWResult::Result::Success; | |
697 | } | |
698 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
5a5c53b8 RG |
699 | if (domain == target && type == QType::A) { |
700 | setLWResult(res, RCode::NoError, true, false, true); | |
a401a9be | 701 | /* no data */ |
5a5c53b8 RG |
702 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); |
703 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
a401a9be | 704 | /* no record for this name */ |
5a5c53b8 RG |
705 | /* exact match */ |
706 | addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::TXT, QType::RRSIG}, 600, res->d_records); | |
707 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
708 | /* no need for next closer or wildcard in that case */ | |
709 | return LWResult::Result::Success; | |
710 | } | |
711 | } | |
712 | } | |
713 | ||
714 | return LWResult::Result::Timeout; | |
715 | }); | |
716 | ||
717 | vector<DNSRecord> ret; | |
718 | int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); | |
719 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
720 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
721 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
7c1fe83b | 722 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
723 | |
724 | ret.clear(); | |
725 | res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret); | |
726 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
727 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
728 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
7c1fe83b | 729 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
730 | } |
731 | ||
732 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata_wildcard) | |
733 | { | |
734 | std::unique_ptr<SyncRes> sr; | |
735 | initSR(sr, true); | |
dcf64a6e | 736 | AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159; |
edcf680e | 737 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); |
5a5c53b8 RG |
738 | |
739 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
740 | ||
741 | primeHints(); | |
742 | /* we first ask a.powerdns.com. | A, get a NODATA (no exact match but there is a wildcard match), | |
743 | then check that the aggressive NSEC cache will use the NSEC3 to prove that the AAAA does not exist either */ | |
744 | const DNSName target("a.powerdns.com."); | |
745 | testkeysset_t keys; | |
746 | ||
747 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
748 | luaconfsCopy.dsAnchors.clear(); | |
749 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
750 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
751 | ||
752 | g_luaconfs.setState(luaconfsCopy); | |
753 | ||
754 | size_t queriesCount = 0; | |
755 | ||
8b428a6b | 756 | sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
5a5c53b8 RG |
757 | queriesCount++; |
758 | ||
759 | if (type == QType::DS || type == QType::DNSKEY) { | |
760 | if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) { | |
761 | /* no cut, NSEC3, name does not exist but there is a wildcard (the generic version will generate an exact NSEC3 for the target, which we don't want) */ | |
762 | setLWResult(res, RCode::NoError, true, false, true); | |
763 | /* no data */ | |
764 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
765 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
766 | /* first the closest encloser */ | |
d270600b | 767 | addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 RG |
768 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
769 | /* then the next closer */ | |
d270600b | 770 | addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 RG |
771 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
772 | /* a wildcard applies but does not have this type */ | |
d270600b | 773 | addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::TXT, QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 RG |
774 | addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com")); |
775 | return LWResult::Result::Success; | |
776 | } | |
777 | else if (domain == DNSName("com.")) { | |
778 | /* no cut */ | |
779 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
780 | } | |
781 | else if (domain == DNSName("powerdns.com.")) { | |
782 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); | |
783 | } | |
784 | else { | |
785 | /* cut */ | |
786 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
787 | } | |
788 | } | |
789 | else { | |
790 | if (isRootServer(ip)) { | |
791 | setLWResult(res, 0, false, false, true); | |
792 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
793 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
794 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
795 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
796 | return LWResult::Result::Success; | |
797 | } | |
798 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
799 | if (domain == target && type == QType::A) { | |
800 | setLWResult(res, RCode::NoError, true, false, true); | |
801 | /* no data */ | |
802 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
803 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
a401a9be | 804 | /* first the closest encloser */ |
eca47c4d | 805 | addNSEC3NoDataNarrowRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 | 806 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
a401a9be | 807 | /* then the next closer */ |
eca47c4d | 808 | addNSEC3NarrowRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), {QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 RG |
809 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
810 | /* a wildcard applies but does not have this type */ | |
eca47c4d | 811 | addNSEC3NoDataNarrowRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), {QType::TXT, QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 | 812 | addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com")); |
a401a9be RG |
813 | return LWResult::Result::Success; |
814 | } | |
815 | } | |
816 | } | |
817 | ||
818 | return LWResult::Result::Timeout; | |
819 | }); | |
820 | ||
821 | vector<DNSRecord> ret; | |
822 | int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); | |
823 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
824 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
825 | BOOST_REQUIRE_EQUAL(ret.size(), 8U); | |
7c1fe83b | 826 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
a401a9be | 827 | |
a401a9be | 828 | ret.clear(); |
5a5c53b8 | 829 | res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret); |
a401a9be RG |
830 | BOOST_CHECK_EQUAL(res, RCode::NoError); |
831 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
832 | BOOST_REQUIRE_EQUAL(ret.size(), 8U); | |
7c1fe83b | 833 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 RG |
834 | } |
835 | ||
d270600b RG |
836 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor) |
837 | { | |
838 | std::unique_ptr<SyncRes> sr; | |
839 | initSR(sr, true); | |
840 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); | |
841 | ||
842 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
843 | ||
844 | primeHints(); | |
845 | /* powerdns.com is signed, sub.powerdns.com. is not. | |
846 | We first get a query for sub.powerdns.com. which leads to an ancestor NSEC3 covering sub.powerdns.com.|DS to be inserted | |
847 | into the aggressive cache, check that we don't mistakenly use that later to prove that something else below that name | |
848 | doesn't exist either. */ | |
849 | const DNSName target("sub.powerdns.com."); | |
850 | testkeysset_t keys; | |
851 | ||
852 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
853 | luaconfsCopy.dsAnchors.clear(); | |
854 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
855 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
856 | ||
857 | g_luaconfs.setState(luaconfsCopy); | |
858 | ||
859 | size_t queriesCount = 0; | |
860 | ||
8b428a6b | 861 | sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
d270600b RG |
862 | queriesCount++; |
863 | ||
864 | if (type == QType::DS || type == QType::DNSKEY) { | |
865 | if (domain == DNSName("com.")) { | |
866 | /* no cut */ | |
867 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
868 | } | |
869 | else { | |
870 | /* cut */ | |
871 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
872 | } | |
873 | } | |
874 | else { | |
875 | if (isRootServer(ip)) { | |
876 | setLWResult(res, 0, false, false, true); | |
877 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
878 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
879 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
880 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
881 | return LWResult::Result::Success; | |
882 | } | |
883 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
884 | if (domain.isPartOf(DNSName("sub.powerdns.com."))) { | |
885 | setLWResult(res, 0, false, false, true); | |
886 | addRecordToLW(res, "sub.powerdns.com.", QType::NS, "ns.sub.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); | |
887 | /* proof that the DS doesn't exist follows */ | |
888 | /* NSEC3 ancestor for sub.powerdns.com (1 additional iteration, deadbeef as salt), : 7v5rgf7okrmumvb8rscop0t3j1m5o4mb | |
889 | next is crafted to cover 4.sub.powerdns.com => 930v7tmju1s48fopjh5ktsp1jmagi20p */ | |
890 | addNSEC3RecordToLW(DNSName("7v5rgf7okrmumvb8rscop0t3j1m5o4mb.powerdns.com."), fromBase32Hex("930v7tmju1s48fopjh5ktsp1jmagi20q"), "deadbeef", 1, {QType::NS}, 600, res->d_records); | |
891 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
892 | addRecordToLW(res, "ns.sub.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); | |
893 | return LWResult::Result::Success; | |
894 | } | |
895 | else if (domain == DNSName("sub16.powerdns.com.")) { | |
896 | setLWResult(res, RCode::NXDomain, true, false, true); | |
897 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
898 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
899 | /* first the closest encloser */ | |
900 | addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::SOA, QType::NS}, 600, res->d_records, 1); | |
901 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
902 | /* then the next closer sub16.powerdns.com. */ | |
903 | addNSEC3NarrowRecordToLW(DNSName("sub16.powerdns.com."), DNSName("powerdns.com."), {QType::A}, 600, res->d_records, 1); | |
904 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
905 | /* then the wildcard *.powerdns.com: mtrrinpd8l9e7fmn8lp74o8dffnivs8i (minus one because NXD) | |
906 | next is crafted to cover the wildcard *.sub.powerdns.com (ocgb0ilk3g1m3olpms0q1quhn18nncc0) | |
907 | */ | |
908 | addNSEC3RecordToLW(DNSName("mtrrinpd8l9e7fmn8lp74o8dffnivs8h.powerdns.com."), fromBase32Hex("ocgb0ilk3g1m3olpms0q1quhn18nncc1"), "deadbeef", 1, {QType::TXT, QType::RRSIG}, 600, res->d_records); | |
909 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
910 | return LWResult::Result::Success; | |
911 | } | |
912 | } | |
913 | else if (ip == ComboAddress("192.0.2.2:53")) { | |
914 | if (domain == target && type == QType::A) { | |
915 | setLWResult(res, RCode::NoError, true, false, true); | |
916 | addRecordToLW(res, DNSName("sub.powerdns.com."), QType::A, "192.0.2.42"); | |
917 | return LWResult::Result::Success; | |
918 | } | |
919 | else if (domain == DNSName("4.sub.powerdns.com.") && type == QType::A) { | |
920 | setLWResult(res, RCode::NoError, true, false, true); | |
921 | addRecordToLW(res, DNSName("4.sub.powerdns.com."), QType::A, "192.0.2.84"); | |
922 | return LWResult::Result::Success; | |
923 | } | |
924 | } | |
925 | } | |
926 | ||
927 | return LWResult::Result::Timeout; | |
928 | }); | |
929 | ||
930 | vector<DNSRecord> ret; | |
931 | int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); | |
932 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
933 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure); | |
934 | BOOST_REQUIRE_EQUAL(ret.size(), 1U); | |
935 | BOOST_CHECK_EQUAL(queriesCount, 5U); | |
936 | ||
937 | /* now we query sub16.powerdns.com, to get a hash covering the wildcard for | |
938 | *.sub.powerdns.com */ | |
939 | ret.clear(); | |
940 | res = sr->beginResolve(DNSName("sub16.powerdns.com."), QType(QType::A), QClass::IN, ret); | |
941 | BOOST_CHECK_EQUAL(res, RCode::NXDomain); | |
942 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
943 | BOOST_REQUIRE_EQUAL(ret.size(), 8U); | |
944 | BOOST_CHECK_EQUAL(queriesCount, 6U); | |
945 | ||
946 | /* now we query other2.sub.powerdns.com, we should NOT be able to use the NSEC3s we have | |
947 | to prove that the name does not exist */ | |
948 | ret.clear(); | |
1eed7f45 | 949 | res = sr->beginResolve(DNSName("4.sub.powerdns.com"), QType(QType::DS), QClass::IN, ret); |
d270600b RG |
950 | BOOST_CHECK_EQUAL(res, RCode::NoError); |
951 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure); | |
952 | BOOST_REQUIRE_EQUAL(ret.size(), 1U); | |
953 | BOOST_CHECK_EQUAL(queriesCount, 8U); | |
954 | } | |
955 | ||
5a5c53b8 RG |
956 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis) |
957 | { | |
958 | std::unique_ptr<SyncRes> sr; | |
959 | initSR(sr, true); | |
edcf680e | 960 | g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000); |
5a5c53b8 RG |
961 | |
962 | setDNSSECValidation(sr, DNSSECMode::ValidateAll); | |
963 | ||
964 | primeHints(); | |
8a64a821 RG |
965 | /* we first ask a.powerdns.com. | A, get an answer synthesized from the wildcard. |
966 | We can't use it right away because we don't have the SOA, so let's do a TXT query to get it, | |
5a5c53b8 RG |
967 | then check that the aggressive NSEC cache will use the wildcard to synthesize an answer |
968 | for b.powerdns.com */ | |
969 | const DNSName target("a.powerdns.com."); | |
970 | testkeysset_t keys; | |
971 | ||
972 | auto luaconfsCopy = g_luaconfs.getCopy(); | |
973 | luaconfsCopy.dsAnchors.clear(); | |
974 | generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); | |
975 | generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); | |
976 | ||
977 | g_luaconfs.setState(luaconfsCopy); | |
978 | ||
979 | size_t queriesCount = 0; | |
980 | ||
8b428a6b | 981 | sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) { |
5a5c53b8 RG |
982 | queriesCount++; |
983 | ||
984 | if (type == QType::DS || type == QType::DNSKEY) { | |
985 | if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) { | |
986 | /* no cut, NSEC3, name does not exist but there is a wildcard (the generic version will generate an exact NSEC3 for the target, which we don't want) */ | |
987 | setLWResult(res, RCode::NoError, true, false, true); | |
988 | /* no data */ | |
989 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); | |
990 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
991 | /* first the closest encloser */ | |
d270600b | 992 | addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 RG |
993 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
994 | /* then the next closer */ | |
d270600b | 995 | addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 RG |
996 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
997 | /* a wildcard applies but does not have this type */ | |
d270600b | 998 | addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::RRSIG}, 600, res->d_records, 10); |
5a5c53b8 RG |
999 | addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com")); |
1000 | return LWResult::Result::Success; | |
1001 | } | |
1002 | else if (domain == DNSName("com.")) { | |
1003 | /* no cut */ | |
1004 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false); | |
1005 | } | |
1006 | else if (domain == DNSName("powerdns.com.")) { | |
1007 | return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); | |
1008 | } | |
1009 | else { | |
1010 | /* cut */ | |
1011 | return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); | |
1012 | } | |
1013 | } | |
1014 | else { | |
1015 | if (isRootServer(ip)) { | |
1016 | setLWResult(res, 0, false, false, true); | |
1017 | addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); | |
1018 | addDS(DNSName("powerdns.com."), 300, res->d_records, keys); | |
1019 | addRRSIG(keys, res->d_records, DNSName("."), 300); | |
1020 | addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); | |
1021 | return LWResult::Result::Success; | |
1022 | } | |
1023 | else if (ip == ComboAddress("192.0.2.1:53")) { | |
8a64a821 RG |
1024 | if (type == QType::A) { |
1025 | setLWResult(res, RCode::NoError, true, false, true); | |
1026 | addRecordToLW(res, domain, QType::A, "192.0.2.1"); | |
1027 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com")); | |
1028 | /* no need for the closest encloser since we have a positive answer expanded from a wildcard */ | |
1029 | /* the next closer */ | |
d270600b | 1030 | addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records, 10); |
8a64a821 RG |
1031 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
1032 | /* and of course we don't deny the wildcard itself */ | |
1033 | return LWResult::Result::Success; | |
1034 | } | |
1035 | else if (type == QType::TXT) { | |
1036 | setLWResult(res, RCode::NoError, true, false, true); | |
1037 | /* the name does not exist, a wildcard applies but does not have the requested type */ | |
1038 | addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY); | |
1039 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); | |
1040 | /* the closest encloser */ | |
d270600b | 1041 | addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "v", {QType::SOA, QType::NS, QType::NSEC3, QType::DNSKEY, QType::RRSIG}, 600, res->d_records, 10); |
8a64a821 RG |
1042 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
1043 | /* the next closer */ | |
d270600b | 1044 | addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records, 10); |
8a64a821 RG |
1045 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
1046 | /* and the wildcard expanded unto itself */ | |
d270600b | 1047 | addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "v", {QType::A}, 600, res->d_records, 10); |
8a64a821 RG |
1048 | addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); |
1049 | return LWResult::Result::Success; | |
1050 | } | |
5a5c53b8 RG |
1051 | } |
1052 | } | |
1053 | ||
1054 | return LWResult::Result::Timeout; | |
1055 | }); | |
1056 | ||
1057 | vector<DNSRecord> ret; | |
1058 | int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); | |
1059 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
1060 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
1061 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
1062 | BOOST_CHECK_EQUAL(ret.at(0).d_name, target); | |
62ad1bd1 | 1063 | BOOST_CHECK_EQUAL(ret.at(0).d_type, QType(QType::A).getCode()); |
7c1fe83b | 1064 | BOOST_CHECK_EQUAL(queriesCount, 4U); |
5a5c53b8 | 1065 | |
8a64a821 RG |
1066 | ret.clear(); |
1067 | res = sr->beginResolve(target, QType(QType::TXT), QClass::IN, ret); | |
1068 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
1069 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
1070 | BOOST_REQUIRE_EQUAL(ret.size(), 8U); | |
1071 | BOOST_CHECK_EQUAL(ret.at(0).d_name, DNSName("powerdns.com.")); | |
1072 | BOOST_CHECK_EQUAL(ret.at(0).d_type, QType(QType::SOA).getCode()); | |
1073 | BOOST_CHECK_EQUAL(queriesCount, 5U); | |
1074 | ||
5a5c53b8 RG |
1075 | ret.clear(); |
1076 | res = sr->beginResolve(DNSName("b.powerdns.com."), QType(QType::A), QClass::IN, ret); | |
1077 | BOOST_CHECK_EQUAL(res, RCode::NoError); | |
1078 | BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure); | |
1079 | BOOST_REQUIRE_EQUAL(ret.size(), 4U); | |
1080 | BOOST_CHECK_EQUAL(ret.at(0).d_name, DNSName("b.powerdns.com.")); | |
62ad1bd1 | 1081 | BOOST_CHECK_EQUAL(ret.at(0).d_type, QType(QType::A).getCode()); |
8a64a821 | 1082 | BOOST_CHECK_EQUAL(queriesCount, 5U); |
a401a9be | 1083 | } |
a401a9be | 1084 | |
f4408114 OM |
1085 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_replace) |
1086 | { | |
1087 | const size_t testSize = 10000; | |
1088 | auto cache = make_unique<AggressiveNSECCache>(testSize); | |
1089 | ||
dab9636b OM |
1090 | struct timeval now |
1091 | { | |
1092 | }; | |
f4408114 OM |
1093 | Utility::gettimeofday(&now, nullptr); |
1094 | ||
1095 | vector<DNSName> names; | |
1096 | names.reserve(testSize); | |
1097 | for (size_t i = 0; i < testSize; i++) { | |
1098 | names.emplace_back(std::to_string(i) + "powerdns.com"); | |
1099 | } | |
1100 | ||
1101 | DTime time; | |
1102 | time.set(); | |
1103 | ||
1104 | for (const auto& name : names) { | |
1105 | DNSRecord rec; | |
1106 | rec.d_name = name; | |
1107 | rec.d_type = QType::NSEC3; | |
1108 | rec.d_ttl = now.tv_sec + 10; | |
1109 | rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3")); | |
1110 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data"); | |
1111 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, true); | |
1112 | } | |
1113 | auto diff1 = time.udiff(true); | |
1114 | ||
1115 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), testSize); | |
1116 | for (const auto& name : names) { | |
1117 | DNSRecord rec; | |
1118 | rec.d_name = name; | |
1119 | rec.d_type = QType::NSEC3; | |
1120 | rec.d_ttl = now.tv_sec + 10; | |
1121 | rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3")); | |
1122 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data"); | |
1123 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, true); | |
1124 | } | |
1125 | ||
1126 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), testSize); | |
1127 | ||
1128 | auto diff2 = time.udiff(true); | |
1129 | // Check that replace is about equally fast as insert | |
10d5c4df | 1130 | BOOST_CHECK(diff1 < diff2 * 2 && diff2 < diff1 * 2); |
f4408114 OM |
1131 | } |
1132 | ||
5586c462 RG |
1133 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wiping) |
1134 | { | |
1135 | auto cache = make_unique<AggressiveNSECCache>(10000); | |
1136 | ||
1137 | struct timeval now; | |
1138 | Utility::gettimeofday(&now, 0); | |
1139 | ||
1140 | DNSRecord rec; | |
1141 | rec.d_name = DNSName("www.powerdns.com"); | |
1142 | rec.d_type = QType::NSEC; | |
1143 | rec.d_ttl = now.tv_sec + 10; | |
d06dcda4 | 1144 | rec.setContent(getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC")); |
5586c462 RG |
1145 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1146 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false); | |
1147 | ||
1148 | rec.d_name = DNSName("z.powerdns.com"); | |
d06dcda4 | 1149 | rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC")); |
5586c462 RG |
1150 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false); |
1151 | ||
1152 | rec.d_name = DNSName("www.powerdns.org"); | |
1153 | rec.d_type = QType::NSEC3; | |
1154 | rec.d_ttl = now.tv_sec + 10; | |
d06dcda4 | 1155 | rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3")); |
5586c462 RG |
1156 | rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1157 | cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true); | |
1158 | ||
9fdd68d1 | 1159 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U); |
5586c462 RG |
1160 | |
1161 | /* remove just that zone */ | |
1162 | cache->removeZoneInfo(DNSName("powerdns.org"), false); | |
9fdd68d1 | 1163 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U); |
5586c462 RG |
1164 | |
1165 | /* add it back */ | |
1166 | cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true); | |
9fdd68d1 | 1167 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U); |
5586c462 RG |
1168 | |
1169 | /* remove everything under .org (which should end up in the same way) */ | |
1170 | cache->removeZoneInfo(DNSName("org."), true); | |
9fdd68d1 | 1171 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U); |
5586c462 RG |
1172 | |
1173 | /* add it back */ | |
1174 | cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true); | |
9fdd68d1 | 1175 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U); |
5586c462 RG |
1176 | |
1177 | /* remove everything */ | |
1178 | cache->removeZoneInfo(DNSName("."), true); | |
9fdd68d1 | 1179 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 0U); |
5586c462 RG |
1180 | } |
1181 | ||
1182 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_pruning) | |
1183 | { | |
1184 | auto cache = make_unique<AggressiveNSECCache>(2); | |
1185 | ||
1186 | struct timeval now; | |
1187 | Utility::gettimeofday(&now, 0); | |
1188 | ||
1189 | DNSRecord rec; | |
1190 | rec.d_name = DNSName("www.powerdns.com"); | |
1191 | rec.d_type = QType::NSEC; | |
1192 | rec.d_ttl = now.tv_sec + 10; | |
d06dcda4 | 1193 | rec.setContent(getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC")); |
5586c462 RG |
1194 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1195 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false); | |
1196 | ||
1197 | rec.d_name = DNSName("z.powerdns.com"); | |
d06dcda4 | 1198 | rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC")); |
5586c462 RG |
1199 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false); |
1200 | ||
9fdd68d1 | 1201 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U); |
5586c462 RG |
1202 | /* we are at the limit of the number of entries, so we will scan 1/5th of the entries, |
1203 | and prune the expired ones, which mean we should not remove anything */ | |
1204 | cache->prune(now.tv_sec); | |
9fdd68d1 | 1205 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U); |
5586c462 RG |
1206 | |
1207 | rec.d_name = DNSName("www.powerdns.org"); | |
1208 | rec.d_type = QType::NSEC3; | |
f4408114 | 1209 | rec.d_ttl = now.tv_sec + 20; |
d06dcda4 | 1210 | rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3")); |
5586c462 RG |
1211 | rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1212 | cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true); | |
1213 | ||
9fdd68d1 | 1214 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U); |
5586c462 RG |
1215 | |
1216 | /* we have set a upper bound to 2 entries, so we are above, | |
198282a9 | 1217 | and one entry is actually expired, so we will prune one entry |
5586c462 | 1218 | to get below the limit */ |
f4408114 | 1219 | cache->prune(now.tv_sec + 15); |
9fdd68d1 | 1220 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U); |
5586c462 | 1221 | |
198282a9 OM |
1222 | /* now we are at the limit, so we will scan 1/10th of all zones entries, rounded up, |
1223 | and prune the expired ones, which mean we will also be removing the remaining two */ | |
5586c462 | 1224 | cache->prune(now.tv_sec + 600); |
9fdd68d1 | 1225 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 0U); |
5586c462 RG |
1226 | } |
1227 | ||
1228 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_dump) | |
1229 | { | |
1230 | auto cache = make_unique<AggressiveNSECCache>(10000); | |
1231 | ||
1232 | std::vector<std::string> expected; | |
993712a1 OM |
1233 | expected.emplace_back("; Zone powerdns.com.\n"); |
1234 | expected.emplace_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n"); | |
1235 | expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n"); | |
1236 | expected.emplace_back("z.powerdns.com. 10 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n"); | |
1237 | expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n"); | |
1238 | expected.emplace_back("; Zone powerdns.org.\n"); | |
1239 | expected.emplace_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n"); | |
1240 | expected.emplace_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n"); | |
1241 | ||
1242 | struct timeval now | |
1243 | { | |
1244 | }; | |
1245 | Utility::gettimeofday(&now, nullptr); | |
5586c462 RG |
1246 | |
1247 | DNSRecord rec; | |
1248 | rec.d_name = DNSName("www.powerdns.com"); | |
1249 | rec.d_type = QType::NSEC; | |
1250 | rec.d_ttl = now.tv_sec + 10; | |
d06dcda4 | 1251 | rec.setContent(getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC")); |
5586c462 RG |
1252 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1253 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false); | |
1254 | ||
1255 | rec.d_name = DNSName("z.powerdns.com"); | |
d06dcda4 | 1256 | rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC")); |
5586c462 RG |
1257 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false); |
1258 | ||
1259 | rec.d_name = DNSName("www.powerdns.org"); | |
1260 | rec.d_type = QType::NSEC3; | |
1261 | rec.d_ttl = now.tv_sec + 10; | |
d06dcda4 | 1262 | rec.setContent(getRecordContent(QType::NSEC3, "1 0 50 ab HASG==== A RRSIG NSEC3")); |
5586c462 RG |
1263 | rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1264 | cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true); | |
1265 | ||
9fdd68d1 | 1266 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U); |
5586c462 | 1267 | |
993712a1 OM |
1268 | auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose); |
1269 | if (!filePtr) { | |
5586c462 RG |
1270 | BOOST_FAIL("Temporary file could not be opened"); |
1271 | } | |
1272 | ||
993712a1 | 1273 | BOOST_CHECK_EQUAL(cache->dumpToFile(filePtr, now), 3U); |
5586c462 | 1274 | |
993712a1 | 1275 | rewind(filePtr.get()); |
5586c462 RG |
1276 | char* line = nullptr; |
1277 | size_t len = 0; | |
5586c462 | 1278 | |
993712a1 OM |
1279 | for (const auto& str : expected) { |
1280 | auto read = getline(&line, &len, filePtr.get()); | |
5586c462 RG |
1281 | if (read == -1) { |
1282 | BOOST_FAIL("Unable to read a line from the temp file"); | |
1283 | } | |
1284 | BOOST_CHECK_EQUAL(line, str); | |
1285 | } | |
1286 | ||
93b25e96 | 1287 | expected.clear(); |
993712a1 OM |
1288 | expected.emplace_back("; Zone powerdns.com.\n"); |
1289 | expected.emplace_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n"); | |
1290 | expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n"); | |
1291 | expected.emplace_back("z.powerdns.com. 30 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n"); | |
1292 | expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n"); | |
1293 | expected.emplace_back("; Zone powerdns.org.\n"); | |
1294 | expected.emplace_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n"); | |
1295 | expected.emplace_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n"); | |
93b25e96 OM |
1296 | |
1297 | rec.d_name = DNSName("z.powerdns.com"); | |
1298 | rec.d_type = QType::NSEC; | |
1299 | rec.d_ttl = now.tv_sec + 30; | |
1300 | rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC")); | |
1301 | rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data"); | |
1302 | cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false); | |
1303 | ||
993712a1 OM |
1304 | rewind(filePtr.get()); |
1305 | BOOST_CHECK_EQUAL(cache->dumpToFile(filePtr, now), 3U); | |
93b25e96 | 1306 | |
993712a1 | 1307 | rewind(filePtr.get()); |
93b25e96 | 1308 | |
993712a1 OM |
1309 | for (const auto& str : expected) { |
1310 | auto read = getline(&line, &len, filePtr.get()); | |
93b25e96 OM |
1311 | if (read == -1) { |
1312 | BOOST_FAIL("Unable to read a line from the temp file"); | |
1313 | } | |
1314 | BOOST_CHECK_EQUAL(line, str); | |
1315 | } | |
1316 | ||
5586c462 RG |
1317 | /* getline() allocates a buffer when called with a nullptr, |
1318 | then reallocates it when needed, but we need to free the | |
1319 | last allocation if any. */ | |
993712a1 | 1320 | free(line); // NOLINT: it's the API. |
5586c462 RG |
1321 | } |
1322 | ||
15e973d6 OM |
1323 | static bool getDenialWrapper(std::unique_ptr<AggressiveNSECCache>& cache, time_t now, const DNSName& name, const QType& qtype, const std::optional<int> expectedResult = std::nullopt, const std::optional<size_t> expectedRecordsCount = std::nullopt) |
1324 | { | |
1325 | int res; | |
1326 | std::vector<DNSRecord> results; | |
1327 | pdns::validation::ValidationContext validationContext; | |
1328 | validationContext.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(validationContext.d_nsec3IterationsRemainingQuota)>::max(); | |
1329 | bool found = cache->getDenial(now, name, qtype, results, res, ComboAddress("192.0.2.1"), boost::none, true, validationContext); | |
1330 | if (expectedResult) { | |
1331 | BOOST_CHECK_EQUAL(res, *expectedResult); | |
1332 | } | |
1333 | if (expectedRecordsCount) { | |
1334 | BOOST_CHECK_EQUAL(results.size(), *expectedRecordsCount); | |
1335 | } | |
1336 | return found; | |
1337 | } | |
1338 | ||
684b0a56 RG |
1339 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover) |
1340 | { | |
1341 | /* test that we don't compare a hash using the wrong (former) salt or iterations count in case of a rollover, | |
1342 | or when different servers use different parameters */ | |
dcf64a6e | 1343 | AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159; |
684b0a56 RG |
1344 | auto cache = make_unique<AggressiveNSECCache>(10000); |
1345 | g_recCache = std::make_unique<MemRecursorCache>(); | |
1346 | ||
1347 | const DNSName zone("powerdns.com"); | |
1348 | time_t now = time(nullptr); | |
1349 | ||
1350 | /* first we need a SOA */ | |
1351 | std::vector<DNSRecord> records; | |
1352 | time_t ttd = now + 30; | |
1353 | DNSRecord drSOA; | |
1354 | drSOA.d_name = zone; | |
1355 | drSOA.d_type = QType::SOA; | |
1356 | drSOA.d_class = QClass::IN; | |
d06dcda4 | 1357 | drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600")); |
684b0a56 RG |
1358 | drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation |
1359 | drSOA.d_place = DNSResourceRecord::ANSWER; | |
1360 | records.push_back(drSOA); | |
1361 | ||
1362 | g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure); | |
1363 | BOOST_CHECK_EQUAL(g_recCache->size(), 1U); | |
1364 | ||
1365 | std::string oldSalt = "ab"; | |
1366 | std::string newSalt = "cd"; | |
1367 | unsigned int oldIterationsCount = 2; | |
1368 | unsigned int newIterationsCount = 1; | |
1369 | DNSName name("www.powerdns.com"); | |
1370 | std::string hashed = hashQNameWithSalt(oldSalt, oldIterationsCount, name); | |
1371 | ||
1372 | DNSRecord rec; | |
1373 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1374 | rec.d_type = QType::NSEC3; | |
1375 | rec.d_ttl = now + 10; | |
1376 | ||
1377 | NSEC3RecordContent nrc; | |
1378 | nrc.d_algorithm = 1; | |
1379 | nrc.d_flags = 0; | |
1380 | nrc.d_iterations = oldIterationsCount; | |
1381 | nrc.d_salt = oldSalt; | |
1382 | nrc.d_nexthash = hashed; | |
1383 | incrementHash(nrc.d_nexthash); | |
45c1026d | 1384 | for (const auto& type : {QType::A}) { |
684b0a56 RG |
1385 | nrc.set(type); |
1386 | } | |
1387 | ||
d06dcda4 | 1388 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
684b0a56 RG |
1389 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1390 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1391 | ||
cf4ad993 | 1392 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); |
684b0a56 | 1393 | |
684b0a56 RG |
1394 | /* we can use the NSEC3s we have */ |
1395 | /* direct match */ | |
15e973d6 | 1396 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), true); |
684b0a56 RG |
1397 | |
1398 | DNSName other("other.powerdns.com"); | |
1399 | /* now we insert a new NSEC3, with a different salt, changing that value for the zone */ | |
1400 | hashed = hashQNameWithSalt(newSalt, oldIterationsCount, other); | |
1401 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1402 | rec.d_type = QType::NSEC3; | |
1403 | rec.d_ttl = now + 10; | |
1404 | nrc.d_algorithm = 1; | |
1405 | nrc.d_flags = 0; | |
1406 | nrc.d_iterations = oldIterationsCount; | |
1407 | nrc.d_salt = newSalt; | |
1408 | nrc.d_nexthash = hashed; | |
1409 | incrementHash(nrc.d_nexthash); | |
45c1026d | 1410 | for (const auto& type : {QType::A}) { |
684b0a56 RG |
1411 | nrc.set(type); |
1412 | } | |
1413 | ||
d06dcda4 | 1414 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
684b0a56 RG |
1415 | rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1416 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1417 | ||
1418 | /* the existing entries should have been cleared */ | |
cf4ad993 | 1419 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); |
684b0a56 RG |
1420 | |
1421 | /* we should be able to find a direct match for that name */ | |
1422 | /* direct match */ | |
15e973d6 | 1423 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other, QType::AAAA), true); |
684b0a56 RG |
1424 | |
1425 | /* but we should not be able to use the other NSEC3s */ | |
15e973d6 | 1426 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false); |
684b0a56 RG |
1427 | |
1428 | /* and the same thing but this time updating the iterations count instead of the salt */ | |
1429 | DNSName other2("other2.powerdns.com"); | |
1430 | hashed = hashQNameWithSalt(newSalt, newIterationsCount, other2); | |
1431 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1432 | rec.d_type = QType::NSEC3; | |
1433 | rec.d_ttl = now + 10; | |
1434 | nrc.d_algorithm = 1; | |
1435 | nrc.d_flags = 0; | |
1436 | nrc.d_iterations = newIterationsCount; | |
1437 | nrc.d_salt = newSalt; | |
1438 | nrc.d_nexthash = hashed; | |
1439 | incrementHash(nrc.d_nexthash); | |
45c1026d | 1440 | for (const auto& type : {QType::A}) { |
684b0a56 RG |
1441 | nrc.set(type); |
1442 | } | |
1443 | ||
d06dcda4 | 1444 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
684b0a56 RG |
1445 | rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data"); |
1446 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1447 | ||
1448 | /* the existing entries should have been cleared */ | |
cf4ad993 | 1449 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); |
684b0a56 RG |
1450 | |
1451 | /* we should be able to find a direct match for that name */ | |
1452 | /* direct match */ | |
15e973d6 | 1453 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other2, QType::AAAA), true); |
684b0a56 RG |
1454 | |
1455 | /* but we should not be able to use the other NSEC3s */ | |
15e973d6 | 1456 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other, QType::AAAA), false); |
684b0a56 RG |
1457 | } |
1458 | ||
48c54748 RG |
1459 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases) |
1460 | { | |
1461 | auto cache = make_unique<AggressiveNSECCache>(10000); | |
1462 | g_recCache = std::make_unique<MemRecursorCache>(); | |
1463 | ||
1464 | const DNSName zone("powerdns.com"); | |
1465 | time_t now = time(nullptr); | |
1466 | ||
1467 | /* first we need a SOA */ | |
1468 | std::vector<DNSRecord> records; | |
1469 | time_t ttd = now + 30; | |
1470 | DNSRecord drSOA; | |
1471 | drSOA.d_name = zone; | |
1472 | drSOA.d_type = QType::SOA; | |
1473 | drSOA.d_class = QClass::IN; | |
d06dcda4 | 1474 | drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600")); |
48c54748 RG |
1475 | drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation |
1476 | drSOA.d_place = DNSResourceRecord::ANSWER; | |
1477 | records.push_back(drSOA); | |
1478 | ||
1479 | g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure); | |
1480 | BOOST_CHECK_EQUAL(g_recCache->size(), 1U); | |
1481 | ||
1482 | { | |
1483 | cache = make_unique<AggressiveNSECCache>(10000); | |
1484 | /* insert a NSEC matching the exact name (apex) */ | |
1485 | DNSName name("sub.powerdns.com"); | |
1486 | DNSRecord rec; | |
1487 | rec.d_name = name; | |
1488 | rec.d_type = QType::NSEC; | |
1489 | rec.d_ttl = now + 10; | |
1490 | ||
1491 | NSECRecordContent nrc; | |
1492 | nrc.d_next = DNSName("sub1.powerdns.com"); | |
1493 | for (const auto& type : {QType::A}) { | |
1494 | nrc.set(type); | |
1495 | } | |
1496 | ||
d06dcda4 | 1497 | rec.setContent(std::make_shared<NSECRecordContent>(nrc)); |
48c54748 RG |
1498 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 sub.powerdns.com. data"); |
1499 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false); | |
1500 | ||
1501 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1502 | ||
1503 | /* the cache should now be able to deny other types (except the DS) */ | |
15e973d6 | 1504 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true); |
b48a269a | 1505 | /* but not the DS that lives in the parent zone */ |
15e973d6 | 1506 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false); |
48c54748 RG |
1507 | } |
1508 | ||
1509 | { | |
1510 | cache = make_unique<AggressiveNSECCache>(10000); | |
1511 | /* insert a NSEC matching the exact name, but it is an ancestor NSEC (delegation) */ | |
1512 | DNSName name("sub.powerdns.com"); | |
1513 | DNSRecord rec; | |
1514 | rec.d_name = name; | |
1515 | rec.d_type = QType::NSEC; | |
1516 | rec.d_ttl = now + 10; | |
1517 | ||
1518 | NSECRecordContent nrc; | |
1519 | nrc.d_next = DNSName("sub1.powerdns.com"); | |
1520 | for (const auto& type : {QType::NS}) { | |
1521 | nrc.set(type); | |
1522 | } | |
1523 | ||
d06dcda4 | 1524 | rec.setContent(std::make_shared<NSECRecordContent>(nrc)); |
48c54748 RG |
1525 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1526 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false); | |
1527 | ||
1528 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1529 | ||
1530 | /* the cache should now be able to deny the DS */ | |
15e973d6 | 1531 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true); |
48c54748 | 1532 | /* but not any type that lives in the child zone */ |
15e973d6 | 1533 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false); |
48c54748 RG |
1534 | } |
1535 | ||
1536 | { | |
1537 | cache = make_unique<AggressiveNSECCache>(10000); | |
1538 | /* insert a NSEC matching the exact name inside a zone (neither apex nor delegation point) */ | |
1539 | DNSName name("sub.powerdns.com"); | |
1540 | DNSRecord rec; | |
1541 | rec.d_name = name; | |
1542 | rec.d_type = QType::NSEC; | |
1543 | rec.d_ttl = now + 10; | |
1544 | ||
1545 | NSECRecordContent nrc; | |
1546 | nrc.d_next = DNSName("sub1.powerdns.com"); | |
1547 | for (const auto& type : {QType::A}) { | |
1548 | nrc.set(type); | |
1549 | } | |
1550 | ||
d06dcda4 | 1551 | rec.setContent(std::make_shared<NSECRecordContent>(nrc)); |
48c54748 RG |
1552 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1553 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false); | |
1554 | ||
1555 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1556 | ||
1557 | /* the cache should now be able to deny other types */ | |
15e973d6 | 1558 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true); |
48c54748 | 1559 | /* including the DS */ |
15e973d6 | 1560 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true); |
48c54748 RG |
1561 | } |
1562 | ||
1563 | { | |
1564 | /* nxd inside a zone (neither apex nor delegation point) */ | |
1565 | cache = make_unique<AggressiveNSECCache>(10000); | |
1566 | /* insert NSEC proving that the name does not exist */ | |
1567 | DNSName name("sub.powerdns.com."); | |
1568 | DNSName wc("*.powerdns.com."); | |
1569 | ||
1570 | { | |
1571 | DNSRecord rec; | |
1572 | rec.d_name = DNSName("sua.powerdns.com"); | |
1573 | rec.d_type = QType::NSEC; | |
1574 | rec.d_ttl = now + 10; | |
1575 | ||
1576 | NSECRecordContent nrc; | |
1577 | nrc.d_next = DNSName("suc.powerdns.com"); | |
1578 | for (const auto& type : {QType::A, QType::SOA, QType::NS}) { | |
1579 | nrc.set(type); | |
1580 | } | |
1581 | ||
d06dcda4 | 1582 | rec.setContent(std::make_shared<NSECRecordContent>(nrc)); |
48c54748 RG |
1583 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1584 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false); | |
1585 | ||
1586 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1587 | } | |
1588 | { | |
1589 | /* wildcard */ | |
1590 | DNSRecord rec; | |
1591 | rec.d_name = DNSName(").powerdns.com."); | |
1592 | rec.d_type = QType::NSEC; | |
1593 | rec.d_ttl = now + 10; | |
1594 | ||
1595 | NSECRecordContent nrc; | |
1596 | nrc.d_next = DNSName("+.powerdns.com."); | |
1597 | for (const auto& type : {QType::NS}) { | |
1598 | nrc.set(type); | |
1599 | } | |
1600 | ||
d06dcda4 | 1601 | rec.setContent(std::make_shared<NSECRecordContent>(nrc)); |
48c54748 RG |
1602 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1603 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false); | |
1604 | ||
1605 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U); | |
1606 | } | |
1607 | ||
1608 | /* the cache should now be able to deny any type for the name */ | |
15e973d6 | 1609 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NXDomain, 5U), true); |
48c54748 RG |
1610 | |
1611 | /* including the DS, since we are not at the apex */ | |
15e973d6 | 1612 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NXDomain, 5U), true); |
48c54748 RG |
1613 | } |
1614 | } | |
1615 | ||
1616 | BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases) | |
1617 | { | |
dcf64a6e | 1618 | AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159; |
48c54748 RG |
1619 | auto cache = make_unique<AggressiveNSECCache>(10000); |
1620 | g_recCache = std::make_unique<MemRecursorCache>(); | |
1621 | ||
1622 | const DNSName zone("powerdns.com"); | |
1623 | time_t now = time(nullptr); | |
1624 | ||
1625 | /* first we need a SOA */ | |
1626 | std::vector<DNSRecord> records; | |
1627 | time_t ttd = now + 30; | |
1628 | DNSRecord drSOA; | |
1629 | drSOA.d_name = zone; | |
1630 | drSOA.d_type = QType::SOA; | |
1631 | drSOA.d_class = QClass::IN; | |
d06dcda4 | 1632 | drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600")); |
48c54748 RG |
1633 | drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation |
1634 | drSOA.d_place = DNSResourceRecord::ANSWER; | |
1635 | records.push_back(drSOA); | |
1636 | ||
1637 | g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure); | |
1638 | BOOST_CHECK_EQUAL(g_recCache->size(), 1U); | |
1639 | ||
1640 | const std::string salt("ab"); | |
1641 | const unsigned int iterationsCount = 1; | |
1642 | ||
1643 | { | |
1644 | cache = make_unique<AggressiveNSECCache>(10000); | |
1645 | /* insert a NSEC3 matching the exact name (apex) */ | |
1646 | DNSName name("sub.powerdns.com"); | |
1647 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, name); | |
1648 | DNSRecord rec; | |
1649 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1650 | rec.d_type = QType::NSEC3; | |
1651 | rec.d_ttl = now + 10; | |
1652 | ||
1653 | NSEC3RecordContent nrc; | |
1654 | nrc.d_algorithm = 1; | |
1655 | nrc.d_flags = 0; | |
1656 | nrc.d_iterations = iterationsCount; | |
1657 | nrc.d_salt = salt; | |
1658 | nrc.d_nexthash = hashed; | |
1659 | incrementHash(nrc.d_nexthash); | |
1660 | for (const auto& type : {QType::A}) { | |
1661 | nrc.set(type); | |
1662 | } | |
1663 | ||
d06dcda4 | 1664 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1665 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 sub.powerdns.com. data"); |
1666 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1667 | ||
1668 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1669 | ||
1670 | /* the cache should now be able to deny other types (except the DS) */ | |
15e973d6 | 1671 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true); |
b48a269a | 1672 | /* but not the DS that lives in the parent zone */ |
15e973d6 | 1673 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false); |
48c54748 RG |
1674 | } |
1675 | ||
1676 | { | |
1677 | cache = make_unique<AggressiveNSECCache>(10000); | |
1678 | /* insert a NSEC3 matching the exact name, but it is an ancestor NSEC3 (delegation) */ | |
1679 | DNSName name("sub.powerdns.com"); | |
1680 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, name); | |
1681 | DNSRecord rec; | |
1682 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1683 | rec.d_type = QType::NSEC3; | |
1684 | rec.d_ttl = now + 10; | |
1685 | ||
1686 | NSEC3RecordContent nrc; | |
1687 | nrc.d_algorithm = 1; | |
1688 | nrc.d_flags = 0; | |
1689 | nrc.d_iterations = iterationsCount; | |
1690 | nrc.d_salt = salt; | |
1691 | nrc.d_nexthash = hashed; | |
1692 | incrementHash(nrc.d_nexthash); | |
1693 | for (const auto& type : {QType::NS}) { | |
1694 | nrc.set(type); | |
1695 | } | |
1696 | ||
d06dcda4 | 1697 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1698 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1699 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1700 | ||
1701 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1702 | ||
1703 | /* the cache should now be able to deny the DS */ | |
15e973d6 | 1704 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true); |
48c54748 | 1705 | /* but not any type that lives in the child zone */ |
15e973d6 | 1706 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false); |
48c54748 RG |
1707 | } |
1708 | ||
1709 | { | |
1710 | cache = make_unique<AggressiveNSECCache>(10000); | |
1711 | /* insert a NSEC3 matching the exact name inside a zone (neither apex nor delegation point) */ | |
1712 | DNSName name("sub.powerdns.com"); | |
1713 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, name); | |
1714 | DNSRecord rec; | |
1715 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1716 | rec.d_type = QType::NSEC3; | |
1717 | rec.d_ttl = now + 10; | |
1718 | ||
1719 | NSEC3RecordContent nrc; | |
1720 | nrc.d_algorithm = 1; | |
1721 | nrc.d_flags = 0; | |
1722 | nrc.d_iterations = iterationsCount; | |
1723 | nrc.d_salt = salt; | |
1724 | nrc.d_nexthash = hashed; | |
1725 | incrementHash(nrc.d_nexthash); | |
1726 | for (const auto& type : {QType::A}) { | |
1727 | nrc.set(type); | |
1728 | } | |
1729 | ||
d06dcda4 | 1730 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1731 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1732 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1733 | ||
1734 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1735 | ||
1736 | /* the cache should now be able to deny other types */ | |
15e973d6 | 1737 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true); |
48c54748 | 1738 | /* including the DS */ |
15e973d6 | 1739 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true); |
48c54748 RG |
1740 | } |
1741 | ||
1742 | { | |
1743 | /* nxd inside a zone (neither apex nor delegation point) */ | |
1744 | cache = make_unique<AggressiveNSECCache>(10000); | |
1745 | /* insert NSEC3s proving that the name does not exist */ | |
1746 | DNSName name("sub.powerdns.com."); | |
1747 | DNSName closestEncloser("powerdns.com."); | |
1748 | DNSName nextCloser("sub.powerdns.com."); | |
1749 | DNSName wc("*.powerdns.com."); | |
1750 | ||
1751 | { | |
1752 | /* closest encloser */ | |
1753 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, closestEncloser); | |
1754 | DNSRecord rec; | |
1755 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1756 | rec.d_type = QType::NSEC3; | |
1757 | rec.d_ttl = now + 10; | |
1758 | ||
1759 | NSEC3RecordContent nrc; | |
1760 | nrc.d_algorithm = 1; | |
1761 | nrc.d_flags = 0; | |
1762 | nrc.d_iterations = iterationsCount; | |
1763 | nrc.d_salt = salt; | |
1764 | nrc.d_nexthash = hashed; | |
1765 | incrementHash(nrc.d_nexthash); | |
1766 | for (const auto& type : {QType::A, QType::SOA, QType::NS}) { | |
1767 | nrc.set(type); | |
1768 | } | |
1769 | ||
d06dcda4 | 1770 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1771 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1772 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1773 | ||
1774 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1775 | } | |
1776 | { | |
1777 | /* next closer */ | |
1778 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, nextCloser); | |
1779 | decrementHash(hashed); | |
1780 | ||
1781 | DNSRecord rec; | |
1782 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1783 | rec.d_type = QType::NSEC3; | |
1784 | rec.d_ttl = now + 10; | |
1785 | ||
1786 | NSEC3RecordContent nrc; | |
1787 | nrc.d_algorithm = 1; | |
1788 | nrc.d_flags = 0; | |
1789 | nrc.d_iterations = iterationsCount; | |
1790 | nrc.d_salt = salt; | |
1791 | nrc.d_nexthash = hashed; | |
1792 | incrementHash(nrc.d_nexthash); | |
1793 | incrementHash(nrc.d_nexthash); | |
1794 | for (const auto& type : {QType::NS}) { | |
1795 | nrc.set(type); | |
1796 | } | |
1797 | ||
d06dcda4 | 1798 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1799 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1800 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1801 | ||
1802 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U); | |
1803 | } | |
1804 | { | |
1805 | /* wildcard */ | |
1806 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, wc); | |
1807 | decrementHash(hashed); | |
1808 | ||
1809 | DNSRecord rec; | |
1810 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1811 | rec.d_type = QType::NSEC3; | |
1812 | rec.d_ttl = now + 10; | |
1813 | ||
1814 | NSEC3RecordContent nrc; | |
1815 | nrc.d_algorithm = 1; | |
1816 | nrc.d_flags = 0; | |
1817 | nrc.d_iterations = iterationsCount; | |
1818 | nrc.d_salt = salt; | |
1819 | nrc.d_nexthash = hashed; | |
1820 | incrementHash(nrc.d_nexthash); | |
1821 | incrementHash(nrc.d_nexthash); | |
1822 | for (const auto& type : {QType::NS}) { | |
1823 | nrc.set(type); | |
1824 | } | |
1825 | ||
d06dcda4 | 1826 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1827 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1828 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1829 | ||
1830 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U); | |
1831 | } | |
1832 | ||
48c54748 | 1833 | /* the cache should now be able to deny any type for the name */ |
15e973d6 | 1834 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NXDomain, 7U), true); |
48c54748 | 1835 | /* including the DS, since we are not at the apex */ |
15e973d6 | 1836 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NXDomain, 7U), true); |
48c54748 RG |
1837 | } |
1838 | { | |
1839 | /* we insert NSEC3s coming from the parent zone that could look like a valid denial but are not */ | |
1840 | cache = make_unique<AggressiveNSECCache>(10000); | |
1841 | ||
1842 | DNSName name("www.sub.powerdns.com."); | |
1843 | DNSName closestEncloser("powerdns.com."); | |
1844 | DNSName nextCloser("sub.powerdns.com."); | |
1845 | DNSName wc("*.powerdns.com."); | |
1846 | ||
1847 | { | |
1848 | /* closest encloser */ | |
1849 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, closestEncloser); | |
1850 | DNSRecord rec; | |
1851 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1852 | rec.d_type = QType::NSEC3; | |
1853 | rec.d_ttl = now + 10; | |
1854 | ||
1855 | NSEC3RecordContent nrc; | |
1856 | nrc.d_algorithm = 1; | |
1857 | nrc.d_flags = 0; | |
1858 | nrc.d_iterations = iterationsCount; | |
1859 | nrc.d_salt = salt; | |
1860 | nrc.d_nexthash = hashed; | |
1861 | incrementHash(nrc.d_nexthash); | |
1862 | /* delegation ! */ | |
1863 | for (const auto& type : {QType::NS}) { | |
1864 | nrc.set(type); | |
1865 | } | |
1866 | ||
d06dcda4 | 1867 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1868 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1869 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1870 | ||
1871 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); | |
1872 | } | |
1873 | { | |
1874 | /* next closer */ | |
1875 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, nextCloser); | |
1876 | decrementHash(hashed); | |
1877 | ||
1878 | DNSRecord rec; | |
1879 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1880 | rec.d_type = QType::NSEC3; | |
1881 | rec.d_ttl = now + 10; | |
1882 | ||
1883 | NSEC3RecordContent nrc; | |
1884 | nrc.d_algorithm = 1; | |
1885 | nrc.d_flags = 0; | |
1886 | nrc.d_iterations = iterationsCount; | |
1887 | nrc.d_salt = salt; | |
1888 | nrc.d_nexthash = hashed; | |
1889 | incrementHash(nrc.d_nexthash); | |
1890 | incrementHash(nrc.d_nexthash); | |
1891 | for (const auto& type : {QType::A}) { | |
1892 | nrc.set(type); | |
1893 | } | |
1894 | ||
d06dcda4 | 1895 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1896 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1897 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1898 | ||
1899 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U); | |
1900 | } | |
1901 | { | |
1902 | /* wildcard */ | |
1903 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, wc); | |
1904 | decrementHash(hashed); | |
1905 | ||
1906 | DNSRecord rec; | |
1907 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1908 | rec.d_type = QType::NSEC3; | |
1909 | rec.d_ttl = now + 10; | |
1910 | ||
1911 | NSEC3RecordContent nrc; | |
1912 | nrc.d_algorithm = 1; | |
1913 | nrc.d_flags = 0; | |
1914 | nrc.d_iterations = iterationsCount; | |
1915 | nrc.d_salt = salt; | |
1916 | nrc.d_nexthash = hashed; | |
1917 | incrementHash(nrc.d_nexthash); | |
1918 | incrementHash(nrc.d_nexthash); | |
1919 | for (const auto& type : {QType::A}) { | |
1920 | nrc.set(type); | |
1921 | } | |
1922 | ||
d06dcda4 | 1923 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); |
48c54748 RG |
1924 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); |
1925 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1926 | ||
1927 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U); | |
1928 | } | |
1929 | ||
1930 | /* the cache should NOT be able to deny the name */ | |
15e973d6 | 1931 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, std::nullopt, 0U), false); |
48c54748 | 1932 | /* and the same for the DS */ |
15e973d6 OM |
1933 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false); |
1934 | } | |
1935 | } | |
1936 | ||
1937 | BOOST_AUTO_TEST_CASE(test_aggressive_max_nsec3_hash_cost) | |
1938 | { | |
1939 | AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159; | |
1940 | g_recCache = std::make_unique<MemRecursorCache>(); | |
1941 | ||
1942 | const DNSName zone("powerdns.com"); | |
1943 | time_t now = time(nullptr); | |
1944 | ||
1945 | /* first we need a SOA */ | |
1946 | std::vector<DNSRecord> records; | |
1947 | time_t ttd = now + 30; | |
1948 | DNSRecord drSOA; | |
1949 | drSOA.d_name = zone; | |
1950 | drSOA.d_type = QType::SOA; | |
1951 | drSOA.d_class = QClass::IN; | |
1952 | drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600")); | |
1953 | drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation | |
1954 | drSOA.d_place = DNSResourceRecord::ANSWER; | |
1955 | records.push_back(drSOA); | |
1956 | ||
1957 | g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure); | |
1958 | BOOST_CHECK_EQUAL(g_recCache->size(), 1U); | |
1959 | ||
1960 | auto insertNSEC3s = [zone, now](std::unique_ptr<AggressiveNSECCache>& cache, const std::string& salt, unsigned int iterationsCount) -> void { | |
1961 | { | |
1962 | /* insert a NSEC3 matching the apex (will be the closest encloser) */ | |
1963 | DNSName name("powerdns.com"); | |
1964 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, name); | |
1965 | DNSRecord rec; | |
1966 | rec.d_name = DNSName(toBase32Hex(hashed)) + zone; | |
1967 | rec.d_type = QType::NSEC3; | |
1968 | rec.d_ttl = now + 10; | |
1969 | ||
1970 | NSEC3RecordContent nrc; | |
1971 | nrc.d_algorithm = 1; | |
1972 | nrc.d_flags = 0; | |
1973 | nrc.d_iterations = iterationsCount; | |
1974 | nrc.d_salt = salt; | |
1975 | nrc.d_nexthash = hashed; | |
1976 | incrementHash(nrc.d_nexthash); | |
1977 | for (const auto& type : {QType::A}) { | |
1978 | nrc.set(type); | |
1979 | } | |
1980 | ||
1981 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); | |
1982 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); | |
1983 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
1984 | } | |
1985 | { | |
1986 | /* insert a NSEC3 matching *.powerdns.com (wildcard) */ | |
1987 | DNSName name("*.powerdns.com"); | |
1988 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, name); | |
1989 | auto before = hashed; | |
1990 | decrementHash(before); | |
1991 | DNSRecord rec; | |
1992 | rec.d_name = DNSName(toBase32Hex(before)) + zone; | |
1993 | rec.d_type = QType::NSEC3; | |
1994 | rec.d_ttl = now + 10; | |
1995 | ||
1996 | NSEC3RecordContent nrc; | |
1997 | nrc.d_algorithm = 1; | |
1998 | nrc.d_flags = 0; | |
1999 | nrc.d_iterations = iterationsCount; | |
2000 | nrc.d_salt = salt; | |
2001 | nrc.d_nexthash = hashed; | |
2002 | incrementHash(nrc.d_nexthash); | |
2003 | for (const auto& type : {QType::A}) { | |
2004 | nrc.set(type); | |
2005 | } | |
2006 | ||
2007 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); | |
2008 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); | |
2009 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
2010 | } | |
2011 | { | |
2012 | /* insert a NSEC3 matching sub.powerdns.com (next closer) */ | |
2013 | DNSName name("sub.powerdns.com"); | |
2014 | std::string hashed = hashQNameWithSalt(salt, iterationsCount, name); | |
2015 | auto before = hashed; | |
2016 | decrementHash(before); | |
2017 | DNSRecord rec; | |
2018 | rec.d_name = DNSName(toBase32Hex(before)) + zone; | |
2019 | rec.d_type = QType::NSEC3; | |
2020 | rec.d_ttl = now + 10; | |
2021 | ||
2022 | NSEC3RecordContent nrc; | |
2023 | nrc.d_algorithm = 1; | |
2024 | nrc.d_flags = 0; | |
2025 | nrc.d_iterations = iterationsCount; | |
2026 | nrc.d_salt = salt; | |
2027 | nrc.d_nexthash = hashed; | |
2028 | incrementHash(nrc.d_nexthash); | |
2029 | for (const auto& type : {QType::A}) { | |
2030 | nrc.set(type); | |
2031 | } | |
2032 | ||
2033 | rec.setContent(std::make_shared<NSEC3RecordContent>(nrc)); | |
2034 | auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data"); | |
2035 | cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true); | |
2036 | } | |
2037 | BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U); | |
2038 | }; | |
2039 | ||
2040 | { | |
2041 | /* zone with cheap parameters */ | |
2042 | const std::string salt; | |
2043 | const unsigned int iterationsCount = 0; | |
2044 | AggressiveNSECCache::s_nsec3DenialProofMaxCost = 10; | |
2045 | ||
2046 | auto cache = make_unique<AggressiveNSECCache>(10000); | |
2047 | insertNSEC3s(cache, salt, iterationsCount); | |
2048 | ||
2049 | /* the cache should now be able to deny everything below sub.powerdns.com, | |
2050 | IF IT DOES NOT EXCEED THE COST */ | |
2051 | { | |
2052 | /* short name: 10 labels below the zone apex */ | |
2053 | DNSName lookupName("a.b.c.d.e.f.g.h.i.sub.powerdns.com."); | |
2054 | BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 10U); | |
2055 | BOOST_CHECK_LE(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost); | |
2056 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA, RCode::NXDomain, 7U), true); | |
2057 | } | |
2058 | { | |
2059 | /* longer name: 11 labels below the zone apex */ | |
2060 | DNSName lookupName("a.b.c.d.e.f.g.h.i.j.sub.powerdns.com."); | |
2061 | BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 11U); | |
2062 | BOOST_CHECK_GT(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost); | |
2063 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA), false); | |
2064 | } | |
2065 | } | |
2066 | ||
2067 | { | |
2068 | /* zone with expensive parameters */ | |
2069 | const std::string salt("deadbeef"); | |
2070 | const unsigned int iterationsCount = 50; | |
2071 | AggressiveNSECCache::s_nsec3DenialProofMaxCost = 100; | |
2072 | ||
2073 | auto cache = make_unique<AggressiveNSECCache>(10000); | |
2074 | insertNSEC3s(cache, salt, iterationsCount); | |
2075 | ||
2076 | /* the cache should now be able to deny everything below sub.powerdns.com, | |
2077 | IF IT DOES NOT EXCEED THE COST */ | |
2078 | { | |
2079 | /* short name: 1 label below the zone apex */ | |
2080 | DNSName lookupName("sub.powerdns.com."); | |
2081 | BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 1U); | |
2082 | BOOST_CHECK_LE(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost); | |
2083 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA, RCode::NXDomain, 7U), true); | |
2084 | } | |
2085 | { | |
2086 | /* longer name: 2 labels below the zone apex */ | |
2087 | DNSName lookupName("a.sub.powerdns.com."); | |
2088 | BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 2U); | |
2089 | BOOST_CHECK_GT(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost); | |
2090 | BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA), false); | |
2091 | } | |
48c54748 RG |
2092 | } |
2093 | } | |
2094 | ||
a401a9be | 2095 | BOOST_AUTO_TEST_SUITE_END() |