]>
Commit | Line | Data |
---|---|---|
644dd1da | 1 | #include "dnsparser.hh" |
2 | #include "dnsrecords.hh" | |
4ba9d5dc | 3 | #include "ixfr.hh" |
644dd1da | 4 | #include "syncres.hh" |
39ec5d29 | 5 | #include "resolver.hh" |
6 | #include "logger.hh" | |
ad42489c | 7 | #include "rec-lua-conf.hh" |
4ba9d5dc RG |
8 | #include "rpzloader.hh" |
9 | #include "zoneparser-tng.hh" | |
644dd1da | 10 | |
11 | static Netmask makeNetmaskFromRPZ(const DNSName& name) | |
12 | { | |
13 | auto parts = name.getRawLabels(); | |
b8470add PL |
14 | /* |
15 | * why 2?, the minimally valid IPv6 address that can be encoded in an RPZ is | |
16 | * $NETMASK.zz (::/$NETMASK) | |
17 | * Terrible right? | |
18 | */ | |
19 | if(parts.size() < 2 || parts.size() > 9) | |
86f1af1c | 20 | throw PDNSException("Invalid IP address in RPZ: "+name.toLogString()); |
b8470add PL |
21 | |
22 | bool isV6 = (stoi(parts[0]) > 32); | |
23 | bool hadZZ = false; | |
24 | ||
25 | for (auto &part : parts) { | |
26 | // Check if we have an IPv4 octet | |
27 | for (auto c : part) | |
28 | if (!isdigit(c)) | |
29 | isV6 = true; | |
30 | ||
31 | if (pdns_iequals(part,"zz")) { | |
32 | if (hadZZ) | |
86f1af1c | 33 | throw PDNSException("more than one 'zz' label found in RPZ name"+name.toLogString()); |
b8470add PL |
34 | part = ""; |
35 | isV6 = true; | |
36 | hadZZ = true; | |
37 | } | |
38 | } | |
39 | ||
40 | if (isV6 && parts.size() < 9 && !hadZZ) | |
86f1af1c | 41 | throw PDNSException("No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: "+name.toLogString()); |
b8470add PL |
42 | |
43 | if (parts.size() == 5 && !isV6) | |
44 | return Netmask(parts[4]+"."+parts[3]+"."+parts[2]+"."+parts[1]+"/"+parts[0]); | |
45 | ||
46 | string v6; | |
47 | ||
48 | for (uint8_t i = parts.size()-1 ; i > 0; i--) { | |
49 | v6 += parts[i]; | |
50 | if (parts[i] == "" && i == 1 && i == parts.size()-1) | |
51 | v6+= "::"; | |
52 | if (parts[i] == "" && i != parts.size()-1) | |
53 | v6+= ":"; | |
54 | if (parts[i] != "" && i != 1) | |
55 | v6 += ":"; | |
56 | } | |
57 | v6 += "/" + parts[0]; | |
58 | ||
59 | return Netmask(v6); | |
644dd1da | 60 | } |
61 | ||
6b972d59 | 62 | void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL) |
644dd1da | 63 | { |
644dd1da | 64 | static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru."); |
644dd1da | 65 | static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"), |
66 | rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip."); | |
39c9bef5 | 67 | static const std::string rpzPrefix("rpz-"); |
39ec5d29 | 68 | |
1f1ca368 | 69 | DNSFilterEngine::Policy pol; |
39ec5d29 | 70 | |
ba3c54cb RG |
71 | if(dr.d_class != QClass::IN) { |
72 | return; | |
73 | } | |
74 | ||
39ec5d29 | 75 | if(dr.d_type == QType::CNAME) { |
ba3c54cb RG |
76 | auto crc = getRR<CNAMERecordContent>(dr); |
77 | if (!crc) { | |
78 | return; | |
79 | } | |
dd079764 | 80 | auto crcTarget=crc->getTarget(); |
ad42489c | 81 | if(defpol) { |
82 | pol=*defpol; | |
83 | } | |
dd079764 | 84 | else if(crcTarget.isRoot()) { |
39ec5d29 | 85 | // cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": "; |
86 | pol.d_kind = DNSFilterEngine::PolicyKind::NXDOMAIN; | |
dd079764 | 87 | } else if(crcTarget==g_wildcarddnsname) { |
39ec5d29 | 88 | // cerr<<"Wants NODATA for "<<dr.d_name<<": "; |
89 | pol.d_kind = DNSFilterEngine::PolicyKind::NODATA; | |
90 | } | |
dd079764 | 91 | else if(crcTarget==drop) { |
39ec5d29 | 92 | // cerr<<"Wants DROP for "<<dr.d_name<<": "; |
93 | pol.d_kind = DNSFilterEngine::PolicyKind::Drop; | |
94 | } | |
dd079764 | 95 | else if(crcTarget==truncate) { |
39ec5d29 | 96 | // cerr<<"Wants TRUNCATE for "<<dr.d_name<<": "; |
97 | pol.d_kind = DNSFilterEngine::PolicyKind::Truncate; | |
98 | } | |
dd079764 | 99 | else if(crcTarget==noaction) { |
39ec5d29 | 100 | // cerr<<"Wants NOACTION for "<<dr.d_name<<": "; |
101 | pol.d_kind = DNSFilterEngine::PolicyKind::NoAction; | |
102 | } | |
39c9bef5 RG |
103 | /* "The special RPZ encodings which are not to be taken as Local Data are |
104 | CNAMEs with targets that are: | |
105 | + "." (NXDOMAIN action), | |
106 | + "*." (NODATA action), | |
107 | + a top level domain starting with "rpz-", | |
108 | + a child of a top level domain starting with "rpz-". | |
109 | */ | |
110 | else if(!crcTarget.empty() && !crcTarget.isRoot() && crcTarget.getRawLabel(crcTarget.countLabels() - 1).compare(0, rpzPrefix.length(), rpzPrefix) == 0) { | |
111 | /* this is very likely an higher format number or a configuration error, | |
112 | let's just ignore it. */ | |
e6a9dde5 | 113 | g_log<<Logger::Info<<"Discarding unsupported RPZ entry "<<crcTarget<<" for "<<dr.d_name<<endl; |
39c9bef5 RG |
114 | return; |
115 | } | |
39ec5d29 | 116 | else { |
117 | pol.d_kind = DNSFilterEngine::PolicyKind::Custom; | |
118 | pol.d_custom = dr.d_content; | |
dd079764 | 119 | // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": "; |
39ec5d29 | 120 | } |
121 | } | |
122 | else { | |
db7dcbb1 RG |
123 | if (defpol) { |
124 | pol=*defpol; | |
125 | } | |
126 | else { | |
127 | pol.d_kind = DNSFilterEngine::PolicyKind::Custom; | |
128 | pol.d_custom = dr.d_content; | |
129 | // cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": "; | |
130 | } | |
39ec5d29 | 131 | } |
132 | ||
8f618901 RG |
133 | if (!defpol || defpol->d_ttl < 0) { |
134 | pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dr.d_ttl)); | |
135 | } else { | |
136 | pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl))); | |
137 | } | |
3876ee44 | 138 | |
39ec5d29 | 139 | // now to DO something with that |
39c9bef5 | 140 | |
39ec5d29 | 141 | if(dr.d_name.isPartOf(rpzNSDname)) { |
142 | DNSName filt=dr.d_name.makeRelative(rpzNSDname); | |
143 | if(addOrRemove) | |
6b972d59 | 144 | zone->addNSTrigger(filt, pol); |
39ec5d29 | 145 | else |
6b972d59 | 146 | zone->rmNSTrigger(filt, pol); |
6791663c | 147 | } else if(dr.d_name.isPartOf(rpzClientIP)) { |
b8470add PL |
148 | DNSName filt=dr.d_name.makeRelative(rpzClientIP); |
149 | auto nm=makeNetmaskFromRPZ(filt); | |
39ec5d29 | 150 | if(addOrRemove) |
6b972d59 | 151 | zone->addClientTrigger(nm, pol); |
39ec5d29 | 152 | else |
6b972d59 | 153 | zone->rmClientTrigger(nm, pol); |
39ec5d29 | 154 | |
6791663c | 155 | } else if(dr.d_name.isPartOf(rpzIP)) { |
39ec5d29 | 156 | // cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl; |
b8470add PL |
157 | DNSName filt=dr.d_name.makeRelative(rpzIP); |
158 | auto nm=makeNetmaskFromRPZ(filt); | |
39ec5d29 | 159 | if(addOrRemove) |
6b972d59 | 160 | zone->addResponseTrigger(nm, pol); |
39ec5d29 | 161 | else |
6b972d59 | 162 | zone->rmResponseTrigger(nm, pol); |
39ec5d29 | 163 | } else if(dr.d_name.isPartOf(rpzNSIP)) { |
b8470add PL |
164 | DNSName filt=dr.d_name.makeRelative(rpzNSIP); |
165 | auto nm=makeNetmaskFromRPZ(filt); | |
166 | if(addOrRemove) | |
6b972d59 | 167 | zone->addNSIPTrigger(nm, pol); |
b8470add | 168 | else |
6b972d59 | 169 | zone->rmNSIPTrigger(nm, pol); |
39ec5d29 | 170 | } else { |
171 | if(addOrRemove) | |
6b972d59 | 172 | zone->addQNameTrigger(dr.d_name, pol); |
39ec5d29 | 173 | else |
6b972d59 | 174 | zone->rmQNameTrigger(dr.d_name, pol); |
39ec5d29 | 175 | } |
176 | } | |
177 | ||
ea448a77 | 178 | shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout) |
39ec5d29 | 179 | { |
e6a9dde5 | 180 | g_log<<Logger::Warning<<"Loading RPZ zone '"<<zoneName<<"' from "<<master.toStringWithPort()<<endl; |
98c9ec39 | 181 | if(!tt.name.empty()) |
e6a9dde5 | 182 | g_log<<Logger::Warning<<"With TSIG key '"<<tt.name<<"' of algorithm '"<<tt.algo<<"'"<<endl; |
98c9ec39 | 183 | |
f6a8f7d7 PL |
184 | ComboAddress local(localAddress); |
185 | if (local == ComboAddress()) | |
186 | local = getQueryLocalAddress(master.sin4.sin_family, 0); | |
187 | ||
6b972d59 | 188 | AXFRRetriever axfr(master, zoneName, tt, &local, maxReceivedBytes); |
39ec5d29 | 189 | unsigned int nrecords=0; |
190 | Resolver::res_t nop; | |
191 | vector<DNSRecord> chunk; | |
192 | time_t last=0; | |
cb6218d2 PL |
193 | time_t axfrStart = time(nullptr); |
194 | time_t axfrNow = time(nullptr); | |
39ec5d29 | 195 | shared_ptr<SOARecordContent> sr; |
ea448a77 | 196 | while(axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) { |
39ec5d29 | 197 | for(auto& dr : chunk) { |
98c9ec39 | 198 | if(dr.d_type==QType::NS || dr.d_type==QType::TSIG) { |
199 | continue; | |
200 | } | |
201 | ||
6b972d59 | 202 | dr.d_name.makeUsRelative(zoneName); |
39ec5d29 | 203 | if(dr.d_type==QType::SOA) { |
ba3c54cb | 204 | sr = getRR<SOARecordContent>(dr); |
39ec5d29 | 205 | continue; |
206 | } | |
39ec5d29 | 207 | |
6b972d59 | 208 | RPZRecordToPolicy(dr, zone, true, defpol, maxTTL); |
39ec5d29 | 209 | nrecords++; |
210 | } | |
ea448a77 | 211 | axfrNow = time(nullptr); |
cb6218d2 | 212 | if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) { |
ea448a77 PL |
213 | throw PDNSException("Total AXFR time exceeded!"); |
214 | } | |
39ec5d29 | 215 | if(last != time(0)) { |
e6a9dde5 | 216 | g_log<<Logger::Info<<"Loaded & indexed "<<nrecords<<" policy records so far"<<endl; |
39ec5d29 | 217 | last=time(0); |
218 | } | |
219 | } | |
e6a9dde5 | 220 | g_log<<Logger::Info<<"Done: "<<nrecords<<" policy records active, SOA: "<<sr->getZoneRepresentation()<<endl; |
39ec5d29 | 221 | return sr; |
222 | } | |
223 | ||
c823f860 | 224 | // this function is silent - you do the logging |
6b972d59 | 225 | void loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL) |
39ec5d29 | 226 | { |
227 | ZoneParserTNG zpt(fname); | |
228 | DNSResourceRecord drr; | |
644dd1da | 229 | DNSName domain; |
230 | while(zpt.get(drr)) { | |
644dd1da | 231 | try { |
232 | if(drr.qtype.getCode() == QType::CNAME && drr.content.empty()) | |
233 | drr.content="."; | |
234 | DNSRecord dr(drr); | |
235 | if(dr.d_type == QType::SOA) { | |
6791663c RG |
236 | domain = dr.d_name; |
237 | zone->setDomain(domain); | |
644dd1da | 238 | } |
39ec5d29 | 239 | else if(dr.d_type == QType::NS) { |
240 | continue; | |
241 | } | |
242 | else { | |
644dd1da | 243 | dr.d_name=dr.d_name.makeRelative(domain); |
6b972d59 | 244 | RPZRecordToPolicy(dr, zone, true, defpol, maxTTL); |
644dd1da | 245 | } |
246 | } | |
6791663c | 247 | catch(const PDNSException& pe) { |
86f1af1c | 248 | throw PDNSException("Issue parsing '"+drr.qname.toLogString()+"' '"+drr.content+"' at "+zpt.getLineOfFile()+": "+pe.reason); |
644dd1da | 249 | } |
250 | } | |
644dd1da | 251 | } |
4ba9d5dc | 252 | |
20c37dec | 253 | static std::unordered_map<std::string, shared_ptr<rpzStats> > s_rpzStats; |
4ba9d5dc RG |
254 | static std::mutex s_rpzStatsMutex; |
255 | ||
20c37dec | 256 | shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone) |
4ba9d5dc RG |
257 | { |
258 | std::lock_guard<std::mutex> l(s_rpzStatsMutex); | |
20c37dec PL |
259 | if (s_rpzStats.find(zone) == s_rpzStats.end()) { |
260 | s_rpzStats[zone] = std::make_shared<rpzStats>(); | |
261 | } | |
4ba9d5dc RG |
262 | return s_rpzStats[zone]; |
263 | } | |
264 | ||
265 | static void incRPZFailedTransfers(const std::string& zone) | |
266 | { | |
20c37dec PL |
267 | auto stats = getRPZZoneStats(zone); |
268 | if (stats != nullptr) | |
269 | stats->d_failedTransfers++; | |
4ba9d5dc RG |
270 | } |
271 | ||
272 | static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool wasAXFR) | |
273 | { | |
20c37dec PL |
274 | auto stats = getRPZZoneStats(zone); |
275 | if (stats == nullptr) | |
276 | return; | |
277 | stats->d_successfulTransfers++; | |
4ba9d5dc | 278 | if (wasAXFR) { |
20c37dec | 279 | stats->d_fullTransfers++; |
4ba9d5dc | 280 | } |
20c37dec PL |
281 | stats->d_lastUpdate = time(nullptr); |
282 | stats->d_serial = serial; | |
283 | stats->d_numberOfRecords = numberOfRecords; | |
4ba9d5dc RG |
284 | } |
285 | ||
286 | void RPZIXFRTracker(const ComboAddress& master, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, std::shared_ptr<DNSFilterEngine::Zone> zone, const uint16_t axfrTimeout) | |
287 | { | |
288 | uint32_t refresh = zone->getRefresh(); | |
289 | DNSName zoneName = zone->getDomain(); | |
290 | std::string polName = zone->getName() ? *(zone->getName()) : zoneName.toString(); | |
291 | shared_ptr<SOARecordContent> sr; | |
292 | ||
293 | while (!sr) { | |
294 | try { | |
295 | sr=loadRPZFromServer(master, zoneName, zone, defpol, maxTTL, tt, maxReceivedBytes, localAddress, axfrTimeout); | |
296 | if(refresh == 0) { | |
297 | refresh = sr->d_st.refresh; | |
298 | } | |
299 | zone->setSerial(sr->d_st.serial); | |
300 | setRPZZoneNewState(polName, sr->d_st.serial, zone->size(), true); | |
301 | } | |
302 | catch(const std::exception& e) { | |
e6a9dde5 | 303 | g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.what()<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl; |
4ba9d5dc RG |
304 | incRPZFailedTransfers(polName); |
305 | } | |
306 | catch(const PDNSException& e) { | |
e6a9dde5 | 307 | g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.reason<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl; |
4ba9d5dc RG |
308 | incRPZFailedTransfers(polName); |
309 | } | |
310 | ||
311 | if (!sr) { | |
312 | if (refresh == 0) { | |
313 | sleep(10); | |
314 | } else { | |
315 | sleep(refresh); | |
316 | } | |
317 | } | |
318 | } | |
319 | ||
320 | for(;;) { | |
321 | DNSRecord dr; | |
322 | dr.d_content=sr; | |
323 | ||
324 | sleep(refresh); | |
325 | ||
e6a9dde5 | 326 | g_log<<Logger::Info<<"Getting IXFR deltas for "<<zoneName<<" from "<<master.toStringWithPort()<<", our serial: "<<getRR<SOARecordContent>(dr)->d_st.serial<<endl; |
4ba9d5dc RG |
327 | vector<pair<vector<DNSRecord>, vector<DNSRecord> > > deltas; |
328 | ||
329 | ComboAddress local(localAddress); | |
330 | if (local == ComboAddress()) | |
331 | local = getQueryLocalAddress(master.sin4.sin_family, 0); | |
332 | ||
333 | try { | |
334 | deltas = getIXFRDeltas(master, zoneName, dr, tt, &local, maxReceivedBytes); | |
335 | } catch(std::runtime_error& e ){ | |
e6a9dde5 | 336 | g_log<<Logger::Warning<<e.what()<<endl; |
4ba9d5dc RG |
337 | incRPZFailedTransfers(polName); |
338 | continue; | |
339 | } | |
340 | if(deltas.empty()) | |
341 | continue; | |
e6a9dde5 | 342 | g_log<<Logger::Info<<"Processing "<<deltas.size()<<" delta"<<addS(deltas)<<" for RPZ "<<zoneName<<endl; |
4ba9d5dc RG |
343 | |
344 | auto luaconfsLocal = g_luaconfs.getLocal(); | |
345 | const std::shared_ptr<DNSFilterEngine::Zone> oldZone = luaconfsLocal->dfe.getZone(zoneIdx); | |
346 | /* we need to make a _full copy_ of the zone we are going to work on */ | |
347 | std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone); | |
348 | ||
349 | int totremove=0, totadd=0; | |
350 | bool fullUpdate = false; | |
351 | for(const auto& delta : deltas) { | |
352 | const auto& remove = delta.first; | |
353 | const auto& add = delta.second; | |
354 | if(remove.empty()) { | |
e6a9dde5 | 355 | g_log<<Logger::Warning<<"IXFR update is a whole new zone"<<endl; |
4ba9d5dc RG |
356 | newZone->clear(); |
357 | fullUpdate = true; | |
358 | } | |
359 | for(const auto& rr : remove) { // should always contain the SOA | |
360 | if(rr.d_type == QType::NS) | |
361 | continue; | |
362 | if(rr.d_type == QType::SOA) { | |
363 | auto oldsr = getRR<SOARecordContent>(rr); | |
364 | if(oldsr && oldsr->d_st.serial == sr->d_st.serial) { | |
365 | // cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl; | |
366 | } | |
367 | else | |
e6a9dde5 | 368 | g_log<<Logger::Error<<"GOT WRONG SOA SERIAL REMOVAL, SHOULD TRIGGER WHOLE RELOAD"<<endl; |
4ba9d5dc RG |
369 | } |
370 | else { | |
371 | totremove++; | |
e6a9dde5 | 372 | g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName<<endl; |
4ba9d5dc RG |
373 | RPZRecordToPolicy(rr, newZone, false, defpol, maxTTL); |
374 | } | |
375 | } | |
376 | ||
377 | for(const auto& rr : add) { // should always contain the new SOA | |
378 | if(rr.d_type == QType::NS) | |
379 | continue; | |
380 | if(rr.d_type == QType::SOA) { | |
381 | auto newsr = getRR<SOARecordContent>(rr); | |
e6a9dde5 | 382 | // g_log<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<newsr->d_st.serial<<endl; |
4ba9d5dc RG |
383 | if (newsr) { |
384 | sr = newsr; | |
385 | } | |
386 | } | |
387 | else { | |
388 | totadd++; | |
e6a9dde5 | 389 | g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName<<endl; |
4ba9d5dc RG |
390 | RPZRecordToPolicy(rr, newZone, true, defpol, maxTTL); |
391 | } | |
392 | } | |
393 | } | |
e6a9dde5 | 394 | g_log<<Logger::Info<<"Had "<<totremove<<" RPZ removal"<<addS(totremove)<<", "<<totadd<<" addition"<<addS(totadd)<<" for "<<zoneName<<" New serial: "<<sr->d_st.serial<<endl; |
4ba9d5dc RG |
395 | newZone->setSerial(sr->d_st.serial); |
396 | setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), fullUpdate); | |
397 | ||
398 | /* we need to replace the existing zone with the new one, | |
399 | but we don't want to touch anything else, especially other zones, | |
400 | since they might have been updated by another RPZ IXFR tracker thread. | |
401 | */ | |
402 | g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) { | |
403 | lci.dfe.setZone(zoneIdx, newZone); | |
404 | }); | |
405 | } | |
406 | } |