]>
Commit | Line | Data |
---|---|---|
39cde30d | 1 | #include "arguments.hh" |
644dd1da | 2 | #include "dnsparser.hh" |
3 | #include "dnsrecords.hh" | |
4ba9d5dc | 4 | #include "ixfr.hh" |
644dd1da | 5 | #include "syncres.hh" |
39ec5d29 | 6 | #include "resolver.hh" |
7 | #include "logger.hh" | |
ad42489c | 8 | #include "rec-lua-conf.hh" |
4ba9d5dc RG |
9 | #include "rpzloader.hh" |
10 | #include "zoneparser-tng.hh" | |
519f5484 | 11 | #include "threadname.hh" |
644dd1da | 12 | |
301148e6 | 13 | Netmask makeNetmaskFromRPZ(const DNSName& name) |
644dd1da | 14 | { |
15 | auto parts = name.getRawLabels(); | |
b8470add PL |
16 | /* |
17 | * why 2?, the minimally valid IPv6 address that can be encoded in an RPZ is | |
18 | * $NETMASK.zz (::/$NETMASK) | |
19 | * Terrible right? | |
20 | */ | |
21 | if(parts.size() < 2 || parts.size() > 9) | |
86f1af1c | 22 | throw PDNSException("Invalid IP address in RPZ: "+name.toLogString()); |
b8470add PL |
23 | |
24 | bool isV6 = (stoi(parts[0]) > 32); | |
25 | bool hadZZ = false; | |
26 | ||
27 | for (auto &part : parts) { | |
28 | // Check if we have an IPv4 octet | |
29 | for (auto c : part) | |
30 | if (!isdigit(c)) | |
31 | isV6 = true; | |
32 | ||
33 | if (pdns_iequals(part,"zz")) { | |
34 | if (hadZZ) | |
86f1af1c | 35 | throw PDNSException("more than one 'zz' label found in RPZ name"+name.toLogString()); |
b8470add PL |
36 | part = ""; |
37 | isV6 = true; | |
38 | hadZZ = true; | |
39 | } | |
40 | } | |
41 | ||
42 | if (isV6 && parts.size() < 9 && !hadZZ) | |
86f1af1c | 43 | throw PDNSException("No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: "+name.toLogString()); |
b8470add PL |
44 | |
45 | if (parts.size() == 5 && !isV6) | |
46 | return Netmask(parts[4]+"."+parts[3]+"."+parts[2]+"."+parts[1]+"/"+parts[0]); | |
47 | ||
48 | string v6; | |
49 | ||
301148e6 OM |
50 | if (parts[parts.size()-1] == "") { |
51 | v6 += ":"; | |
52 | } | |
b8470add PL |
53 | for (uint8_t i = parts.size()-1 ; i > 0; i--) { |
54 | v6 += parts[i]; | |
301148e6 | 55 | if (i > 1 || (i == 1 && parts[i] == "")) { |
b8470add | 56 | v6 += ":"; |
301148e6 | 57 | } |
b8470add PL |
58 | } |
59 | v6 += "/" + parts[0]; | |
60 | ||
61 | return Netmask(v6); | |
644dd1da | 62 | } |
63 | ||
d122dac0 | 64 | static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL) |
644dd1da | 65 | { |
644dd1da | 66 | static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru."); |
644dd1da | 67 | static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"), |
68 | rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip."); | |
39c9bef5 | 69 | static const std::string rpzPrefix("rpz-"); |
39ec5d29 | 70 | |
1f1ca368 | 71 | DNSFilterEngine::Policy pol; |
d122dac0 | 72 | bool defpolApplied = false; |
39ec5d29 | 73 | |
ba3c54cb RG |
74 | if(dr.d_class != QClass::IN) { |
75 | return; | |
76 | } | |
77 | ||
39ec5d29 | 78 | if(dr.d_type == QType::CNAME) { |
ba3c54cb RG |
79 | auto crc = getRR<CNAMERecordContent>(dr); |
80 | if (!crc) { | |
81 | return; | |
82 | } | |
dd079764 | 83 | auto crcTarget=crc->getTarget(); |
ad42489c | 84 | if(defpol) { |
85 | pol=*defpol; | |
d122dac0 | 86 | defpolApplied = true; |
ad42489c | 87 | } |
dd079764 | 88 | else if(crcTarget.isRoot()) { |
39ec5d29 | 89 | // cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": "; |
90 | pol.d_kind = DNSFilterEngine::PolicyKind::NXDOMAIN; | |
dd079764 | 91 | } else if(crcTarget==g_wildcarddnsname) { |
39ec5d29 | 92 | // cerr<<"Wants NODATA for "<<dr.d_name<<": "; |
93 | pol.d_kind = DNSFilterEngine::PolicyKind::NODATA; | |
94 | } | |
dd079764 | 95 | else if(crcTarget==drop) { |
39ec5d29 | 96 | // cerr<<"Wants DROP for "<<dr.d_name<<": "; |
97 | pol.d_kind = DNSFilterEngine::PolicyKind::Drop; | |
98 | } | |
dd079764 | 99 | else if(crcTarget==truncate) { |
39ec5d29 | 100 | // cerr<<"Wants TRUNCATE for "<<dr.d_name<<": "; |
101 | pol.d_kind = DNSFilterEngine::PolicyKind::Truncate; | |
102 | } | |
dd079764 | 103 | else if(crcTarget==noaction) { |
39ec5d29 | 104 | // cerr<<"Wants NOACTION for "<<dr.d_name<<": "; |
105 | pol.d_kind = DNSFilterEngine::PolicyKind::NoAction; | |
106 | } | |
39c9bef5 RG |
107 | /* "The special RPZ encodings which are not to be taken as Local Data are |
108 | CNAMEs with targets that are: | |
109 | + "." (NXDOMAIN action), | |
110 | + "*." (NODATA action), | |
111 | + a top level domain starting with "rpz-", | |
112 | + a child of a top level domain starting with "rpz-". | |
113 | */ | |
114 | else if(!crcTarget.empty() && !crcTarget.isRoot() && crcTarget.getRawLabel(crcTarget.countLabels() - 1).compare(0, rpzPrefix.length(), rpzPrefix) == 0) { | |
115 | /* this is very likely an higher format number or a configuration error, | |
116 | let's just ignore it. */ | |
e6a9dde5 | 117 | g_log<<Logger::Info<<"Discarding unsupported RPZ entry "<<crcTarget<<" for "<<dr.d_name<<endl; |
39c9bef5 RG |
118 | return; |
119 | } | |
39ec5d29 | 120 | else { |
121 | pol.d_kind = DNSFilterEngine::PolicyKind::Custom; | |
6da513b2 | 122 | pol.d_custom.emplace_back(dr.d_content); |
dd079764 | 123 | // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": "; |
39ec5d29 | 124 | } |
125 | } | |
126 | else { | |
d122dac0 | 127 | if (defpol && defpolOverrideLocal) { |
db7dcbb1 | 128 | pol=*defpol; |
d122dac0 | 129 | defpolApplied = true; |
db7dcbb1 RG |
130 | } |
131 | else { | |
132 | pol.d_kind = DNSFilterEngine::PolicyKind::Custom; | |
6da513b2 | 133 | pol.d_custom.emplace_back(dr.d_content); |
db7dcbb1 RG |
134 | // cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": "; |
135 | } | |
39ec5d29 | 136 | } |
137 | ||
d122dac0 | 138 | if (!defpolApplied || defpol->d_ttl < 0) { |
8f618901 RG |
139 | pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dr.d_ttl)); |
140 | } else { | |
141 | pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl))); | |
142 | } | |
3876ee44 | 143 | |
39ec5d29 | 144 | // now to DO something with that |
39c9bef5 | 145 | |
39ec5d29 | 146 | if(dr.d_name.isPartOf(rpzNSDname)) { |
147 | DNSName filt=dr.d_name.makeRelative(rpzNSDname); | |
148 | if(addOrRemove) | |
6da513b2 | 149 | zone->addNSTrigger(filt, std::move(pol)); |
39ec5d29 | 150 | else |
6da513b2 | 151 | zone->rmNSTrigger(filt, std::move(pol)); |
6791663c | 152 | } else if(dr.d_name.isPartOf(rpzClientIP)) { |
b8470add PL |
153 | DNSName filt=dr.d_name.makeRelative(rpzClientIP); |
154 | auto nm=makeNetmaskFromRPZ(filt); | |
39ec5d29 | 155 | if(addOrRemove) |
6da513b2 | 156 | zone->addClientTrigger(nm, std::move(pol)); |
39ec5d29 | 157 | else |
6da513b2 | 158 | zone->rmClientTrigger(nm, std::move(pol)); |
39ec5d29 | 159 | |
6791663c | 160 | } else if(dr.d_name.isPartOf(rpzIP)) { |
39ec5d29 | 161 | // cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl; |
b8470add PL |
162 | DNSName filt=dr.d_name.makeRelative(rpzIP); |
163 | auto nm=makeNetmaskFromRPZ(filt); | |
39ec5d29 | 164 | if(addOrRemove) |
6da513b2 | 165 | zone->addResponseTrigger(nm, std::move(pol)); |
39ec5d29 | 166 | else |
6da513b2 | 167 | zone->rmResponseTrigger(nm, std::move(pol)); |
39ec5d29 | 168 | } else if(dr.d_name.isPartOf(rpzNSIP)) { |
b8470add PL |
169 | DNSName filt=dr.d_name.makeRelative(rpzNSIP); |
170 | auto nm=makeNetmaskFromRPZ(filt); | |
171 | if(addOrRemove) | |
6da513b2 | 172 | zone->addNSIPTrigger(nm, std::move(pol)); |
b8470add | 173 | else |
6da513b2 | 174 | zone->rmNSIPTrigger(nm, std::move(pol)); |
39ec5d29 | 175 | } else { |
d122dac0 RG |
176 | if(addOrRemove) { |
177 | /* if we did override the existing policy with the default policy, | |
178 | we might turn two A or AAAA into a CNAME, which would trigger | |
179 | an exception. Let's just ignore it. */ | |
180 | zone->addQNameTrigger(dr.d_name, std::move(pol), defpolApplied); | |
181 | } | |
182 | else { | |
6da513b2 | 183 | zone->rmQNameTrigger(dr.d_name, std::move(pol)); |
d122dac0 | 184 | } |
39ec5d29 | 185 | } |
186 | } | |
187 | ||
d122dac0 | 188 | static shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout) |
39ec5d29 | 189 | { |
e6a9dde5 | 190 | g_log<<Logger::Warning<<"Loading RPZ zone '"<<zoneName<<"' from "<<master.toStringWithPort()<<endl; |
98c9ec39 | 191 | if(!tt.name.empty()) |
e6a9dde5 | 192 | g_log<<Logger::Warning<<"With TSIG key '"<<tt.name<<"' of algorithm '"<<tt.algo<<"'"<<endl; |
98c9ec39 | 193 | |
f6a8f7d7 PL |
194 | ComboAddress local(localAddress); |
195 | if (local == ComboAddress()) | |
196 | local = getQueryLocalAddress(master.sin4.sin_family, 0); | |
197 | ||
e07c3801 | 198 | AXFRRetriever axfr(master, zoneName, tt, &local, maxReceivedBytes, axfrTimeout); |
39ec5d29 | 199 | unsigned int nrecords=0; |
200 | Resolver::res_t nop; | |
201 | vector<DNSRecord> chunk; | |
202 | time_t last=0; | |
cb6218d2 PL |
203 | time_t axfrStart = time(nullptr); |
204 | time_t axfrNow = time(nullptr); | |
39ec5d29 | 205 | shared_ptr<SOARecordContent> sr; |
ea448a77 | 206 | while(axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) { |
39ec5d29 | 207 | for(auto& dr : chunk) { |
98c9ec39 | 208 | if(dr.d_type==QType::NS || dr.d_type==QType::TSIG) { |
209 | continue; | |
210 | } | |
211 | ||
6b972d59 | 212 | dr.d_name.makeUsRelative(zoneName); |
39ec5d29 | 213 | if(dr.d_type==QType::SOA) { |
ba3c54cb | 214 | sr = getRR<SOARecordContent>(dr); |
39ec5d29 | 215 | continue; |
216 | } | |
39ec5d29 | 217 | |
d122dac0 | 218 | RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL); |
39ec5d29 | 219 | nrecords++; |
220 | } | |
ea448a77 | 221 | axfrNow = time(nullptr); |
cb6218d2 | 222 | if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) { |
ea448a77 PL |
223 | throw PDNSException("Total AXFR time exceeded!"); |
224 | } | |
39ec5d29 | 225 | if(last != time(0)) { |
210ae1af | 226 | g_log<<Logger::Info<<"Loaded & indexed "<<nrecords<<" policy records so far for RPZ zone '"<<zoneName<<"'"<<endl; |
39ec5d29 | 227 | last=time(0); |
228 | } | |
229 | } | |
e6a9dde5 | 230 | g_log<<Logger::Info<<"Done: "<<nrecords<<" policy records active, SOA: "<<sr->getZoneRepresentation()<<endl; |
39ec5d29 | 231 | return sr; |
232 | } | |
233 | ||
c823f860 | 234 | // this function is silent - you do the logging |
d122dac0 | 235 | std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL) |
39ec5d29 | 236 | { |
a66c5cfa | 237 | shared_ptr<SOARecordContent> sr = nullptr; |
39ec5d29 | 238 | ZoneParserTNG zpt(fname); |
39cde30d | 239 | zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps")); |
39ec5d29 | 240 | DNSResourceRecord drr; |
644dd1da | 241 | DNSName domain; |
242 | while(zpt.get(drr)) { | |
644dd1da | 243 | try { |
244 | if(drr.qtype.getCode() == QType::CNAME && drr.content.empty()) | |
245 | drr.content="."; | |
246 | DNSRecord dr(drr); | |
247 | if(dr.d_type == QType::SOA) { | |
a66c5cfa | 248 | sr = getRR<SOARecordContent>(dr); |
6791663c RG |
249 | domain = dr.d_name; |
250 | zone->setDomain(domain); | |
644dd1da | 251 | } |
39ec5d29 | 252 | else if(dr.d_type == QType::NS) { |
253 | continue; | |
254 | } | |
255 | else { | |
644dd1da | 256 | dr.d_name=dr.d_name.makeRelative(domain); |
d122dac0 | 257 | RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL); |
644dd1da | 258 | } |
259 | } | |
6791663c | 260 | catch(const PDNSException& pe) { |
86f1af1c | 261 | throw PDNSException("Issue parsing '"+drr.qname.toLogString()+"' '"+drr.content+"' at "+zpt.getLineOfFile()+": "+pe.reason); |
644dd1da | 262 | } |
263 | } | |
a66c5cfa | 264 | |
d70a7627 OM |
265 | if (sr != nullptr) { |
266 | zone->setRefresh(sr->d_st.refresh); | |
267 | } | |
a66c5cfa | 268 | return sr; |
644dd1da | 269 | } |
4ba9d5dc | 270 | |
20c37dec | 271 | static std::unordered_map<std::string, shared_ptr<rpzStats> > s_rpzStats; |
4ba9d5dc RG |
272 | static std::mutex s_rpzStatsMutex; |
273 | ||
20c37dec | 274 | shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone) |
4ba9d5dc RG |
275 | { |
276 | std::lock_guard<std::mutex> l(s_rpzStatsMutex); | |
20c37dec PL |
277 | if (s_rpzStats.find(zone) == s_rpzStats.end()) { |
278 | s_rpzStats[zone] = std::make_shared<rpzStats>(); | |
279 | } | |
4ba9d5dc RG |
280 | return s_rpzStats[zone]; |
281 | } | |
282 | ||
283 | static void incRPZFailedTransfers(const std::string& zone) | |
284 | { | |
20c37dec PL |
285 | auto stats = getRPZZoneStats(zone); |
286 | if (stats != nullptr) | |
287 | stats->d_failedTransfers++; | |
4ba9d5dc RG |
288 | } |
289 | ||
290 | static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool wasAXFR) | |
291 | { | |
20c37dec PL |
292 | auto stats = getRPZZoneStats(zone); |
293 | if (stats == nullptr) | |
294 | return; | |
295 | stats->d_successfulTransfers++; | |
4ba9d5dc | 296 | if (wasAXFR) { |
20c37dec | 297 | stats->d_fullTransfers++; |
4ba9d5dc | 298 | } |
20c37dec PL |
299 | stats->d_lastUpdate = time(nullptr); |
300 | stats->d_serial = serial; | |
301 | stats->d_numberOfRecords = numberOfRecords; | |
4ba9d5dc RG |
302 | } |
303 | ||
23f886c0 RG |
304 | static bool dumpZoneToDisk(const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& newZone, const std::string& dumpZoneFileName) |
305 | { | |
306 | std::string temp = dumpZoneFileName + "XXXXXX"; | |
307 | int fd = mkstemp(&temp.at(0)); | |
308 | if (fd < 0) { | |
309 | g_log<<Logger::Warning<<"Unable to open a file to dump the content of the RPZ zone "<<zoneName.toLogString()<<endl; | |
310 | return false; | |
311 | } | |
312 | ||
5e1f23ca RG |
313 | auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(fd, "w+"), fclose); |
314 | if (!fp) { | |
23f886c0 RG |
315 | close(fd); |
316 | g_log<<Logger::Warning<<"Unable to open a file pointer to dump the content of the RPZ zone "<<zoneName.toLogString()<<endl; | |
317 | return false; | |
318 | } | |
23f886c0 RG |
319 | fd = -1; |
320 | ||
321 | try { | |
5e1f23ca | 322 | newZone->dump(fp.get()); |
23f886c0 RG |
323 | } |
324 | catch(const std::exception& e) { | |
325 | g_log<<Logger::Warning<<"Error while dumping the content of the RPZ zone "<<zoneName.toLogString()<<": "<<e.what()<<endl; | |
23f886c0 RG |
326 | return false; |
327 | } | |
328 | ||
5e1f23ca | 329 | if (fflush(fp.get()) != 0) { |
04360367 | 330 | g_log<<Logger::Warning<<"Error while flushing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<stringerror()<<endl; |
23f886c0 RG |
331 | return false; |
332 | } | |
333 | ||
5e1f23ca | 334 | if (fsync(fileno(fp.get())) != 0) { |
04360367 | 335 | g_log<<Logger::Warning<<"Error while syncing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<stringerror()<<endl; |
23f886c0 RG |
336 | return false; |
337 | } | |
338 | ||
5e1f23ca | 339 | if (fclose(fp.release()) != 0) { |
04360367 | 340 | g_log<<Logger::Warning<<"Error while writing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<stringerror()<<endl; |
23f886c0 RG |
341 | return false; |
342 | } | |
343 | ||
344 | if (rename(temp.c_str(), dumpZoneFileName.c_str()) != 0) { | |
04360367 | 345 | g_log<<Logger::Warning<<"Error while moving the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<stringerror()<<endl; |
23f886c0 RG |
346 | return false; |
347 | } | |
348 | ||
349 | return true; | |
350 | } | |
351 | ||
d70a7627 | 352 | void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, const uint32_t refreshFromConf, std::shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration) |
4ba9d5dc | 353 | { |
519f5484 | 354 | setThreadName("pdns-r/RPZIXFR"); |
a66c5cfa | 355 | bool isPreloaded = sr != nullptr; |
1bf8d12a | 356 | auto luaconfsLocal = g_luaconfs.getLocal(); |
d70a7627 | 357 | |
1bf8d12a RG |
358 | /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */ |
359 | std::shared_ptr<DNSFilterEngine::Zone> oldZone = luaconfsLocal->dfe.getZone(zoneIdx); | |
360 | if (!oldZone) { | |
361 | g_log<<Logger::Error<<"Unable to retrieve RPZ zone with index "<<zoneIdx<<" from the configuration, exiting"<<endl; | |
362 | return; | |
363 | } | |
d70a7627 OM |
364 | |
365 | time_t refresh; | |
1bf8d12a RG |
366 | DNSName zoneName = oldZone->getDomain(); |
367 | std::string polName = oldZone->getName() ? *(oldZone->getName()) : zoneName.toString(); | |
4ba9d5dc RG |
368 | |
369 | while (!sr) { | |
a66c5cfa RG |
370 | /* if we received an empty sr, the zone was not really preloaded */ |
371 | ||
1bf8d12a RG |
372 | /* full copy, as promised */ |
373 | std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone); | |
5f311886 RG |
374 | for (const auto& master : masters) { |
375 | try { | |
87e7a726 | 376 | refresh = refreshFromConf ? refreshFromConf : 10U; |
d122dac0 | 377 | sr = loadRPZFromServer(master, zoneName, newZone, defpol, defpolOverrideLocal, maxTTL, tt, maxReceivedBytes, localAddress, axfrTimeout); |
5f311886 | 378 | newZone->setSerial(sr->d_st.serial); |
982817f3 | 379 | newZone->setRefresh(sr->d_st.refresh); |
5f311886 RG |
380 | setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), true); |
381 | ||
382 | g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) { | |
383 | lci.dfe.setZone(zoneIdx, newZone); | |
384 | }); | |
385 | ||
386 | if (!dumpZoneFileName.empty()) { | |
387 | dumpZoneToDisk(zoneName, newZone, dumpZoneFileName); | |
388 | } | |
389 | ||
390 | /* no need to try another master */ | |
391 | break; | |
4ba9d5dc | 392 | } |
5f311886 | 393 | catch(const std::exception& e) { |
d70a7627 | 394 | g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.what()<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl; |
5f311886 RG |
395 | incRPZFailedTransfers(polName); |
396 | } | |
397 | catch(const PDNSException& e) { | |
d70a7627 | 398 | g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.reason<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl; |
5f311886 | 399 | incRPZFailedTransfers(polName); |
23f886c0 | 400 | } |
4ba9d5dc RG |
401 | } |
402 | ||
403 | if (!sr) { | |
d70a7627 | 404 | sleep(refresh); |
4ba9d5dc RG |
405 | } |
406 | } | |
407 | ||
87e7a726 | 408 | refresh = std::max(refreshFromConf ? refreshFromConf : oldZone->getRefresh(), 1U); |
a66c5cfa | 409 | bool skipRefreshDelay = isPreloaded; |
13dacc77 | 410 | |
4ba9d5dc RG |
411 | for(;;) { |
412 | DNSRecord dr; | |
413 | dr.d_content=sr; | |
414 | ||
a66c5cfa RG |
415 | if (skipRefreshDelay) { |
416 | skipRefreshDelay = false; | |
417 | } | |
418 | else { | |
419 | sleep(refresh); | |
420 | } | |
4ba9d5dc | 421 | |
13dacc77 RG |
422 | if (luaconfsLocal->generation != configGeneration) { |
423 | /* the configuration has been reloaded, meaning that a new thread | |
424 | has been started to handle that zone and we are now obsolete. | |
425 | */ | |
426 | g_log<<Logger::Info<<"A more recent configuration has been found, stopping the existing RPZ update thread for "<<zoneName<<endl; | |
427 | return; | |
428 | } | |
429 | ||
4ba9d5dc | 430 | vector<pair<vector<DNSRecord>, vector<DNSRecord> > > deltas; |
5f311886 RG |
431 | for (const auto& master : masters) { |
432 | g_log<<Logger::Info<<"Getting IXFR deltas for "<<zoneName<<" from "<<master.toStringWithPort()<<", our serial: "<<getRR<SOARecordContent>(dr)->d_st.serial<<endl; | |
433 | ||
434 | ComboAddress local(localAddress); | |
435 | if (local == ComboAddress()) { | |
436 | local = getQueryLocalAddress(master.sin4.sin_family, 0); | |
437 | } | |
4ba9d5dc | 438 | |
5f311886 RG |
439 | try { |
440 | deltas = getIXFRDeltas(master, zoneName, dr, tt, &local, maxReceivedBytes); | |
4ba9d5dc | 441 | |
5f311886 RG |
442 | /* no need to try another master */ |
443 | break; | |
444 | } catch(const std::runtime_error& e ){ | |
445 | g_log<<Logger::Warning<<e.what()<<endl; | |
446 | incRPZFailedTransfers(polName); | |
447 | continue; | |
448 | } | |
4ba9d5dc | 449 | } |
5f311886 RG |
450 | |
451 | if(deltas.empty()) { | |
4ba9d5dc | 452 | continue; |
5f311886 RG |
453 | } |
454 | ||
e6a9dde5 | 455 | g_log<<Logger::Info<<"Processing "<<deltas.size()<<" delta"<<addS(deltas)<<" for RPZ "<<zoneName<<endl; |
4ba9d5dc | 456 | |
1bf8d12a | 457 | oldZone = luaconfsLocal->dfe.getZone(zoneIdx); |
4ba9d5dc RG |
458 | /* we need to make a _full copy_ of the zone we are going to work on */ |
459 | std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone); | |
460 | ||
461 | int totremove=0, totadd=0; | |
462 | bool fullUpdate = false; | |
463 | for(const auto& delta : deltas) { | |
464 | const auto& remove = delta.first; | |
465 | const auto& add = delta.second; | |
466 | if(remove.empty()) { | |
e6a9dde5 | 467 | g_log<<Logger::Warning<<"IXFR update is a whole new zone"<<endl; |
4ba9d5dc RG |
468 | newZone->clear(); |
469 | fullUpdate = true; | |
470 | } | |
471 | for(const auto& rr : remove) { // should always contain the SOA | |
472 | if(rr.d_type == QType::NS) | |
473 | continue; | |
474 | if(rr.d_type == QType::SOA) { | |
475 | auto oldsr = getRR<SOARecordContent>(rr); | |
476 | if(oldsr && oldsr->d_st.serial == sr->d_st.serial) { | |
477 | // cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl; | |
478 | } | |
479 | else | |
e6a9dde5 | 480 | g_log<<Logger::Error<<"GOT WRONG SOA SERIAL REMOVAL, SHOULD TRIGGER WHOLE RELOAD"<<endl; |
4ba9d5dc RG |
481 | } |
482 | else { | |
483 | totremove++; | |
e6a9dde5 | 484 | g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName<<endl; |
d122dac0 | 485 | RPZRecordToPolicy(rr, newZone, false, defpol, defpolOverrideLocal, maxTTL); |
4ba9d5dc RG |
486 | } |
487 | } | |
488 | ||
489 | for(const auto& rr : add) { // should always contain the new SOA | |
490 | if(rr.d_type == QType::NS) | |
491 | continue; | |
492 | if(rr.d_type == QType::SOA) { | |
493 | auto newsr = getRR<SOARecordContent>(rr); | |
e6a9dde5 | 494 | // g_log<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<newsr->d_st.serial<<endl; |
4ba9d5dc RG |
495 | if (newsr) { |
496 | sr = newsr; | |
497 | } | |
498 | } | |
499 | else { | |
500 | totadd++; | |
e6a9dde5 | 501 | g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName<<endl; |
d122dac0 | 502 | RPZRecordToPolicy(rr, newZone, true, defpol, defpolOverrideLocal, maxTTL); |
4ba9d5dc RG |
503 | } |
504 | } | |
505 | } | |
e6a9dde5 | 506 | g_log<<Logger::Info<<"Had "<<totremove<<" RPZ removal"<<addS(totremove)<<", "<<totadd<<" addition"<<addS(totadd)<<" for "<<zoneName<<" New serial: "<<sr->d_st.serial<<endl; |
4ba9d5dc | 507 | newZone->setSerial(sr->d_st.serial); |
d70a7627 | 508 | newZone->setRefresh(sr->d_st.refresh); |
4ba9d5dc RG |
509 | setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), fullUpdate); |
510 | ||
511 | /* we need to replace the existing zone with the new one, | |
512 | but we don't want to touch anything else, especially other zones, | |
513 | since they might have been updated by another RPZ IXFR tracker thread. | |
514 | */ | |
515 | g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) { | |
516 | lci.dfe.setZone(zoneIdx, newZone); | |
517 | }); | |
23f886c0 RG |
518 | |
519 | if (!dumpZoneFileName.empty()) { | |
520 | dumpZoneToDisk(zoneName, newZone, dumpZoneFileName); | |
521 | } | |
87e7a726 | 522 | refresh = std::max(refreshFromConf ? refreshFromConf : newZone->getRefresh(), 1U); |
4ba9d5dc RG |
523 | } |
524 | } |