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