]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/recursordist/rpzloader.cc
Add test, only clear cache if the notify wasn't for an RPZ
[thirdparty/pdns.git] / pdns / recursordist / rpzloader.cc
CommitLineData
29691be2
OM
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22#include <condition_variable>
39cde30d 23#include "arguments.hh"
644dd1da 24#include "dnsparser.hh"
25#include "dnsrecords.hh"
4ba9d5dc 26#include "ixfr.hh"
644dd1da 27#include "syncres.hh"
0dd42dbb 28#include "axfr-retriever.hh"
0fb64ae1 29#include "lock.hh"
39ec5d29 30#include "logger.hh"
f3cba034 31#include "logging.hh"
ad42489c 32#include "rec-lua-conf.hh"
4ba9d5dc
RG
33#include "rpzloader.hh"
34#include "zoneparser-tng.hh"
519f5484 35#include "threadname.hh"
20829585 36#include "query-local-address.hh"
644dd1da 37
301148e6 38Netmask makeNetmaskFromRPZ(const DNSName& name)
644dd1da 39{
40 auto parts = name.getRawLabels();
b8470add
PL
41 /*
42 * why 2?, the minimally valid IPv6 address that can be encoded in an RPZ is
43 * $NETMASK.zz (::/$NETMASK)
44 * Terrible right?
45 */
f8f66c91
OM
46 if (parts.size() < 2 || parts.size() > 9)
47 throw PDNSException("Invalid IP address in RPZ: " + name.toLogString());
b8470add
PL
48
49 bool isV6 = (stoi(parts[0]) > 32);
50 bool hadZZ = false;
51
f8f66c91 52 for (auto& part : parts) {
b8470add
PL
53 // Check if we have an IPv4 octet
54 for (auto c : part)
55 if (!isdigit(c))
56 isV6 = true;
57
f8f66c91 58 if (pdns_iequals(part, "zz")) {
b8470add 59 if (hadZZ)
f8f66c91 60 throw PDNSException("more than one 'zz' label found in RPZ name" + name.toLogString());
b8470add
PL
61 part = "";
62 isV6 = true;
63 hadZZ = true;
64 }
65 }
66
67 if (isV6 && parts.size() < 9 && !hadZZ)
f8f66c91 68 throw PDNSException("No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: " + name.toLogString());
b8470add
PL
69
70 if (parts.size() == 5 && !isV6)
f8f66c91 71 return Netmask(parts[4] + "." + parts[3] + "." + parts[2] + "." + parts[1] + "/" + parts[0]);
b8470add
PL
72
73 string v6;
74
f8f66c91 75 if (parts[parts.size() - 1] == "") {
301148e6
OM
76 v6 += ":";
77 }
f8f66c91 78 for (uint8_t i = parts.size() - 1; i > 0; i--) {
b8470add 79 v6 += parts[i];
301148e6 80 if (i > 1 || (i == 1 && parts[i] == "")) {
b8470add 81 v6 += ":";
301148e6 82 }
b8470add
PL
83 }
84 v6 += "/" + parts[0];
85
86 return Netmask(v6);
644dd1da 87}
88
62b191dc 89static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, Logr::log_t log)
644dd1da 90{
644dd1da 91 static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
644dd1da 92 static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
93 rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
39c9bef5 94 static const std::string rpzPrefix("rpz-");
39ec5d29 95
1f1ca368 96 DNSFilterEngine::Policy pol;
d122dac0 97 bool defpolApplied = false;
39ec5d29 98
f8f66c91 99 if (dr.d_class != QClass::IN) {
ba3c54cb
RG
100 return;
101 }
102
f8f66c91 103 if (dr.d_type == QType::CNAME) {
ba3c54cb
RG
104 auto crc = getRR<CNAMERecordContent>(dr);
105 if (!crc) {
106 return;
107 }
f8f66c91
OM
108 auto crcTarget = crc->getTarget();
109 if (defpol) {
110 pol = *defpol;
d122dac0 111 defpolApplied = true;
ad42489c 112 }
f8f66c91 113 else if (crcTarget.isRoot()) {
39ec5d29 114 // cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": ";
115 pol.d_kind = DNSFilterEngine::PolicyKind::NXDOMAIN;
f8f66c91
OM
116 }
117 else if (crcTarget == g_wildcarddnsname) {
39ec5d29 118 // cerr<<"Wants NODATA for "<<dr.d_name<<": ";
119 pol.d_kind = DNSFilterEngine::PolicyKind::NODATA;
120 }
f8f66c91 121 else if (crcTarget == drop) {
39ec5d29 122 // cerr<<"Wants DROP for "<<dr.d_name<<": ";
123 pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
124 }
f8f66c91 125 else if (crcTarget == truncate) {
39ec5d29 126 // cerr<<"Wants TRUNCATE for "<<dr.d_name<<": ";
127 pol.d_kind = DNSFilterEngine::PolicyKind::Truncate;
128 }
f8f66c91 129 else if (crcTarget == noaction) {
39ec5d29 130 // cerr<<"Wants NOACTION for "<<dr.d_name<<": ";
131 pol.d_kind = DNSFilterEngine::PolicyKind::NoAction;
132 }
39c9bef5
RG
133 /* "The special RPZ encodings which are not to be taken as Local Data are
134 CNAMEs with targets that are:
135 + "." (NXDOMAIN action),
136 + "*." (NODATA action),
137 + a top level domain starting with "rpz-",
138 + a child of a top level domain starting with "rpz-".
139 */
f8f66c91 140 else if (!crcTarget.empty() && !crcTarget.isRoot() && crcTarget.getRawLabel(crcTarget.countLabels() - 1).compare(0, rpzPrefix.length(), rpzPrefix) == 0) {
39c9bef5
RG
141 /* this is very likely an higher format number or a configuration error,
142 let's just ignore it. */
f8f66c91 143 SLOG(g_log << Logger::Info << "Discarding unsupported RPZ entry " << crcTarget << " for " << dr.d_name << endl,
62b191dc 144 log->info(Logr::Info, "Discarding unsupported RPZ entry", "target", Logging::Loggable(crcTarget), "name", Logging::Loggable(dr.d_name)));
39c9bef5
RG
145 return;
146 }
39ec5d29 147 else {
148 pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
d06dcda4 149 pol.d_custom.emplace_back(dr.getContent());
dd079764 150 // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
39ec5d29 151 }
152 }
153 else {
d122dac0 154 if (defpol && defpolOverrideLocal) {
f8f66c91 155 pol = *defpol;
d122dac0 156 defpolApplied = true;
db7dcbb1
RG
157 }
158 else {
159 pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
d06dcda4 160 pol.d_custom.emplace_back(dr.getContent());
db7dcbb1
RG
161 // cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": ";
162 }
39ec5d29 163 }
164
d122dac0 165 if (!defpolApplied || defpol->d_ttl < 0) {
8f618901 166 pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dr.d_ttl));
f8f66c91
OM
167 }
168 else {
8f618901
RG
169 pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl)));
170 }
3876ee44 171
39ec5d29 172 // now to DO something with that
39c9bef5 173
f8f66c91
OM
174 if (dr.d_name.isPartOf(rpzNSDname)) {
175 DNSName filt = dr.d_name.makeRelative(rpzNSDname);
176 if (addOrRemove)
d8f1f4e6 177 zone->addNSTrigger(filt, std::move(pol), defpolApplied);
39ec5d29 178 else
6da513b2 179 zone->rmNSTrigger(filt, std::move(pol));
f8f66c91
OM
180 }
181 else if (dr.d_name.isPartOf(rpzClientIP)) {
182 DNSName filt = dr.d_name.makeRelative(rpzClientIP);
183 auto nm = makeNetmaskFromRPZ(filt);
184 if (addOrRemove)
d8f1f4e6 185 zone->addClientTrigger(nm, std::move(pol), defpolApplied);
39ec5d29 186 else
6da513b2 187 zone->rmClientTrigger(nm, std::move(pol));
f8f66c91
OM
188 }
189 else if (dr.d_name.isPartOf(rpzIP)) {
39ec5d29 190 // cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
f8f66c91
OM
191 DNSName filt = dr.d_name.makeRelative(rpzIP);
192 auto nm = makeNetmaskFromRPZ(filt);
193 if (addOrRemove)
d8f1f4e6 194 zone->addResponseTrigger(nm, std::move(pol), defpolApplied);
39ec5d29 195 else
6da513b2 196 zone->rmResponseTrigger(nm, std::move(pol));
f8f66c91
OM
197 }
198 else if (dr.d_name.isPartOf(rpzNSIP)) {
199 DNSName filt = dr.d_name.makeRelative(rpzNSIP);
200 auto nm = makeNetmaskFromRPZ(filt);
201 if (addOrRemove)
d8f1f4e6 202 zone->addNSIPTrigger(nm, std::move(pol), defpolApplied);
b8470add 203 else
6da513b2 204 zone->rmNSIPTrigger(nm, std::move(pol));
f8f66c91
OM
205 }
206 else {
207 if (addOrRemove) {
d122dac0
RG
208 /* if we did override the existing policy with the default policy,
209 we might turn two A or AAAA into a CNAME, which would trigger
210 an exception. Let's just ignore it. */
211 zone->addQNameTrigger(dr.d_name, std::move(pol), defpolApplied);
212 }
213 else {
6da513b2 214 zone->rmQNameTrigger(dr.d_name, std::move(pol));
d122dac0 215 }
39ec5d29 216 }
217}
218
d06dcda4 219static shared_ptr<const SOARecordContent> loadRPZFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
39ec5d29 220{
f3cba034 221
49f30160 222 auto logger = plogger->withValues("primary", Logging::Loggable(primary));
f8f66c91 223 SLOG(g_log << Logger::Warning << "Loading RPZ zone '" << zoneName << "' from " << primary.toStringWithPort() << endl,
8a38b7e6 224 logger->info(Logr::Info, "Loading RPZ from nameserver"));
f8f66c91
OM
225 if (!tt.name.empty()) {
226 SLOG(g_log << Logger::Warning << "With TSIG key '" << tt.name << "' of algorithm '" << tt.algo << "'" << endl,
8a38b7e6 227 logger->info(Logr::Info, "Using TSIG key for authentication", "tsig_key_name", Logging::Loggable(tt.name), "tsig_key_algorithm", Logging::Loggable(tt.algo)));
f3cba034 228 }
98c9ec39 229
f6a8f7d7
PL
230 ComboAddress local(localAddress);
231 if (local == ComboAddress())
3d324e00 232 local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
f6a8f7d7 233
3d324e00 234 AXFRRetriever axfr(primary, zoneName, tt, &local, maxReceivedBytes, axfrTimeout);
f8f66c91 235 unsigned int nrecords = 0;
39ec5d29 236 Resolver::res_t nop;
237 vector<DNSRecord> chunk;
f8f66c91 238 time_t last = 0;
cb6218d2
PL
239 time_t axfrStart = time(nullptr);
240 time_t axfrNow = time(nullptr);
d06dcda4 241 shared_ptr<const SOARecordContent> sr;
8f67f0c2 242 // coverity[store_truncates_time_t]
f8f66c91
OM
243 while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
244 for (auto& dr : chunk) {
245 if (dr.d_type == QType::NS || dr.d_type == QType::TSIG) {
246 continue;
98c9ec39 247 }
248
6b972d59 249 dr.d_name.makeUsRelative(zoneName);
f8f66c91
OM
250 if (dr.d_type == QType::SOA) {
251 sr = getRR<SOARecordContent>(dr);
46572de5 252 zone->setSOA(dr);
f8f66c91 253 continue;
39ec5d29 254 }
39ec5d29 255
62b191dc 256 RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
39ec5d29 257 nrecords++;
f8f66c91 258 }
ea448a77 259 axfrNow = time(nullptr);
cb6218d2 260 if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
ea448a77
PL
261 throw PDNSException("Total AXFR time exceeded!");
262 }
f8f66c91
OM
263 if (last != time(0)) {
264 SLOG(g_log << Logger::Info << "Loaded & indexed " << nrecords << " policy records so far for RPZ zone '" << zoneName << "'" << endl,
8a38b7e6 265 logger->info(Logr::Info, "RPZ load in progress", "nrecords", Logging::Loggable(nrecords)));
f8f66c91 266 last = time(0);
39ec5d29 267 }
268 }
f8f66c91 269 SLOG(g_log << Logger::Info << "Done: " << nrecords << " policy records active, SOA: " << sr->getZoneRepresentation() << endl,
8a38b7e6 270 logger->info(Logr::Info, "RPZ load completed", "nrecords", Logging::Loggable(nrecords), "soa", Logging::Loggable(sr->getZoneRepresentation())));
39ec5d29 271 return sr;
272}
273
f8f66c91 274static LockGuarded<std::unordered_map<std::string, shared_ptr<rpzStats>>> s_rpzStats;
f2c4f873
RG
275
276shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone)
277{
278 auto stats = s_rpzStats.lock();
279 auto it = stats->find(zone);
280 if (it == stats->end()) {
281 auto stat = std::make_shared<rpzStats>();
282 (*stats)[zone] = stat;
283 return stat;
284 }
285 return it->second;
286}
287
288static void incRPZFailedTransfers(const std::string& zone)
289{
290 auto stats = getRPZZoneStats(zone);
291 if (stats != nullptr)
292 stats->d_failedTransfers++;
293}
294
295static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool fromFile, bool wasAXFR)
296{
297 auto stats = getRPZZoneStats(zone);
298 if (stats == nullptr) {
299 return;
300 }
301 if (!fromFile) {
302 stats->d_successfulTransfers++;
303 if (wasAXFR) {
304 stats->d_fullTransfers++;
305 }
306 }
307 stats->d_lastUpdate = time(nullptr);
308 stats->d_serial = serial;
309 stats->d_numberOfRecords = numberOfRecords;
310}
311
c823f860 312// this function is silent - you do the logging
d06dcda4 313std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL)
39ec5d29 314{
d06dcda4 315 shared_ptr<const SOARecordContent> sr = nullptr;
39ec5d29 316 ZoneParserTNG zpt(fname);
39cde30d 317 zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
e3fc3ebd 318 zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
39ec5d29 319 DNSResourceRecord drr;
46572de5 320 DNSRecord soaRecord;
644dd1da 321 DNSName domain;
62b191dc 322 auto log = g_slog->withName("rpz")->withValues("file", Logging::Loggable(fname), "zone", Logging::Loggable(zone->getName()));
f8f66c91 323 while (zpt.get(drr)) {
644dd1da 324 try {
f8f66c91
OM
325 if (drr.qtype.getCode() == QType::CNAME && drr.content.empty())
326 drr.content = ".";
644dd1da 327 DNSRecord dr(drr);
f8f66c91 328 if (dr.d_type == QType::SOA) {
a66c5cfa 329 sr = getRR<SOARecordContent>(dr);
6791663c
RG
330 domain = dr.d_name;
331 zone->setDomain(domain);
50bd111e 332 soaRecord = std::move(dr);
644dd1da 333 }
f8f66c91
OM
334 else if (dr.d_type == QType::NS) {
335 continue;
39ec5d29 336 }
337 else {
f8f66c91
OM
338 dr.d_name = dr.d_name.makeRelative(domain);
339 RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, log);
644dd1da 340 }
341 }
f8f66c91
OM
342 catch (const PDNSException& pe) {
343 throw PDNSException("Issue parsing '" + drr.qname.toLogString() + "' '" + drr.content + "' at " + zpt.getLineOfFile() + ": " + pe.reason);
644dd1da 344 }
345 }
a66c5cfa 346
d70a7627
OM
347 if (sr != nullptr) {
348 zone->setRefresh(sr->d_st.refresh);
50bd111e 349 zone->setSOA(std::move(soaRecord));
f2c4f873 350 setRPZZoneNewState(zone->getName(), sr->d_st.serial, zone->size(), true, false);
d70a7627 351 }
a66c5cfa 352 return sr;
644dd1da 353}
4ba9d5dc 354
62b191dc 355static bool dumpZoneToDisk(Logr::log_t logger, const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& newZone, const std::string& dumpZoneFileName)
23f886c0 356{
62b191dc 357 logger->info(Logr::Debug, "Dumping zone to disk", "destination_file", Logging::Loggable(dumpZoneFileName));
23f886c0
RG
358 std::string temp = dumpZoneFileName + "XXXXXX";
359 int fd = mkstemp(&temp.at(0));
360 if (fd < 0) {
f8f66c91 361 SLOG(g_log << Logger::Warning << "Unable to open a file to dump the content of the RPZ zone " << zoneName << endl,
d197c31c 362 logger->error(Logr::Error, errno, "Unable to create temporary file"));
23f886c0
RG
363 return false;
364 }
365
f8f66c91 366 auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fd, "w+"), fclose);
5e1f23ca 367 if (!fp) {
f3cba034 368 int err = errno;
23f886c0 369 close(fd);
f8f66c91 370 SLOG(g_log << Logger::Warning << "Unable to open a file pointer to dump the content of the RPZ zone " << zoneName << endl,
d197c31c 371 logger->error(Logr::Error, err, "Unable to open file pointer"));
23f886c0
RG
372 return false;
373 }
23f886c0
RG
374
375 try {
5e1f23ca 376 newZone->dump(fp.get());
23f886c0 377 }
f8f66c91
OM
378 catch (const std::exception& e) {
379 SLOG(g_log << Logger::Warning << "Error while dumping the content of the RPZ zone " << zoneName << ": " << e.what() << endl,
d197c31c 380 logger->error(Logr::Error, e.what(), "Error while dumping the content of the RPZ"));
23f886c0
RG
381 return false;
382 }
383
5e1f23ca 384 if (fflush(fp.get()) != 0) {
f8f66c91 385 SLOG(g_log << Logger::Warning << "Error while flushing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
8a38b7e6 386 logger->error(Logr::Warning, errno, "Error while flushing the content of the RPZ"));
23f886c0
RG
387 return false;
388 }
389
5e1f23ca 390 if (fsync(fileno(fp.get())) != 0) {
f8f66c91 391 SLOG(g_log << Logger::Warning << "Error while syncing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
d197c31c 392 logger->error(Logr::Error, errno, "Error while syncing the content of the RPZ"));
23f886c0
RG
393 return false;
394 }
395
5e1f23ca 396 if (fclose(fp.release()) != 0) {
f8f66c91 397 SLOG(g_log << Logger::Warning << "Error while writing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
d197c31c 398 logger->error(Logr::Error, errno, "Error while writing the content of the RPZ"));
23f886c0
RG
399 return false;
400 }
401
402 if (rename(temp.c_str(), dumpZoneFileName.c_str()) != 0) {
f8f66c91 403 SLOG(g_log << Logger::Warning << "Error while moving the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
d197c31c 404 logger->error(Logr::Error, errno, "Error while moving the content of the RPZ", "destination_file", Logging::Loggable(dumpZoneFileName)));
23f886c0
RG
405 return false;
406 }
407
408 return true;
409}
410
b59c7fc3
OM
411// A struct that holds the condition var and related stuff to allow notifies to be sent to the tread owning
412// the struct.
29691be2
OM
413struct RPZWaiter
414{
415 RPZWaiter(std::thread::id arg) :
416 id(arg) {}
b59c7fc3
OM
417 std::thread::id id;
418 std::mutex mutex;
419 std::condition_variable condVar;
420 std::atomic<bool> stop{false};
421};
422
423static void preloadRPZFIle(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, RPZWaiter& rpzwaiter, Logr::log_t logger)
4ba9d5dc 424{
4fc57a40 425 while (!params.soaRecordContent) {
a66c5cfa
RG
426 /* if we received an empty sr, the zone was not really preloaded */
427
1bf8d12a
RG
428 /* full copy, as promised */
429 std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
4fc57a40 430 for (const auto& primary : params.primaries) {
5f311886 431 try {
4fc57a40
OM
432 params.soaRecordContent = loadRPZFromServer(logger, primary, zoneName, newZone, params.defpol, params.defpolOverrideLocal, params.maxTTL, params.tsigtriplet, params.maxReceivedBytes, params.localAddress, params.xfrTimeout);
433 newZone->setSerial(params.soaRecordContent->d_st.serial);
434 newZone->setRefresh(params.soaRecordContent->d_st.refresh);
435 refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : newZone->getRefresh(), 1U);
436 setRPZZoneNewState(polName, params.soaRecordContent->d_st.serial, newZone->size(), false, true);
5f311886 437
4fc57a40 438 g_luaconfs.modify([zoneIdx = params.zoneIdx, &newZone](LuaConfigItems& lci) {
f8f66c91
OM
439 lci.dfe.setZone(zoneIdx, newZone);
440 });
5f311886 441
4fc57a40
OM
442 if (!params.dumpZoneFileName.empty()) {
443 dumpZoneToDisk(logger, zoneName, newZone, params.dumpZoneFileName);
5f311886
RG
444 }
445
3d324e00 446 /* no need to try another primary */
5f311886 447 break;
4ba9d5dc 448 }
f8f66c91
OM
449 catch (const std::exception& e) {
450 SLOG(g_log << Logger::Warning << "Unable to load RPZ zone '" << zoneName << "' from '" << primary << "': '" << e.what() << "'. (Will try again in " << refresh << " seconds...)" << endl,
77d23f79 451 logger->error(Logr::Warning, e.what(), "Unable to load RPZ zone, will retry", "from", Logging::Loggable(primary), "exception", Logging::Loggable("std::exception"), "refresh", Logging::Loggable(refresh)));
5f311886
RG
452 incRPZFailedTransfers(polName);
453 }
f8f66c91
OM
454 catch (const PDNSException& e) {
455 SLOG(g_log << Logger::Warning << "Unable to load RPZ zone '" << zoneName << "' from '" << primary << "': '" << e.reason << "'. (Will try again in " << refresh << " seconds...)" << endl,
77d23f79 456 logger->error(Logr::Warning, e.reason, "Unable to load RPZ zone, will retry", "from", Logging::Loggable(primary), "exception", Logging::Loggable("PDNSException"), "refresh", Logging::Loggable(refresh)));
5f311886 457 incRPZFailedTransfers(polName);
23f886c0 458 }
4ba9d5dc 459 }
60d691fb
OM
460 // Release newZone before (long) sleep to reduce memory usage
461 newZone = nullptr;
4fc57a40 462 if (!params.soaRecordContent) {
b59c7fc3
OM
463 std::unique_lock lock(rpzwaiter.mutex);
464 rpzwaiter.condVar.wait_for(lock, std::chrono::seconds(refresh),
29691be2 465 [&stop = rpzwaiter.stop] { return stop.load(); });
4ba9d5dc 466 }
b59c7fc3 467 rpzwaiter.stop = false;
4ba9d5dc 468 }
4fc57a40 469}
4ba9d5dc 470
b59c7fc3 471static bool RPZTrackerIteration(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, bool& skipRefreshDelay, uint64_t configGeneration, RPZWaiter& rpzwaiter, Logr::log_t logger)
4fc57a40
OM
472{
473 // Don't hold on to oldZone, it well be re-assigned after sleep in the try block
474 oldZone = nullptr;
b59c7fc3
OM
475 DNSRecord dnsRecord;
476 dnsRecord.setContent(params.soaRecordContent);
4ba9d5dc 477
4fc57a40
OM
478 if (skipRefreshDelay) {
479 skipRefreshDelay = false;
480 }
481 else {
b59c7fc3
OM
482 std::unique_lock lock(rpzwaiter.mutex);
483 rpzwaiter.condVar.wait_for(lock, std::chrono::seconds(refresh),
29691be2 484 [&stop = rpzwaiter.stop] { return stop.load(); });
b59c7fc3 485 rpzwaiter.stop = false;
4fc57a40
OM
486 }
487 auto luaconfsLocal = g_luaconfs.getLocal();
4ba9d5dc 488
4fc57a40
OM
489 if (luaconfsLocal->generation != configGeneration) {
490 /* the configuration has been reloaded, meaning that a new thread
491 has been started to handle that zone and we are now obsolete.
492 */
493 SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
494 logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
b59c7fc3 495 return false;
4fc57a40 496 }
13dacc77 497
4fc57a40
OM
498 vector<pair<vector<DNSRecord>, vector<DNSRecord>>> deltas;
499 for (const auto& primary : params.primaries) {
b59c7fc3 500 auto soa = getRR<SOARecordContent>(dnsRecord);
4fc57a40
OM
501 auto serial = soa ? soa->d_st.serial : 0;
502 SLOG(g_log << Logger::Info << "Getting IXFR deltas for " << zoneName << " from " << primary.toStringWithPort() << ", our serial: " << serial << endl,
503 logger->info(Logr::Info, "Getting IXFR deltas", "address", Logging::Loggable(primary), "ourserial", Logging::Loggable(serial)));
5f311886 504
4fc57a40
OM
505 ComboAddress local(params.localAddress);
506 if (local == ComboAddress()) {
507 local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
508 }
4ba9d5dc 509
4fc57a40 510 try {
b59c7fc3 511 deltas = getIXFRDeltas(primary, zoneName, dnsRecord, params.xfrTimeout, true, params.tsigtriplet, &local, params.maxReceivedBytes);
4ba9d5dc 512
4fc57a40
OM
513 /* no need to try another primary */
514 break;
4ba9d5dc 515 }
4fc57a40
OM
516 catch (const std::runtime_error& e) {
517 SLOG(g_log << Logger::Warning << e.what() << endl,
518 logger->error(Logr::Warning, e.what(), "Exception during retrieval of delta", "exception", Logging::Loggable("std::runtime_error")));
519 incRPZFailedTransfers(polName);
4ba9d5dc 520 continue;
5f311886 521 }
4fc57a40 522 }
5f311886 523
4fc57a40 524 if (deltas.empty()) {
b59c7fc3 525 return true;
4fc57a40 526 }
7d3cb915 527
4fc57a40
OM
528 try {
529 SLOG(g_log << Logger::Info << "Processing " << deltas.size() << " delta" << addS(deltas) << " for RPZ " << zoneName << endl,
530 logger->info(Logr::Info, "Processing deltas", "size", Logging::Loggable(deltas.size())));
531
532 if (luaconfsLocal->generation != configGeneration) {
533 SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
534 logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
b59c7fc3 535 return false;
4fc57a40
OM
536 }
537 oldZone = luaconfsLocal->dfe.getZone(params.zoneIdx);
538 if (!oldZone || oldZone->getDomain() != zoneName) {
539 SLOG(g_log << Logger::Info << "This policy is no more, stopping the existing RPZ update thread for " << zoneName << endl,
540 logger->info(Logr::Info, "This policy is no more, stopping the existing RPZ update thread"));
b59c7fc3 541 return false;
4fc57a40
OM
542 }
543 /* we need to make a _full copy_ of the zone we are going to work on */
544 std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
545 /* initialize the current serial to the last one */
546 std::shared_ptr<const SOARecordContent> currentSR = params.soaRecordContent;
547
548 int totremove = 0;
549 int totadd = 0;
550 bool fullUpdate = false;
551 for (const auto& delta : deltas) {
552 const auto& remove = delta.first;
553 const auto& add = delta.second;
554 if (remove.empty()) {
555 SLOG(g_log << Logger::Warning << "IXFR update is a whole new zone" << endl,
556 logger->info(Logr::Warning, "IXFR update is a whole new zone"));
557 newZone->clear();
558 fullUpdate = true;
0c98e3de 559 }
4fc57a40
OM
560 for (const auto& resourceRecord : remove) { // should always contain the SOA
561 if (resourceRecord.d_type == QType::NS) {
562 continue;
7d3cb915 563 }
4fc57a40
OM
564 if (resourceRecord.d_type == QType::SOA) {
565 auto oldsr = getRR<SOARecordContent>(resourceRecord);
566 if (oldsr && oldsr->d_st.serial == currentSR->d_st.serial) {
046b69a5 567 // Got good removal of SOA serial, no work to be done
7d3cb915
RG
568 }
569 else {
4fc57a40
OM
570 if (!oldsr) {
571 throw std::runtime_error("Unable to extract serial from SOA record while processing the removal part of an update");
7d3cb915 572 }
4fc57a40 573 throw std::runtime_error("Received an unexpected serial (" + std::to_string(oldsr->d_st.serial) + ", expecting " + std::to_string(currentSR->d_st.serial) + ") from SOA record while processing the removal part of an update");
7d3cb915
RG
574 }
575 }
4fc57a40
OM
576 else {
577 totremove++;
578 SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had removal of " << resourceRecord.d_name << " from RPZ zone " << zoneName << endl,
579 logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Remove from RPZ zone", "name", Logging::Loggable(resourceRecord.d_name)));
580 RPZRecordToPolicy(resourceRecord, newZone, false, params.defpol, params.defpolOverrideLocal, params.maxTTL, logger);
581 }
4ba9d5dc 582 }
7d3cb915 583
4fc57a40
OM
584 for (const auto& resourceRecord : add) { // should always contain the new SOA
585 if (resourceRecord.d_type == QType::NS) {
586 continue;
587 }
588 if (resourceRecord.d_type == QType::SOA) {
589 auto tempSR = getRR<SOARecordContent>(resourceRecord);
4fc57a40
OM
590 if (tempSR) {
591 currentSR = std::move(tempSR);
592 }
593 }
594 else {
595 totadd++;
596 SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had addition of " << resourceRecord.d_name << " to RPZ zone " << zoneName << endl,
597 logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Addition to RPZ zone", "name", Logging::Loggable(resourceRecord.d_name)));
598 RPZRecordToPolicy(resourceRecord, newZone, true, params.defpol, params.defpolOverrideLocal, params.maxTTL, logger);
599 }
0c98e3de 600 }
4fc57a40 601 }
4ba9d5dc 602
4fc57a40
OM
603 /* only update sr now that all changes have been converted */
604 if (currentSR) {
605 params.soaRecordContent = std::move(currentSR);
4ba9d5dc 606 }
4fc57a40
OM
607 SLOG(g_log << Logger::Info << "Had " << totremove << " RPZ removal" << addS(totremove) << ", " << totadd << " addition" << addS(totadd) << " for " << zoneName << " New serial: " << params.soaRecordContent->d_st.serial << endl,
608 logger->info(Logr::Info, "RPZ mutations", "removals", Logging::Loggable(totremove), "additions", Logging::Loggable(totadd), "newserial", Logging::Loggable(params.soaRecordContent->d_st.serial)));
609 newZone->setSerial(params.soaRecordContent->d_st.serial);
610 newZone->setRefresh(params.soaRecordContent->d_st.refresh);
611 setRPZZoneNewState(polName, params.soaRecordContent->d_st.serial, newZone->size(), false, fullUpdate);
612
613 /* we need to replace the existing zone with the new one,
614 but we don't want to touch anything else, especially other zones,
615 since they might have been updated by another RPZ IXFR tracker thread.
616 */
617 if (luaconfsLocal->generation != configGeneration) {
618 SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
619 logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
b59c7fc3 620 return false;
23f886c0 621 }
4fc57a40
OM
622 g_luaconfs.modify([zoneIdx = params.zoneIdx, &newZone](LuaConfigItems& lci) {
623 lci.dfe.setZone(zoneIdx, newZone);
624 });
625
626 if (!params.dumpZoneFileName.empty()) {
627 dumpZoneToDisk(logger, zoneName, newZone, params.dumpZoneFileName);
55a99233 628 }
4fc57a40
OM
629 refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : newZone->getRefresh(), 1U);
630 }
631 catch (const std::exception& e) {
632 SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.what() << endl,
633 logger->error(Logr::Error, e.what(), "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("std::exception")));
634 }
635 catch (const PDNSException& e) {
636 SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.reason << endl,
637 logger->error(Logr::Error, e.reason, "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("PDNSException")));
638 }
b59c7fc3
OM
639 return true;
640}
641
642// As there can be multiple threads doing updates (due to config reloads), we use a multimap.
643// The value contains the actual thread id that owns the struct.
644
645static LockGuarded<std::multimap<DNSName, RPZWaiter&>> condVars;
646
647// Notify all threads trakcing the RPZ name
648bool notifyRPZTracker(const DNSName& name)
649{
650 auto lock = condVars.lock();
651 auto [start, end] = lock->equal_range(name);
652 if (start == end) {
653 // Did not find any thread tracking that RPZ name
654 return false;
655 }
656 while (start != end) {
657 start->second.stop = true;
658 start->second.condVar.notify_one();
659 ++start;
660 }
661 return true;
4fc57a40
OM
662}
663
664void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration)
665{
666 setThreadName("rec/rpzixfr");
667 bool isPreloaded = params.soaRecordContent != nullptr;
668 auto logger = g_slog->withName("rpz");
b59c7fc3 669 RPZWaiter waiter(std::this_thread::get_id());
4fc57a40
OM
670
671 /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
672 std::shared_ptr<DNSFilterEngine::Zone> oldZone = g_luaconfs.getLocal()->dfe.getZone(params.zoneIdx);
673 if (!oldZone) {
674 SLOG(g_log << Logger::Error << "Unable to retrieve RPZ zone with index " << params.zoneIdx << " from the configuration, exiting" << endl,
675 logger->error(Logr::Error, "Unable to retrieve RPZ zone from configuration", "index", Logging::Loggable(params.zoneIdx)));
676 return;
677 }
678
679 // If oldZone failed to load its getRefresh() returns 0, protect against that
680 uint32_t refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : oldZone->getRefresh(), 10U);
681 DNSName zoneName = oldZone->getDomain();
682 std::string polName = !oldZone->getName().empty() ? oldZone->getName() : zoneName.toStringNoDot();
683
684 // Now that we know the name, set it in the logger
685 logger = logger->withValues("zone", Logging::Loggable(zoneName));
686
b59c7fc3
OM
687 {
688 auto lock = condVars.lock();
689 lock->emplace(zoneName, waiter);
690 }
691 preloadRPZFIle(params, zoneName, oldZone, refresh, polName, waiter, logger);
4fc57a40
OM
692
693 bool skipRefreshDelay = isPreloaded;
694
b59c7fc3
OM
695 while (RPZTrackerIteration(params, zoneName, oldZone, refresh, polName, skipRefreshDelay, configGeneration, waiter, logger)) {
696 // empty
697 }
698
699 // Zap our (and only our) RPZWaiter struct out of the multimap
700 auto lock = condVars.lock();
701 auto [start, end] = lock->equal_range(zoneName);
702 while (start != end) {
703 if (start->second.id == std::this_thread::get_id()) {
704 start = lock->erase(start);
705 }
706 else {
707 ++start;
708 }
4ba9d5dc
RG
709 }
710}