]>
Commit | Line | Data |
---|---|---|
288f4aa9 | 1 | /* |
6edbf68a PL |
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 | */ | |
bc7a7b24 OM |
22 | |
23 | #include "rec-main.hh" | |
3e61e7f7 | 24 | |
288f4aa9 | 25 | #include "arguments.hh" |
eeaa1cb6 | 26 | #include "dns_random.hh" |
94e2a9b0 | 27 | #include "ednsextendederror.hh" |
53604c2a | 28 | #include "ednspadding.hh" |
20829585 | 29 | #include "query-local-address.hh" |
87c4554a | 30 | #include "rec-taskqueue.hh" |
eeaa1cb6 OM |
31 | #include "shuffle.hh" |
32 | #include "validate-recursor.hh" | |
aa7929a3 | 33 | |
6b6720de PL |
34 | #ifdef HAVE_SYSTEMD |
35 | #include <systemd/sd-daemon.h> | |
36 | #endif | |
37 | ||
eeaa1cb6 OM |
38 | #ifdef NOD_ENABLED |
39 | #include "nod.hh" | |
40 | #include "logging.hh" | |
41 | #endif /* NOD_ENABLED */ | |
5cc8371b | 42 | |
bc7a7b24 | 43 | thread_local std::shared_ptr<RecursorLua4> t_pdl; |
eeaa1cb6 | 44 | thread_local std::shared_ptr<Regex> t_traceRegex; |
f3493699 | 45 | thread_local FDWrapper t_tracefd = -1; |
babe9430 OM |
46 | thread_local ProtobufServersInfo t_protobufServers; |
47 | thread_local ProtobufServersInfo t_outgoingProtobufServers; | |
f26bf547 | 48 | |
e8e12e8c | 49 | thread_local std::unique_ptr<MT_t> g_multiTasker; // the big MTasker |
ccfadb6c OM |
50 | std::unique_ptr<MemRecursorCache> g_recCache; |
51 | std::unique_ptr<NegCache> g_negCache; | |
39e42424 | 52 | std::unique_ptr<RecursorPacketCache> g_packetCache; |
a7956123 | 53 | |
a5040968 | 54 | thread_local std::unique_ptr<FDMultiplexer> t_fdm; |
be9078b3 | 55 | thread_local std::unique_ptr<addrringbuf_t> t_remotes, t_servfailremotes, t_largeanswerremotes, t_bogusremotes; |
8c1cb8aa | 56 | thread_local std::unique_ptr<boost::circular_buffer<pair<DNSName, uint16_t>>> t_queryring, t_servfailqueryring, t_bogusqueryring; |
f26bf547 | 57 | thread_local std::shared_ptr<NetmaskGroup> t_allowFrom; |
35189b7d KF |
58 | thread_local std::shared_ptr<NetmaskGroup> t_allowNotifyFrom; |
59 | thread_local std::shared_ptr<notifyset_t> t_allowNotifyFor; | |
d187038c | 60 | __thread struct timeval g_now; // timestamp, updated (too) frequently |
d7dae798 | 61 | |
e8e12e8c | 62 | using listenSocketsAddresses_t = map<int, ComboAddress>; // is shared across all threads right now |
3ea54bf0 | 63 | |
d187038c | 64 | static listenSocketsAddresses_t g_listenSocketsAddresses; // is shared across all threads right now |
2ea1d2c0 | 65 | static set<int> g_fromtosockets; // listen sockets that use 'sendfromto()' mechanism (without actually using sendfromto()) |
bc7a7b24 | 66 | NetmaskGroup g_paddingFrom; |
bc7a7b24 | 67 | size_t g_proxyProtocolMaximumSize; |
b35191d7 | 68 | size_t g_maxUDPQueriesPerRound; |
bc7a7b24 | 69 | unsigned int g_maxMThreads; |
bc7a7b24 | 70 | unsigned int g_paddingTag; |
eeaa1cb6 OM |
71 | PaddingMode g_paddingMode; |
72 | uint16_t g_udpTruncationThreshold; | |
bc7a7b24 | 73 | std::atomic<bool> g_quiet; |
bd3a0469 | 74 | bool g_allowNoRD; |
bc7a7b24 | 75 | bool g_logCommonErrors; |
bc7a7b24 OM |
76 | bool g_reusePort{false}; |
77 | bool g_gettagNeedsEDNSOptions{false}; | |
eeaa1cb6 | 78 | bool g_useKernelTimestamp; |
a6f7f5fe | 79 | std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries; |
f097141b | 80 | #ifdef HAVE_BOOST_CONTAINER_FLAT_SET_HPP |
b35191d7 | 81 | boost::container::flat_set<uint16_t> g_avoidUdpSourcePorts; |
f097141b | 82 | #else |
b35191d7 | 83 | std::set<uint16_t> g_avoidUdpSourcePorts; |
f097141b | 84 | #endif |
b35191d7 OM |
85 | uint16_t g_minUdpSourcePort; |
86 | uint16_t g_maxUdpSourcePort; | |
87 | double g_balancingFactor; | |
49a699c4 | 88 | |
c1c29961 | 89 | bool g_lowercaseOutgoing; |
bf19ccfd | 90 | unsigned int g_networkTimeoutMsec; |
d187038c | 91 | uint16_t g_outgoingEDNSBufsize; |
c3828c03 | 92 | |
2b20e918 O |
93 | // Used in Syncres to counts DNSSEC stats for names in a different "universe" |
94 | GlobalStateHolder<SuffixMatchNode> g_xdnssec; | |
559b6c93 PL |
95 | // Used in the Syncres to not throttle certain servers |
96 | GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames; | |
97 | GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks; | |
b4ae1398 | 98 | GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames; |
eeaa1cb6 | 99 | uint64_t g_latencyStatSize; |
559b6c93 | 100 | |
e8e12e8c | 101 | LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fileDesc) |
8c1cb8aa | 102 | { |
e8e12e8c OM |
103 | *fileDesc = makeClientSocket(toaddr.sin4.sin_family); |
104 | if (*fileDesc < 0) { // temporary error - receive exception otherwise | |
8c1cb8aa | 105 | return LWResult::Result::OSLimitError; |
4ef015cd BH |
106 | } |
107 | ||
e8e12e8c | 108 | if (connect(*fileDesc, reinterpret_cast<const struct sockaddr*>(&toaddr), toaddr.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)) |
8c1cb8aa | 109 | int err = errno; |
80baf329 | 110 | try { |
e8e12e8c | 111 | closesocket(*fileDesc); |
80baf329 | 112 | } |
8c1cb8aa | 113 | catch (const PDNSException& e) { |
91092a9f OM |
114 | SLOG(g_log << Logger::Error << "Error closing UDP socket after connect() failed: " << e.reason << endl, |
115 | g_slogout->error(Logr::Error, e.reason, "Error closing UDP socket after connect() failed", "exception", Logging::Loggable("PDNSException"))); | |
80baf329 | 116 | } |
2bee9b7c | 117 | |
8c1cb8aa OM |
118 | if (err == ENETUNREACH) { // Seth "My Interfaces Are Like A Yo Yo" Arnold special |
119 | return LWResult::Result::OSLimitError; | |
a7b68ae7 | 120 | } |
3ddb9247 | 121 | |
8c1cb8aa | 122 | return LWResult::Result::PermanentError; |
4ef015cd | 123 | } |
d8f6d49f | 124 | |
8c1cb8aa OM |
125 | d_numsocks++; |
126 | return LWResult::Result::Success; | |
127 | } | |
2bee9b7c | 128 | |
8c1cb8aa | 129 | // return a socket to the pool, or simply erase it |
e8e12e8c | 130 | void UDPClientSocks::returnSocket(int fileDesc) |
8c1cb8aa OM |
131 | { |
132 | try { | |
e8e12e8c | 133 | t_fdm->removeReadFD(fileDesc); |
8c1cb8aa OM |
134 | } |
135 | catch (const FDMultiplexerException& e) { | |
136 | // we sometimes return a socket that has not yet been assigned to t_fdm | |
137 | } | |
138 | ||
139 | try { | |
e8e12e8c | 140 | closesocket(fileDesc); |
8c1cb8aa OM |
141 | } |
142 | catch (const PDNSException& e) { | |
91092a9f OM |
143 | SLOG(g_log << Logger::Error << "Error closing returned UDP socket: " << e.reason << endl, |
144 | g_slogout->error(Logr::Error, e.reason, "Error closing returned UDP socket", "exception", Logging::Loggable("PDNSException"))); | |
8c1cb8aa OM |
145 | } |
146 | ||
147 | --d_numsocks; | |
148 | } | |
149 | ||
150 | // returns -1 for errors which might go away, throws for ones that won't | |
eeaa1cb6 | 151 | int UDPClientSocks::makeClientSocket(int family) |
8c1cb8aa OM |
152 | { |
153 | int ret = socket(family, SOCK_DGRAM, 0); // turns out that setting CLO_EXEC and NONBLOCK from here is not a performance win on Linux (oddly enough) | |
42c235e5 | 154 | |
8c1cb8aa OM |
155 | if (ret < 0 && errno == EMFILE) { // this is not a catastrophic error |
156 | return ret; | |
157 | } | |
158 | if (ret < 0) { | |
18e9f940 OM |
159 | int err = errno; |
160 | throw PDNSException("Making a socket for resolver (family = " + std::to_string(family) + "): " + stringerror(err)); | |
8c1cb8aa | 161 | } |
3ddb9247 | 162 | |
8c1cb8aa OM |
163 | // The loop below runs the body with [tries-1 tries-2 ... 1]. Last iteration with tries == 1 is special: it uses a kernel |
164 | // allocated UDP port. | |
165 | #if !defined(__OpenBSD__) | |
166 | int tries = 10; | |
2ea1d2c0 | 167 | #else |
8c1cb8aa | 168 | int tries = 2; // hit the reliable kernel random case for OpenBSD immediately (because it will match tries==1 below), using sysctl net.inet.udp.baddynamic to exclude ports |
2ea1d2c0 | 169 | #endif |
8c1cb8aa | 170 | ComboAddress sin; |
18e9f940 | 171 | while (--tries != 0) { |
e8e12e8c | 172 | in_port_t port = 0; |
5a38281c | 173 | |
8c1cb8aa OM |
174 | if (tries == 1) { // last iteration: fall back to kernel 'random' |
175 | port = 0; | |
d8f6d49f | 176 | } |
8c1cb8aa OM |
177 | else { |
178 | do { | |
b35191d7 | 179 | port = g_minUdpSourcePort + dns_random(g_maxUdpSourcePort - g_minUdpSourcePort + 1); |
18e9f940 | 180 | } while (g_avoidUdpSourcePorts.count(port) != 0); |
9ec48f21 RG |
181 | } |
182 | ||
8c1cb8aa | 183 | sin = pdns::getQueryLocalAddress(family, port); // does htons for us |
e8e12e8c | 184 | if (::bind(ret, reinterpret_cast<struct sockaddr*>(&sin), sin.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
8c1cb8aa | 185 | break; |
18e9f940 | 186 | } |
8c1cb8aa OM |
187 | } |
188 | ||
18e9f940 OM |
189 | int err = errno; |
190 | ||
191 | if (tries == 0) { | |
8c1cb8aa | 192 | closesocket(ret); |
18e9f940 | 193 | throw PDNSException("Resolver binding to local query client socket on " + sin.toString() + ": " + stringerror(err)); |
d8f6d49f | 194 | } |
49a699c4 | 195 | |
8c1cb8aa OM |
196 | try { |
197 | setReceiveSocketErrors(ret, family); | |
198 | setNonBlocking(ret); | |
199 | } | |
200 | catch (...) { | |
201 | closesocket(ret); | |
202 | throw; | |
203 | } | |
204 | return ret; | |
205 | } | |
eeaa1cb6 | 206 | |
e8e12e8c | 207 | static void handleGenUDPQueryResponse(int fileDesc, FDMultiplexer::funcparam_t& var) |
eeaa1cb6 | 208 | { |
e8e12e8c | 209 | auto pident = boost::any_cast<std::shared_ptr<PacketID>>(var); |
eeaa1cb6 OM |
210 | PacketBuffer resp; |
211 | resp.resize(512); | |
212 | ComboAddress fromaddr; | |
213 | socklen_t addrlen = sizeof(fromaddr); | |
214 | ||
22577619 | 215 | ssize_t ret = recvfrom(fileDesc, resp.data(), resp.size(), 0, reinterpret_cast<sockaddr*>(&fromaddr), &addrlen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
eeaa1cb6 | 216 | if (fromaddr != pident->remote) { |
91092a9f OM |
217 | SLOG(g_log << Logger::Notice << "Response received from the wrong remote host (" << fromaddr.toStringWithPort() << " instead of " << pident->remote.toStringWithPort() << "), discarding" << endl, |
218 | g_slog->withName("lua")->info(Logr::Notice, "Response received from the wrong remote host. discarding", "method", Logging::Loggable("GenUDPQueryResponse"), "fromaddr", Logging::Loggable(fromaddr), "expected", Logging::Loggable(pident->remote))); | |
eeaa1cb6 OM |
219 | } |
220 | ||
e8e12e8c | 221 | t_fdm->removeReadFD(fileDesc); |
8c1cb8aa | 222 | if (ret >= 0) { |
cbb5ac45 | 223 | resp.resize(ret); |
e8e12e8c | 224 | g_multiTasker->sendEvent(pident, &resp); |
eeaa1cb6 OM |
225 | } |
226 | else { | |
227 | PacketBuffer empty; | |
e8e12e8c | 228 | g_multiTasker->sendEvent(pident, &empty); |
eeaa1cb6 OM |
229 | // cerr<<"Had some kind of error: "<<ret<<", "<<stringerror()<<endl; |
230 | } | |
231 | } | |
232 | ||
233 | PacketBuffer GenUDPQueryResponse(const ComboAddress& dest, const string& query) | |
234 | { | |
e8e12e8c OM |
235 | Socket socket(dest.sin4.sin_family, SOCK_DGRAM); |
236 | socket.setNonBlocking(); | |
eeaa1cb6 OM |
237 | ComboAddress local = pdns::getQueryLocalAddress(dest.sin4.sin_family, 0); |
238 | ||
e8e12e8c OM |
239 | socket.bind(local); |
240 | socket.connect(dest); | |
241 | socket.send(query); | |
eeaa1cb6 OM |
242 | |
243 | std::shared_ptr<PacketID> pident = std::make_shared<PacketID>(); | |
e8e12e8c | 244 | pident->fd = socket.getHandle(); |
eeaa1cb6 OM |
245 | pident->remote = dest; |
246 | pident->type = 0; | |
e8e12e8c | 247 | t_fdm->addReadFD(socket.getHandle(), handleGenUDPQueryResponse, pident); |
eeaa1cb6 OM |
248 | |
249 | PacketBuffer data; | |
e8e12e8c | 250 | int ret = g_multiTasker->waitEvent(pident, &data, g_networkTimeoutMsec); |
eeaa1cb6 | 251 | |
e8e12e8c OM |
252 | if (ret == 0 || ret == -1) { // timeout |
253 | t_fdm->removeReadFD(socket.getHandle()); | |
eeaa1cb6 | 254 | } |
8c1cb8aa | 255 | else if (data.empty()) { // error, EOF or other |
eeaa1cb6 OM |
256 | // we could special case this |
257 | return data; | |
258 | } | |
259 | return data; | |
260 | } | |
261 | ||
e8e12e8c | 262 | static void handleUDPServerResponse(int fileDesc, FDMultiplexer::funcparam_t& var); |
eeaa1cb6 | 263 | |
eeaa1cb6 | 264 | thread_local std::unique_ptr<UDPClientSocks> t_udpclientsocks; |
4ef015cd | 265 | |
288f4aa9 | 266 | /* these two functions are used by LWRes */ |
e8cf6180 | 267 | LWResult::Result asendto(const void* data, size_t len, int /* flags */, |
e8e12e8c | 268 | const ComboAddress& toAddress, uint16_t qid, const DNSName& domain, uint16_t qtype, bool ecs, int* fileDesc) |
288f4aa9 | 269 | { |
34801ab1 | 270 | |
a2eef000 O |
271 | auto pident = std::make_shared<PacketID>(); |
272 | pident->domain = domain; | |
e8e12e8c | 273 | pident->remote = toAddress; |
a2eef000 | 274 | pident->type = qtype; |
34801ab1 | 275 | |
51dca170 | 276 | // We cannot merge ECS-enabled queries based on the ECS source only, as the scope |
444f1341 OM |
277 | // of the response might be narrower, so instead we do not chain ECS-enabled queries |
278 | // at all. | |
5b90e5da OM |
279 | if (!ecs) { |
280 | // See if there is an existing outstanding request we can chain on to, using partial equivalence | |
281 | // function looking for the same query (qname and qtype) to the same host, but with a different | |
282 | // message ID. | |
2e8bc0d8 | 283 | auto chain = g_multiTasker->getWaiters().equal_range(pident, PacketIDBirthdayCompare()); |
5b90e5da OM |
284 | |
285 | for (; chain.first != chain.second; chain.first++) { | |
00c48b6d | 286 | // Line below detected an issue with the two ways of ordering PacketIDs (birthday and non-birthday) |
e8e12e8c | 287 | assert(chain.first->key->domain == pident->domain); // NOLINT |
5b90e5da OM |
288 | // don't chain onto existing chained waiter or a chain already processed |
289 | if (chain.first->key->fd > -1 && !chain.first->key->closed) { | |
e8e12e8c OM |
290 | chain.first->key->chain.insert(qid); // we can chain |
291 | *fileDesc = -1; // gets used in waitEvent / sendEvent later on | |
5b90e5da OM |
292 | return LWResult::Result::Success; |
293 | } | |
34801ab1 BH |
294 | } |
295 | } | |
296 | ||
e8e12e8c | 297 | auto ret = t_udpclientsocks->getSocket(toAddress, fileDesc); |
308f4c43 | 298 | if (ret != LWResult::Result::Success) { |
d8f6d49f | 299 | return ret; |
308f4c43 | 300 | } |
34801ab1 | 301 | |
e8e12e8c OM |
302 | pident->fd = *fileDesc; |
303 | pident->id = qid; | |
3ddb9247 | 304 | |
e8e12e8c OM |
305 | t_fdm->addReadFD(*fileDesc, handleUDPServerResponse, pident); |
306 | ssize_t sent = send(*fileDesc, data, len, 0); | |
bb4bdbaf | 307 | |
5b0ddd18 | 308 | int tmp = errno; |
bb4bdbaf | 309 | |
308f4c43 | 310 | if (sent < 0) { |
e8e12e8c | 311 | t_udpclientsocks->returnSocket(*fileDesc); |
308f4c43 RG |
312 | errno = tmp; // this is for logging purposes only |
313 | return LWResult::Result::PermanentError; | |
314 | } | |
bb4bdbaf | 315 | |
308f4c43 | 316 | return LWResult::Result::Success; |
288f4aa9 BH |
317 | } |
318 | ||
e8e12e8c OM |
319 | LWResult::Result arecvfrom(PacketBuffer& packet, int /* flags */, const ComboAddress& fromAddr, size_t& len, |
320 | uint16_t qid, const DNSName& domain, uint16_t qtype, int fileDesc, const struct timeval& now) | |
288f4aa9 | 321 | { |
e73106c0 | 322 | static const unsigned int nearMissLimit = ::arg().asNum("spoof-nearmiss-max"); |
0d5f0a9f | 323 | |
a2eef000 | 324 | auto pident = std::make_shared<PacketID>(); |
e8e12e8c OM |
325 | pident->fd = fileDesc; |
326 | pident->id = qid; | |
a2eef000 O |
327 | pident->domain = domain; |
328 | pident->type = qtype; | |
e8e12e8c | 329 | pident->remote = fromAddr; |
b636533b | 330 | |
e8e12e8c | 331 | int ret = g_multiTasker->waitEvent(pident, &packet, g_networkTimeoutMsec, &now); |
0c6e4870 | 332 | len = 0; |
34801ab1 | 333 | |
9ec48f21 | 334 | /* -1 means error, 0 means timeout, 1 means a result from handleUDPServerResponse() which might still be an error */ |
308f4c43 | 335 | if (ret > 0) { |
9ec48f21 | 336 | /* handleUDPServerResponse() will close the socket for us no matter what */ |
308f4c43 RG |
337 | if (packet.empty()) { // means "error" |
338 | return LWResult::Result::PermanentError; | |
339 | } | |
998a4334 | 340 | |
d6126b8b | 341 | len = packet.size(); |
f128d20d | 342 | |
a2eef000 | 343 | if (nearMissLimit > 0 && pident->nearMisses > nearMissLimit) { |
027e8f47 RG |
344 | /* we have received more than nearMissLimit answers on the right IP and port, from the right source (we are using connected sockets), |
345 | for the correct qname and qtype, but with an unexpected message ID. That looks like a spoofing attempt. */ | |
e8e12e8c | 346 | SLOG(g_log << Logger::Error << "Too many (" << pident->nearMisses << " > " << nearMissLimit << ") answers with a wrong message ID for '" << domain << "' from " << fromAddr.toString() << ", assuming spoof attempt." << endl, |
91092a9f OM |
347 | g_slogudpin->info(Logr::Error, "Too many answers with a wrong message ID, assuming spoofing attempt", |
348 | "nearmisses", Logging::Loggable(pident->nearMisses), | |
7d0727b1 | 349 | "nearmisslimit", Logging::Loggable(nearMissLimit), |
91092a9f | 350 | "qname", Logging::Loggable(domain), |
e8e12e8c | 351 | "from", Logging::Loggable(fromAddr))); |
7d3d2f4f | 352 | t_Counters.at(rec::Counter::spoofCount)++; |
1a6d9b13 | 353 | return LWResult::Result::Spoofed; |
35ce8576 | 354 | } |
308f4c43 RG |
355 | |
356 | return LWResult::Result::Success; | |
288f4aa9 | 357 | } |
e8e12e8c OM |
358 | /* getting there means error or timeout, it's up to us to close the socket */ |
359 | if (fileDesc >= 0) { | |
360 | t_udpclientsocks->returnSocket(fileDesc); | |
09e6702a | 361 | } |
308f4c43 RG |
362 | |
363 | return ret == 0 ? LWResult::Result::Timeout : LWResult::Result::PermanentError; | |
288f4aa9 BH |
364 | } |
365 | ||
92011b8f | 366 | // the idea is, only do things that depend on the *response* here. Incoming accounting is on incoming. |
d187038c | 367 | static void updateResponseStats(int res, const ComboAddress& remote, unsigned int packetsize, const DNSName* query, uint16_t qtype) |
2cc13433 | 368 | { |
e8e12e8c | 369 | if (packetsize > 1000 && t_largeanswerremotes) { |
92011b8f | 370 | t_largeanswerremotes->push_back(remote); |
e8e12e8c | 371 | } |
8c1cb8aa | 372 | switch (res) { |
2cc13433 | 373 | case RCode::ServFail: |
8c1cb8aa | 374 | if (t_servfailremotes) { |
92011b8f | 375 | t_servfailremotes->push_back(remote); |
e8e12e8c | 376 | if (query != nullptr && t_servfailqueryring) { // packet cache |
e32a8d46 | 377 | t_servfailqueryring->push_back({*query, qtype}); |
e8e12e8c | 378 | } |
92011b8f | 379 | } |
7d3d2f4f | 380 | ++t_Counters.at(rec::Counter::servFails); |
2cc13433 BH |
381 | break; |
382 | case RCode::NXDomain: | |
7d3d2f4f | 383 | ++t_Counters.at(rec::Counter::nxDomains); |
2cc13433 BH |
384 | break; |
385 | case RCode::NoError: | |
7d3d2f4f | 386 | t_Counters.at(rec::Counter::noErrors)++; |
2cc13433 BH |
387 | break; |
388 | } | |
389 | } | |
390 | ||
e8e12e8c | 391 | static string makeLoginfo(const std::unique_ptr<DNSComboWriter>& comboWriter) |
8c1cb8aa | 392 | try { |
e8e12e8c | 393 | return "(" + comboWriter->d_mdp.d_qname.toLogString() + "/" + DNSRecordContent::NumberToType(comboWriter->d_mdp.d_qtype) + " from " + (comboWriter->getRemote()) + ")"; |
a903b39c | 394 | } |
8c1cb8aa | 395 | catch (...) { |
a903b39c | 396 | return "Exception making error message for exception"; |
397 | } | |
398 | ||
53508135 PL |
399 | /** |
400 | * Chases the CNAME provided by the PolicyCustom RPZ policy. | |
401 | * | |
402 | * @param spoofed: The DNSRecord that was created by the policy, should already be added to ret | |
403 | * @param qtype: The QType of the original query | |
404 | * @param sr: A SyncRes | |
405 | * @param res: An integer that will contain the RCODE of the lookup we do | |
406 | * @param ret: A vector of DNSRecords where the result of the CNAME chase should be appended to | |
407 | */ | |
e8e12e8c | 408 | static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRes& resolver, int& res, vector<DNSRecord>& ret) |
53508135 PL |
409 | { |
410 | if (spoofed.d_type == QType::CNAME) { | |
e8e12e8c OM |
411 | bool oldWantsRPZ = resolver.getWantsRPZ(); |
412 | resolver.setWantsRPZ(false); | |
53508135 | 413 | vector<DNSRecord> ans; |
e8e12e8c | 414 | res = resolver.beginResolve(DNSName(spoofed.getContent()->getZoneRepresentation()), qtype, QClass::IN, ans); |
53508135 | 415 | for (const auto& rec : ans) { |
8c1cb8aa | 416 | if (rec.d_place == DNSResourceRecord::ANSWER) { |
53508135 PL |
417 | ret.push_back(rec); |
418 | } | |
419 | } | |
420 | // Reset the RPZ state of the SyncRes | |
e8e12e8c | 421 | resolver.setWantsRPZ(oldWantsRPZ); |
53508135 PL |
422 | } |
423 | } | |
424 | ||
e8e12e8c | 425 | static bool addRecordToPacket(DNSPacketWriter& packetWritewr, const DNSRecord& rec, uint32_t& minTTL, uint32_t ttlCap, const uint16_t maxAnswerSize, bool& seenAuthSOA) |
97c6d7e5 | 426 | { |
e8e12e8c | 427 | packetWritewr.startRecord(rec.d_name, rec.d_type, (rec.d_ttl > ttlCap ? ttlCap : rec.d_ttl), rec.d_class, rec.d_place); |
97c6d7e5 | 428 | |
91505918 OM |
429 | if (rec.d_type == QType::SOA && rec.d_place == DNSResourceRecord::AUTHORITY) { |
430 | seenAuthSOA = true; | |
431 | } | |
432 | ||
e8e12e8c | 433 | if (rec.d_type != QType::OPT) { // their TTL ain't real |
97c6d7e5 | 434 | minTTL = min(minTTL, rec.d_ttl); |
e8e12e8c | 435 | } |
97c6d7e5 | 436 | |
e8e12e8c OM |
437 | rec.getContent()->toPacket(packetWritewr); |
438 | if (packetWritewr.size() > static_cast<size_t>(maxAnswerSize)) { | |
439 | packetWritewr.rollback(); | |
8c1cb8aa | 440 | if (rec.d_place != DNSResourceRecord::ADDITIONAL) { |
e8e12e8c OM |
441 | packetWritewr.getHeader()->tc = 1; |
442 | packetWritewr.truncate(); | |
97c6d7e5 RG |
443 | } |
444 | return false; | |
445 | } | |
446 | ||
447 | return true; | |
448 | } | |
449 | ||
05402c02 O |
450 | /** |
451 | * A helper class that handles the TCP in-flight bookkeeping on | |
452 | * destruct. This class ise used by startDoResolve() to not forget | |
453 | * that. You can also signal that the TCP connection must be closed | |
454 | * once the in-flight connections drop to zero. | |
455 | **/ | |
8c1cb8aa OM |
456 | class RunningResolveGuard |
457 | { | |
6b425e61 | 458 | public: |
e8e12e8c OM |
459 | RunningResolveGuard(const RunningResolveGuard&) = default; |
460 | RunningResolveGuard(RunningResolveGuard&&) = delete; | |
461 | RunningResolveGuard& operator=(const RunningResolveGuard&) = delete; | |
462 | RunningResolveGuard& operator=(RunningResolveGuard&&) = delete; | |
463 | RunningResolveGuard(std::unique_ptr<DNSComboWriter>& comboWriter) : | |
464 | d_dc(comboWriter) | |
8c1cb8aa | 465 | { |
8b7fde0e O |
466 | if (d_dc->d_tcp && !d_dc->d_tcpConnection) { |
467 | throw std::runtime_error("incoming TCP case without TCP connection"); | |
468 | } | |
6b425e61 | 469 | } |
8c1cb8aa OM |
470 | ~RunningResolveGuard() |
471 | { | |
6b425e61 | 472 | if (!d_handled && d_dc->d_tcp) { |
1d757f4f O |
473 | try { |
474 | finishTCPReply(d_dc, false, true); | |
475 | } | |
476 | catch (const FDMultiplexerException&) { | |
477 | } | |
6b425e61 O |
478 | } |
479 | } | |
8c1cb8aa OM |
480 | void setHandled() |
481 | { | |
6b425e61 O |
482 | d_handled = true; |
483 | } | |
8c1cb8aa OM |
484 | void setDropOnIdle() |
485 | { | |
8b7fde0e O |
486 | if (d_dc->d_tcp) { |
487 | d_dc->d_tcpConnection->setDropOnIdle(); | |
488 | } | |
6b425e61 | 489 | } |
8c1cb8aa | 490 | |
6b425e61 O |
491 | private: |
492 | std::unique_ptr<DNSComboWriter>& d_dc; | |
493 | bool d_handled{false}; | |
494 | }; | |
495 | ||
8c1cb8aa OM |
496 | enum class PolicyResult : uint8_t |
497 | { | |
498 | NoAction, | |
499 | HaveAnswer, | |
500 | Drop | |
501 | }; | |
d4f08082 | 502 | |
e8e12e8c | 503 | static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& comboWriter, SyncRes& resolver, int& res, vector<DNSRecord>& ret, DNSPacketWriter& packetWriter, RunningResolveGuard& tcpGuard) |
d4f08082 RG |
504 | { |
505 | /* don't account truncate actions for TCP queries, since they are not applied */ | |
e8e12e8c | 506 | if (appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !comboWriter->d_tcp) { |
aa790572 OM |
507 | ++t_Counters.at(rec::PolicyHistogram::policy).at(appliedPolicy.d_kind); |
508 | ++t_Counters.at(rec::PolicyNameHits::policyName).counts[appliedPolicy.getName()]; | |
d4f08082 RG |
509 | } |
510 | ||
e8e12e8c OM |
511 | if (resolver.doLog() && appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) { |
512 | SLOG(g_log << Logger::Warning << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << appliedPolicy.getLogString() << endl, | |
513 | appliedPolicy.info(Logr::Warning, resolver.d_slog)); | |
b3e25e9e OM |
514 | } |
515 | ||
af231ceb | 516 | if (appliedPolicy.d_zoneData && appliedPolicy.d_zoneData->d_extendedErrorCode) { |
e8e12e8c OM |
517 | comboWriter->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode; |
518 | comboWriter->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra; | |
af231ceb RG |
519 | } |
520 | ||
d4f08082 RG |
521 | switch (appliedPolicy.d_kind) { |
522 | ||
523 | case DNSFilterEngine::PolicyKind::NoAction: | |
8c1cb8aa | 524 | return PolicyResult::NoAction; |
d4f08082 RG |
525 | |
526 | case DNSFilterEngine::PolicyKind::Drop: | |
6b425e61 | 527 | tcpGuard.setDropOnIdle(); |
7d3d2f4f | 528 | ++t_Counters.at(rec::Counter::policyDrops); |
d4f08082 RG |
529 | return PolicyResult::Drop; |
530 | ||
531 | case DNSFilterEngine::PolicyKind::NXDOMAIN: | |
532 | ret.clear(); | |
46572de5 | 533 | appliedPolicy.addSOAtoRPZResult(ret); |
d4f08082 RG |
534 | res = RCode::NXDomain; |
535 | return PolicyResult::HaveAnswer; | |
536 | ||
537 | case DNSFilterEngine::PolicyKind::NODATA: | |
538 | ret.clear(); | |
46572de5 | 539 | appliedPolicy.addSOAtoRPZResult(ret); |
d4f08082 RG |
540 | res = RCode::NoError; |
541 | return PolicyResult::HaveAnswer; | |
542 | ||
543 | case DNSFilterEngine::PolicyKind::Truncate: | |
e8e12e8c | 544 | if (!comboWriter->d_tcp) { |
d4f08082 | 545 | ret.clear(); |
46572de5 | 546 | appliedPolicy.addSOAtoRPZResult(ret); |
d4f08082 | 547 | res = RCode::NoError; |
e8e12e8c | 548 | packetWriter.getHeader()->tc = 1; |
d4f08082 RG |
549 | return PolicyResult::HaveAnswer; |
550 | } | |
551 | return PolicyResult::NoAction; | |
552 | ||
553 | case DNSFilterEngine::PolicyKind::Custom: | |
554 | res = RCode::NoError; | |
555 | { | |
e8e12e8c OM |
556 | auto spoofed = appliedPolicy.getCustomRecords(comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype); |
557 | for (auto& record : spoofed) { | |
558 | ret.push_back(record); | |
d4f08082 | 559 | try { |
e8e12e8c | 560 | handleRPZCustom(record, QType(comboWriter->d_mdp.d_qtype), resolver, res, ret); |
d4f08082 RG |
561 | } |
562 | catch (const ImmediateServFailException& e) { | |
563 | if (g_logCommonErrors) { | |
e8e12e8c OM |
564 | SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because: " << e.reason << endl, |
565 | resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve of the custom filter policy", | |
22577619 | 566 | "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("ImmediateServFailException"))); |
d4f08082 RG |
567 | } |
568 | res = RCode::ServFail; | |
569 | break; | |
570 | } | |
15e973d6 OM |
571 | catch (const pdns::validation::TooManySEC3IterationsException& e) { |
572 | if (g_logCommonErrors) { | |
573 | SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl, | |
574 | resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve of the custom filter policy", | |
575 | "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("TooManySEC3IterationsException"))); | |
576 | } | |
577 | res = RCode::ServFail; | |
578 | break; | |
579 | } | |
d4f08082 RG |
580 | catch (const PolicyHitException& e) { |
581 | if (g_logCommonErrors) { | |
e8e12e8c OM |
582 | SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because another RPZ policy was hit" << endl, |
583 | resolver.d_slog->info(Logr::Notice, "Sending SERVFAIL during resolve of the custom filter policy because another RPZ policy was hit", | |
22577619 | 584 | "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("PolicyHitException"))); |
d4f08082 RG |
585 | } |
586 | res = RCode::ServFail; | |
587 | break; | |
588 | } | |
589 | } | |
590 | ||
46572de5 | 591 | appliedPolicy.addSOAtoRPZResult(ret); |
d4f08082 RG |
592 | return PolicyResult::HaveAnswer; |
593 | } | |
594 | } | |
595 | ||
596 | return PolicyResult::NoAction; | |
597 | } | |
598 | ||
eeaa1cb6 | 599 | #ifdef NOD_ENABLED |
d61b8a01 | 600 | static bool nodCheckNewDomain(Logr::log_t nodlogger, const DNSName& dname) |
63341e8d | 601 | { |
eeaa1cb6 OM |
602 | bool ret = false; |
603 | // First check the (sub)domain isn't ignored for NOD purposes | |
604 | if (!g_nodDomainWL.check(dname)) { | |
605 | // Now check the NODDB (note this is probabilistic so can have FNs/FPs) | |
606 | if (t_nodDBp && t_nodDBp->isNewDomain(dname)) { | |
607 | if (g_nodLog) { | |
608 | // This should probably log to a dedicated log file | |
8c1cb8aa | 609 | SLOG(g_log << Logger::Notice << "Newly observed domain nod=" << dname << endl, |
eeaa1cb6 | 610 | nodlogger->info(Logr::Notice, "New domain observed")); |
b773359c | 611 | } |
4387eb5e | 612 | t_Counters.at(rec::Counter::nodCount)++; |
eeaa1cb6 | 613 | ret = true; |
63341e8d | 614 | } |
63341e8d | 615 | } |
eeaa1cb6 | 616 | return ret; |
63341e8d RG |
617 | } |
618 | ||
d61b8a01 | 619 | static void sendNODLookup(Logr::log_t nodlogger, const DNSName& dname) |
63341e8d | 620 | { |
eeaa1cb6 OM |
621 | if (!(g_nodLookupDomain.isRoot())) { |
622 | // Send a DNS A query to <domain>.g_nodLookupDomain | |
623 | DNSName qname; | |
624 | try { | |
625 | qname = dname + g_nodLookupDomain; | |
5e7d1450 | 626 | } |
8c1cb8aa | 627 | catch (const std::range_error& e) { |
2e511a5d OM |
628 | if (g_logCommonErrors) { |
629 | nodlogger->v(10)->error(Logr::Error, "DNSName too long", "Unable to send NOD lookup"); | |
630 | } | |
7d3d2f4f | 631 | ++t_Counters.at(rec::Counter::nodLookupsDroppedOversize); |
5e7d1450 PD |
632 | return; |
633 | } | |
90724faa | 634 | nodlogger->v(10)->info(Logr::Debug, "Sending NOD lookup", "nodqname", Logging::Loggable(qname)); |
3bd27e4a | 635 | vector<DNSRecord> dummy; |
759d43fb | 636 | directResolve(qname, QType::A, QClass::IN, dummy, nullptr, false, nodlogger); |
3bd27e4a RG |
637 | } |
638 | } | |
639 | ||
d61b8a01 | 640 | static bool udrCheckUniqueDNSRecord(Logr::log_t nodlogger, const DNSName& dname, uint16_t qtype, const DNSRecord& record) |
41c542ec NC |
641 | { |
642 | bool ret = false; | |
8c1cb8aa | 643 | if (record.d_place == DNSResourceRecord::ANSWER || record.d_place == DNSResourceRecord::ADDITIONAL) { |
41c542ec | 644 | // Create a string that represent a triplet of (qname, qtype and RR[type, name, content]) |
e8e12e8c OM |
645 | std::stringstream strStream; |
646 | strStream << dname.toDNSStringLC() << ":" << qtype << ":" << qtype << ":" << record.d_type << ":" << record.d_name.toDNSStringLC() << ":" << record.getContent()->getZoneRepresentation(); | |
647 | if (t_udrDBp && t_udrDBp->isUniqueResponse(strStream.str())) { | |
8c1cb8aa | 648 | if (g_udrLog) { |
90724faa | 649 | // This should also probably log to a dedicated file. |
d06dcda4 | 650 | SLOG(g_log << Logger::Notice << "Unique response observed: qname=" << dname << " qtype=" << QType(qtype) << " rrtype=" << QType(record.d_type) << " rrname=" << record.d_name << " rrcontent=" << record.getContent()->getZoneRepresentation() << endl, |
4387eb5e | 651 | nodlogger->info(Logr::Notice, "New response observed", |
91092a9f | 652 | "qtype", Logging::Loggable(QType(qtype)), |
90724faa CHB |
653 | "rrtype", Logging::Loggable(QType(record.d_type)), |
654 | "rrname", Logging::Loggable(record.d_name), | |
d06dcda4 | 655 | "rrcontent", Logging::Loggable(record.getContent()->getZoneRepresentation()));); |
41c542ec | 656 | } |
4387eb5e | 657 | t_Counters.at(rec::Counter::udrCount)++; |
41c542ec NC |
658 | ret = true; |
659 | } | |
660 | } | |
661 | return ret; | |
662 | } | |
af1377b7 NC |
663 | #endif /* NOD_ENABLED */ |
664 | ||
cc05899c | 665 | static bool dns64Candidate(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records); |
aa59465e | 666 | |
22165a18 | 667 | int followCNAMERecords(vector<DNSRecord>& ret, const QType qtype, int rcode) |
37a919d4 RG |
668 | { |
669 | vector<DNSRecord> resolved; | |
670 | DNSName target; | |
e8e12e8c OM |
671 | for (const DNSRecord& record : ret) { |
672 | if (record.d_type == QType::CNAME) { | |
673 | auto rec = getRR<CNAMERecordContent>(record); | |
8c1cb8aa OM |
674 | if (rec) { |
675 | target = rec->getTarget(); | |
37a919d4 RG |
676 | break; |
677 | } | |
678 | } | |
679 | } | |
680 | ||
8c1cb8aa | 681 | if (target.empty()) { |
558d47eb | 682 | return rcode; |
37a919d4 RG |
683 | } |
684 | ||
759d43fb OM |
685 | auto log = g_slog->withName("lua")->withValues("method", Logging::Loggable("followCNAMERecords")); |
686 | rcode = directResolve(target, qtype, QClass::IN, resolved, t_pdl, log); | |
37a919d4 | 687 | |
cc05899c | 688 | if (g_dns64Prefix && qtype == QType::AAAA && dns64Candidate(qtype, rcode, resolved)) { |
aa59465e OM |
689 | rcode = getFakeAAAARecords(target, *g_dns64Prefix, resolved); |
690 | } | |
691 | ||
e8e12e8c OM |
692 | for (DNSRecord& record : resolved) { |
693 | if (record.d_place == DNSResourceRecord::ANSWER) { | |
694 | ret.push_back(std::move(record)); | |
aa59465e | 695 | } |
37a919d4 RG |
696 | } |
697 | return rcode; | |
698 | } | |
699 | ||
ef3ee606 RG |
700 | int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret) |
701 | { | |
759d43fb | 702 | auto log = g_slog->withName("dns64")->withValues("method", Logging::Loggable("getAAAA")); |
acc97511 RG |
703 | /* we pass a separate vector of records because we will be resolving the initial qname |
704 | again, possibly encountering the same CNAME(s), and we don't want to trigger the CNAME | |
705 | loop detection. */ | |
706 | vector<DNSRecord> newRecords; | |
759d43fb | 707 | int rcode = directResolve(qname, QType::A, QClass::IN, newRecords, t_pdl, log); |
acc97511 RG |
708 | |
709 | ret.reserve(ret.size() + newRecords.size()); | |
710 | for (auto& record : newRecords) { | |
711 | ret.push_back(std::move(record)); | |
712 | } | |
ef3ee606 RG |
713 | |
714 | // Remove double CNAME records | |
715 | std::set<DNSName> seenCNAMEs; | |
716 | ret.erase(std::remove_if( | |
8c1cb8aa OM |
717 | ret.begin(), |
718 | ret.end(), | |
e8e12e8c OM |
719 | [&seenCNAMEs](DNSRecord& record) { |
720 | if (record.d_type == QType::CNAME) { | |
721 | auto target = getRR<CNAMERecordContent>(record); | |
8c1cb8aa OM |
722 | if (target == nullptr) { |
723 | return false; | |
724 | } | |
725 | if (seenCNAMEs.count(target->getTarget()) > 0) { | |
726 | // We've had this CNAME before, remove it | |
727 | return true; | |
728 | } | |
729 | seenCNAMEs.insert(target->getTarget()); | |
730 | } | |
731 | return false; | |
732 | }), | |
733 | ret.end()); | |
ef3ee606 RG |
734 | |
735 | bool seenA = false; | |
e8e12e8c OM |
736 | for (DNSRecord& record : ret) { |
737 | if (record.d_type == QType::A && record.d_place == DNSResourceRecord::ANSWER) { | |
738 | if (auto rec = getRR<ARecordContent>(record)) { | |
ef3ee606 | 739 | ComboAddress ipv4(rec->getCA()); |
75e31a0b | 740 | memcpy(&prefix.sin6.sin6_addr.s6_addr[12], &ipv4.sin4.sin_addr.s_addr, sizeof(ipv4.sin4.sin_addr.s_addr)); |
e8e12e8c OM |
741 | record.setContent(std::make_shared<AAAARecordContent>(prefix)); |
742 | record.d_type = QType::AAAA; | |
ef3ee606 RG |
743 | } |
744 | seenA = true; | |
745 | } | |
746 | } | |
747 | ||
748 | if (seenA) { | |
749 | // We've seen an A in the ANSWER section, so there is no need to keep any | |
750 | // SOA in the AUTHORITY section as this is not a NODATA response. | |
751 | ret.erase(std::remove_if( | |
8c1cb8aa OM |
752 | ret.begin(), |
753 | ret.end(), | |
e8e12e8c OM |
754 | [](DNSRecord& record) { |
755 | return (record.d_type == QType::SOA && record.d_place == DNSResourceRecord::AUTHORITY); | |
8c1cb8aa OM |
756 | }), |
757 | ret.end()); | |
ef3ee606 | 758 | } |
7d3d2f4f | 759 | t_Counters.at(rec::Counter::dns64prefixanswers)++; |
ef3ee606 RG |
760 | return rcode; |
761 | } | |
762 | ||
763 | int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret) | |
764 | { | |
765 | /* qname has a reverse ordered IPv6 address, need to extract the underlying IPv4 address from it | |
766 | and turn it into an IPv4 in-addr.arpa query */ | |
767 | ret.clear(); | |
768 | vector<string> parts = qname.getRawLabels(); | |
769 | ||
770 | if (parts.size() < 8) { | |
771 | return -1; | |
772 | } | |
773 | ||
774 | string newquery; | |
e8e12e8c OM |
775 | for (size_t octet = 0; octet < 4; ++octet) { |
776 | newquery += std::to_string(stoll(parts[octet * 2], nullptr, 16) + 16 * stoll(parts[octet * 2 + 1], nullptr, 16)); | |
ef3ee606 RG |
777 | newquery.append(1, '.'); |
778 | } | |
779 | newquery += "in-addr.arpa."; | |
780 | ||
71996c15 OM |
781 | auto log = g_slog->withName("dns64")->withValues("method", Logging::Loggable("getPTR")); |
782 | vector<DNSRecord> answers; | |
783 | int rcode = directResolve(DNSName(newquery), QType::PTR, QClass::IN, answers, t_pdl, log); | |
784 | ||
e8e12e8c OM |
785 | DNSRecord record; |
786 | record.d_name = qname; | |
787 | record.d_type = QType::CNAME; | |
788 | record.setContent(std::make_shared<CNAMERecordContent>(newquery)); | |
df96c2ca | 789 | // Copy the TTL of the synthesized CNAME from the actual answer |
71996c15 | 790 | record.d_ttl = (rcode == RCode::NoError && !answers.empty()) ? answers.at(0).d_ttl : SyncRes::s_minimumTTL; |
e8e12e8c | 791 | ret.push_back(record); |
ef3ee606 | 792 | |
71996c15 | 793 | ret.insert(ret.end(), answers.begin(), answers.end()); |
ef3ee606 | 794 | |
7d3d2f4f | 795 | t_Counters.at(rec::Counter::dns64prefixanswers)++; |
ef3ee606 RG |
796 | return rcode; |
797 | } | |
798 | ||
cc05899c OM |
799 | // RFC 6147 section 5.1 all rcodes except NXDomain should be candidate for dns64 |
800 | // for NoError, check if it is NoData | |
801 | static bool dns64Candidate(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records) | |
802 | { | |
803 | if (rcode == RCode::NoError) { | |
fa5f61e9 | 804 | return SyncRes::answerIsNOData(requestedType, rcode, records); |
cc05899c OM |
805 | } |
806 | return rcode != RCode::NXDomain; | |
807 | } | |
808 | ||
bc7a7b24 | 809 | bool isAllowNotifyForZone(DNSName qname) |
35189b7d KF |
810 | { |
811 | if (t_allowNotifyFor->empty()) { | |
812 | return false; | |
813 | } | |
814 | ||
815 | notifyset_t::const_iterator ret; | |
816 | do { | |
817 | ret = t_allowNotifyFor->find(qname); | |
e8e12e8c | 818 | if (ret != t_allowNotifyFor->end()) { |
35189b7d | 819 | return true; |
e8e12e8c | 820 | } |
35189b7d KF |
821 | } while (qname.chopOff()); |
822 | return false; | |
823 | } | |
824 | ||
ba1a1a4f | 825 | #if defined(HAVE_FSTRM) && defined(NOD_ENABLED) |
9489e2b5 CHB |
826 | #include "dnstap.hh" |
827 | #include "fstrm_logger.hh" | |
828 | ||
829 | static bool isEnabledForNODs(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers) | |
830 | { | |
831 | if (fstreamLoggers == nullptr) { | |
832 | return false; | |
833 | } | |
834 | for (auto& logger : *fstreamLoggers) { | |
835 | if (logger->logNODs()) { | |
836 | return true; | |
837 | } | |
838 | } | |
839 | return false; | |
840 | } | |
841 | static bool isEnabledForUDRs(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers) | |
842 | { | |
843 | if (fstreamLoggers == nullptr) { | |
844 | return false; | |
845 | } | |
846 | for (auto& logger : *fstreamLoggers) { | |
847 | if (logger->logUDRs()) { | |
848 | return true; | |
849 | } | |
850 | } | |
851 | return false; | |
852 | } | |
853 | #endif // HAVE_FSTRM | |
854 | ||
6d437fec | 855 | static void dumpTrace(const string& trace, const timeval& timev) |
42ae54e3 OM |
856 | { |
857 | if (trace.empty()) { | |
858 | return; | |
859 | } | |
6d437fec OM |
860 | timeval now{}; |
861 | Utility::gettimeofday(&now); | |
42ae54e3 OM |
862 | int traceFd = dup(t_tracefd); |
863 | if (traceFd == -1) { | |
864 | int err = errno; | |
865 | SLOG(g_log << Logger::Error << "Could not dup trace file: " << stringerror(err) << endl, | |
866 | g_slog->withName("trace")->error(Logr::Error, err, "Could not dup trace file")); | |
867 | return; | |
868 | } | |
0bae2bdd | 869 | setNonBlocking(traceFd); |
42ae54e3 OM |
870 | auto filep = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(traceFd, "a"), &fclose); |
871 | if (!filep) { | |
872 | int err = errno; | |
873 | SLOG(g_log << Logger::Error << "Could not write to trace file: " << stringerror(err) << endl, | |
874 | g_slog->withName("trace")->error(Logr::Error, err, "Could not write to trace file")); | |
875 | close(traceFd); | |
876 | return; | |
877 | } | |
f688bbaa OM |
878 | timebuf_t timebuf; |
879 | isoDateTimeMillis(timev, timebuf); | |
6d437fec OM |
880 | fprintf(filep.get(), " us === START OF TRACE %s ===\n", timebuf.data()); |
881 | fprintf(filep.get(), "%s", trace.c_str()); | |
f688bbaa | 882 | isoDateTimeMillis(now, timebuf); |
e8e12e8c | 883 | if (ferror(filep.get()) != 0) { |
0bae2bdd OM |
884 | int err = errno; |
885 | SLOG(g_log << Logger::Error << "Problems writing to trace file: " << stringerror(err) << endl, | |
886 | g_slog->withName("trace")->error(Logr::Error, err, "Problems writing to trace file")); | |
68d5b346 OM |
887 | // There's no guarantee the message below will end up in the stream, but we try our best |
888 | clearerr(filep.get()); | |
889 | fprintf(filep.get(), "=== TRACE %s TRUNCATED; USE FILE ARGUMENT INSTEAD OF `-' ===\n", timebuf.data()); | |
890 | } | |
891 | else { | |
892 | fprintf(filep.get(), "=== END OF TRACE %s ===\n", timebuf.data()); | |
0bae2bdd | 893 | } |
42ae54e3 OM |
894 | // fclose by unique_ptr does implicit flush |
895 | } | |
896 | ||
91505918 OM |
897 | static uint32_t capPacketCacheTTL(const struct dnsheader& hdr, uint32_t ttl, bool seenAuthSOA) |
898 | { | |
899 | if (hdr.rcode == RCode::NXDomain || (hdr.rcode == RCode::NoError && hdr.ancount == 0 && seenAuthSOA)) { | |
900 | ttl = std::min(ttl, SyncRes::s_packetcachenegativettl); | |
901 | } | |
902 | else if ((hdr.rcode != RCode::NoError && hdr.rcode != RCode::NXDomain) || (hdr.ancount == 0 && hdr.nscount == 0)) { | |
903 | ttl = min(ttl, SyncRes::s_packetcacheservfailttl); | |
904 | } | |
905 | else { | |
906 | ttl = std::min(ttl, SyncRes::s_packetcachettl); | |
907 | } | |
908 | return ttl; | |
909 | } | |
910 | ||
a4d0f523 OM |
911 | static void addPolicyTagsToPBMessageIfNeeded(DNSComboWriter& comboWriter, pdns::ProtoZero::RecMessage& pbMessage) |
912 | { | |
a89c3250 OM |
913 | /* we do _not_ want to store policy tags set by the gettag hook into the packet cache, |
914 | since the call to gettag for subsequent queries could yield the same PC tag but different policy tags */ | |
a4d0f523 OM |
915 | if (!comboWriter.d_gettagPolicyTags.empty()) { |
916 | for (const auto& tag : comboWriter.d_gettagPolicyTags) { | |
917 | comboWriter.d_policyTags.erase(tag); | |
918 | } | |
919 | } | |
920 | if (!comboWriter.d_policyTags.empty()) { | |
921 | pbMessage.addPolicyTags(comboWriter.d_policyTags); | |
922 | } | |
923 | } | |
924 | ||
e8e12e8c | 925 | void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791 |
288f4aa9 | 926 | { |
b26ff531 | 927 | auto comboWriter = std::unique_ptr<DNSComboWriter>(static_cast<DNSComboWriter*>(arg)); |
e8e12e8c | 928 | SyncRes resolver(comboWriter->d_now); |
288f4aa9 | 929 | try { |
e8e12e8c OM |
930 | if (t_queryring) { |
931 | t_queryring->push_back({comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype}); | |
932 | } | |
92011b8f | 933 | |
e8e12e8c | 934 | uint16_t maxanswersize = comboWriter->d_tcp ? 65535 : min(static_cast<uint16_t>(512), g_udpTruncationThreshold); |
7f7b8d55 | 935 | EDNSOpts edo; |
8c1cb8aa | 936 | std::vector<pair<uint16_t, string>> ednsOpts; |
e8e12e8c | 937 | bool variableAnswer = comboWriter->d_variable; |
8c1cb8aa | 938 | bool haveEDNS = false; |
8ba588d0 | 939 | bool paddingAllowed = false; |
7917914b | 940 | bool addPaddingToResponse = false; |
ca2526f5 NC |
941 | #ifdef NOD_ENABLED |
942 | bool hasUDR = false; | |
fc2e34e3 CHB |
943 | std::shared_ptr<Logr::Logger> nodlogger{nullptr}; |
944 | if (g_udrEnabled || g_nodEnabled) { | |
e8e12e8c | 945 | nodlogger = g_slog->withName("nod")->v(1)->withValues("qname", Logging::Loggable(comboWriter->d_mdp.d_qname)); |
fc2e34e3 | 946 | } |
ca2526f5 | 947 | #endif /* NOD_ENABLED */ |
f1db0de2 PL |
948 | DNSPacketWriter::optvect_t returnedEdnsOptions; // Here we stuff all the options for the return packet |
949 | uint8_t ednsExtRCode = 0; | |
e8e12e8c | 950 | if (getEDNSOpts(comboWriter->d_mdp, &edo)) { |
8c1cb8aa | 951 | haveEDNS = true; |
f1db0de2 PL |
952 | if (edo.d_version != 0) { |
953 | ednsExtRCode = ERCode::BADVERS; | |
954 | } | |
955 | ||
e8e12e8c | 956 | if (!comboWriter->d_tcp) { |
32015748 RG |
957 | /* rfc6891 6.2.3: |
958 | "Values lower than 512 MUST be treated as equal to 512." | |
959 | */ | |
960 | maxanswersize = min(static_cast<uint16_t>(edo.d_packetsize >= 512 ? edo.d_packetsize : 512), g_udpTruncationThreshold); | |
961 | } | |
5164bac3 | 962 | ednsOpts = edo.d_options; |
3af35968 | 963 | maxanswersize -= 11; // EDNS header size |
b40562da | 964 | |
e8e12e8c | 965 | if (!comboWriter->d_responsePaddingDisabled && g_paddingFrom.match(comboWriter->d_remote)) { |
8ba588d0 RG |
966 | paddingAllowed = true; |
967 | if (g_paddingMode == PaddingMode::Always) { | |
7917914b | 968 | addPaddingToResponse = true; |
8ba588d0 RG |
969 | } |
970 | } | |
971 | ||
e8e12e8c OM |
972 | for (const auto& option : edo.d_options) { |
973 | if (option.first == EDNSOptionCode::ECS && g_useIncomingECS && !comboWriter->d_ecsParsed) { | |
974 | comboWriter->d_ecsFound = getEDNSSubnetOptsFromString(option.second, &comboWriter->d_ednssubnet); | |
8c1cb8aa | 975 | } |
e8e12e8c | 976 | else if (option.first == EDNSOptionCode::NSID) { |
f1db0de2 | 977 | const static string mode_server_id = ::arg()["server-id"]; |
8c1cb8aa | 978 | if (mode_server_id != "disabled" && !mode_server_id.empty() && maxanswersize > (EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size())) { |
e32a8d46 | 979 | returnedEdnsOptions.emplace_back(EDNSOptionCode::NSID, mode_server_id); |
f1db0de2 | 980 | variableAnswer = true; // Can't packetcache an answer with NSID |
f49ae8cf | 981 | maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size(); |
f1db0de2 | 982 | } |
8c1cb8aa | 983 | } |
e8e12e8c | 984 | else if (paddingAllowed && !addPaddingToResponse && g_paddingMode == PaddingMode::PaddedQueries && option.first == EDNSOptionCode::PADDING) { |
7917914b | 985 | addPaddingToResponse = true; |
b40562da RG |
986 | } |
987 | } | |
10321a98 | 988 | } |
53604c2a | 989 | |
7917914b RG |
990 | /* the lookup will be done _before_ knowing whether the query actually |
991 | has a padding option, so we need to use the separate tag even when the | |
992 | query does not have padding, as long as it is from an allowed source */ | |
e8e12e8c OM |
993 | if (paddingAllowed && comboWriter->d_tag == 0) { |
994 | comboWriter->d_tag = g_paddingTag; | |
53604c2a RG |
995 | } |
996 | ||
b40562da | 997 | /* perhaps there was no EDNS or no ECS but by now we looked */ |
e8e12e8c | 998 | comboWriter->d_ecsParsed = true; |
e325f20c | 999 | vector<DNSRecord> ret; |
ea634573 | 1000 | vector<uint8_t> packet; |
b23b8614 | 1001 | |
ad42489c | 1002 | auto luaconfsLocal = g_luaconfs.getLocal(); |
b8470add PL |
1003 | // Used to tell syncres later on if we should apply NSDNAME and NSIP RPZ triggers for this query |
1004 | bool wantsRPZ(true); | |
ac10822e | 1005 | RecursorPacketCache::OptPBData pbDataForCache; |
87f46425 | 1006 | pdns::ProtoZero::RecMessage pbMessage; |
63341e8d | 1007 | if (checkProtobufExport(luaconfsLocal)) { |
83b261a7 | 1008 | pbMessage.reserve(128, 128); // It's a bit of a guess... |
e8e12e8c | 1009 | pbMessage.setResponse(comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass); |
83b261a7 | 1010 | pbMessage.setServerIdentity(SyncRes::s_serverID); |
a44a8d66 OM |
1011 | |
1012 | // RRSets added below | |
d9d3f9c1 | 1013 | } |
babe9430 | 1014 | checkOutgoingProtobufExport(luaconfsLocal); // to pick up changed configs |
b9fa43e0 | 1015 | #ifdef HAVE_FSTRM |
9489e2b5 | 1016 | checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo); |
678915f2 | 1017 | checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo); |
b9fa43e0 OM |
1018 | #endif |
1019 | ||
e8e12e8c | 1020 | DNSPacketWriter packetWriter(packet, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass, comboWriter->d_mdp.d_header.opcode); |
ea634573 | 1021 | |
e8e12e8c OM |
1022 | packetWriter.getHeader()->aa = 0; |
1023 | packetWriter.getHeader()->ra = 1; | |
1024 | packetWriter.getHeader()->qr = 1; | |
1025 | packetWriter.getHeader()->tc = 0; | |
1026 | packetWriter.getHeader()->id = comboWriter->d_mdp.d_header.id; | |
1027 | packetWriter.getHeader()->rd = comboWriter->d_mdp.d_header.rd; | |
1028 | packetWriter.getHeader()->cd = comboWriter->d_mdp.d_header.cd; | |
ea634573 | 1029 | |
70fb28d9 RG |
1030 | /* This is the lowest TTL seen in the records of the response, |
1031 | so we can't cache it for longer than this value. | |
1032 | If we have a TTL cap, this value can't be larger than the | |
1033 | cap no matter what. */ | |
e8e12e8c | 1034 | uint32_t minTTL = comboWriter->d_ttlCap; |
91505918 | 1035 | bool seenAuthSOA = false; |
904d3219 | 1036 | |
e8e12e8c OM |
1037 | resolver.d_eventTrace = std::move(comboWriter->d_eventTrace); |
1038 | resolver.setId(g_multiTasker->getTid()); | |
0c43f455 | 1039 | |
8c1cb8aa | 1040 | bool DNSSECOK = false; |
e8e12e8c OM |
1041 | if (comboWriter->d_luaContext) { |
1042 | resolver.setLuaEngine(comboWriter->d_luaContext); | |
3457a2a0 | 1043 | } |
8c1cb8aa | 1044 | if (g_dnssecmode != DNSSECMode::Off) { |
e8e12e8c | 1045 | resolver.setDoDNSSEC(true); |
9eec8c98 PL |
1046 | |
1047 | // Does the requestor want DNSSEC records? | |
e8e12e8c | 1048 | if ((edo.d_extFlags & EDNSOpts::DNSSECOK) != 0) { |
8c1cb8aa | 1049 | DNSSECOK = true; |
7d3d2f4f | 1050 | t_Counters.at(rec::Counter::dnssecQueries)++; |
9eec8c98 | 1051 | } |
e8e12e8c | 1052 | if (comboWriter->d_mdp.d_header.cd) { |
88c33dca RG |
1053 | /* Per rfc6840 section 5.9, "When processing a request with |
1054 | the Checking Disabled (CD) bit set, a resolver SHOULD attempt | |
1055 | to return all response data, even data that has failed DNSSEC | |
1056 | validation. */ | |
7d3d2f4f | 1057 | ++t_Counters.at(rec::Counter::dnssecCheckDisabledQueries); |
88c33dca | 1058 | } |
e8e12e8c | 1059 | if (comboWriter->d_mdp.d_header.ad) { |
88c33dca RG |
1060 | /* Per rfc6840 section 5.7, "the AD bit in a query as a signal |
1061 | indicating that the requester understands and is interested in the | |
1062 | value of the AD bit in the response. This allows a requester to | |
1063 | indicate that it understands the AD bit without also requesting | |
1064 | DNSSEC data via the DO bit. */ | |
7d3d2f4f | 1065 | ++t_Counters.at(rec::Counter::dnssecAuthenticDataQueries); |
88c33dca | 1066 | } |
8c1cb8aa OM |
1067 | } |
1068 | else { | |
9eec8c98 | 1069 | // Ignore the client-set CD flag |
e8e12e8c | 1070 | packetWriter.getHeader()->cd = 0; |
5b9853c9 | 1071 | } |
e8e12e8c | 1072 | resolver.setDNSSECValidationRequested(g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || ((comboWriter->d_mdp.d_header.ad || DNSSECOK) && g_dnssecmode == DNSSECMode::Process)); |
0c43f455 | 1073 | |
e8e12e8c OM |
1074 | resolver.setInitialRequestId(comboWriter->d_uuid); |
1075 | resolver.setOutgoingProtobufServers(t_outgoingProtobufServers.servers); | |
b9fa43e0 | 1076 | #ifdef HAVE_FSTRM |
e8e12e8c | 1077 | resolver.setFrameStreamServers(t_frameStreamServersInfo.servers); |
b9fa43e0 | 1078 | #endif |
94770151 | 1079 | |
20bfad6d OM |
1080 | bool useMapped = true; |
1081 | // If proxy by table is active and had a match, we only want to use the mapped address if it also has a domain match | |
1082 | // (if a domain suffix match table is present in the config) | |
e8e12e8c OM |
1083 | if (t_proxyMapping && comboWriter->d_source != comboWriter->d_mappedSource) { |
1084 | if (const auto* iter = t_proxyMapping->lookup(comboWriter->d_source)) { | |
1085 | if (iter->second.suffixMatchNode) { | |
1086 | if (!iter->second.suffixMatchNode->check(comboWriter->d_mdp.d_qname)) { | |
20bfad6d OM |
1087 | // No match in domains, use original source |
1088 | useMapped = false; | |
1089 | } | |
c375521b | 1090 | else { |
e8e12e8c | 1091 | ++iter->second.stats.suffixMatches; |
c375521b | 1092 | } |
20bfad6d OM |
1093 | } |
1094 | // No suffix match node defined, use mapped address | |
1095 | } | |
1096 | // lookup failing cannot happen as dc->d_source != dc->d_mappedSource | |
1097 | } | |
e8e12e8c | 1098 | resolver.setQuerySource(useMapped ? comboWriter->d_mappedSource : comboWriter->d_source, g_useIncomingECS && !comboWriter->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(comboWriter->d_ednssubnet) : boost::none); |
20bfad6d | 1099 | |
e8e12e8c | 1100 | resolver.setQueryReceivedOverTCP(comboWriter->d_tcp); |
57769f13 | 1101 | |
8c1cb8aa | 1102 | bool tracedQuery = false; // we could consider letting Lua know about this too |
9fc36e90 | 1103 | bool shouldNotValidate = false; |
904d3219 | 1104 | |
ef3b6cd7 RG |
1105 | /* preresolve expects res (dq.rcode) to be set to RCode::NoError by default */ |
1106 | int res = RCode::NoError; | |
37a919d4 | 1107 | |
1f1ca368 | 1108 | DNSFilterEngine::Policy appliedPolicy; |
e8e12e8c OM |
1109 | RecursorLua4::DNSQuestion dnsQuestion(comboWriter->d_source, comboWriter->d_destination, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_tcp, variableAnswer, wantsRPZ, comboWriter->d_logResponse, addPaddingToResponse, (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) ? comboWriter->d_kernelTimestamp : comboWriter->d_now); |
1110 | dnsQuestion.ednsFlags = &edo.d_extFlags; | |
1111 | dnsQuestion.ednsOptions = &ednsOpts; | |
1112 | dnsQuestion.tag = comboWriter->d_tag; | |
1113 | dnsQuestion.discardedPolicies = &resolver.d_discardedPolicies; | |
1114 | dnsQuestion.policyTags = &comboWriter->d_policyTags; | |
1115 | dnsQuestion.appliedPolicy = &appliedPolicy; | |
1116 | dnsQuestion.currentRecords = &ret; | |
1117 | dnsQuestion.dh = &comboWriter->d_mdp.d_header; | |
1118 | dnsQuestion.data = comboWriter->d_data; | |
1119 | dnsQuestion.requestorId = comboWriter->d_requestorId; | |
1120 | dnsQuestion.deviceId = comboWriter->d_deviceId; | |
1121 | dnsQuestion.deviceName = comboWriter->d_deviceName; | |
1122 | dnsQuestion.proxyProtocolValues = &comboWriter->d_proxyProtocolValues; | |
1123 | dnsQuestion.extendedErrorCode = &comboWriter->d_extendedErrorCode; | |
1124 | dnsQuestion.extendedErrorExtra = &comboWriter->d_extendedErrorExtra; | |
1125 | dnsQuestion.meta = std::move(comboWriter->d_meta); | |
1126 | dnsQuestion.fromAuthIP = &resolver.d_fromAuthIP; | |
1127 | ||
1128 | resolver.d_slog = resolver.d_slog->withValues("qname", Logging::Loggable(comboWriter->d_mdp.d_qname), | |
22577619 OM |
1129 | "qtype", Logging::Loggable(QType(comboWriter->d_mdp.d_qtype)), |
1130 | "remote", Logging::Loggable(comboWriter->getRemote()), | |
1131 | "proto", Logging::Loggable(comboWriter->d_tcp ? "tcp" : "udp"), | |
1132 | "ecs", Logging::Loggable(comboWriter->d_ednssubnet.source.empty() ? "" : comboWriter->d_ednssubnet.source.toString()), | |
1133 | "mtid", Logging::Loggable(g_multiTasker->getTid())); | |
e8e12e8c OM |
1134 | RunningResolveGuard tcpGuard(comboWriter); |
1135 | ||
1136 | if (ednsExtRCode != 0 || comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) { | |
1137 | goto sendit; // NOLINT(cppcoreguidelines-avoid-goto) | |
1138 | } | |
1139 | ||
1140 | if (comboWriter->d_mdp.d_qtype == QType::ANY && !comboWriter->d_tcp && g_anyToTcp) { | |
1141 | packetWriter.getHeader()->tc = 1; | |
56b4d21b PD |
1142 | res = 0; |
1143 | variableAnswer = true; | |
e8e12e8c | 1144 | goto sendit; // NOLINT(cppcoreguidelines-avoid-goto) |
e661a20b PD |
1145 | } |
1146 | ||
e8e12e8c OM |
1147 | if (t_traceRegex && t_traceRegex->match(comboWriter->d_mdp.d_qname.toString())) { |
1148 | resolver.setLogMode(SyncRes::Store); | |
8c1cb8aa | 1149 | tracedQuery = true; |
77499b05 | 1150 | } |
3ddb9247 | 1151 | |
8c1cb8aa | 1152 | if (!g_quiet || tracedQuery) { |
ab26d8d5 | 1153 | if (!g_slogStructured) { |
e8e12e8c OM |
1154 | g_log << Logger::Warning << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] " << (comboWriter->d_tcp ? "TCP " : "") << "question for '" << comboWriter->d_mdp.d_qname << "|" |
1155 | << QType(comboWriter->d_mdp.d_qtype) << "' from " << comboWriter->getRemote(); | |
1156 | if (!comboWriter->d_ednssubnet.source.empty()) { | |
1157 | g_log << " (ecs " << comboWriter->d_ednssubnet.source.toString() << ")"; | |
ab26d8d5 OM |
1158 | } |
1159 | g_log << endl; | |
1160 | } | |
1161 | else { | |
e8e12e8c | 1162 | resolver.d_slog->info(Logr::Info, "Question"); |
6e986f5e | 1163 | } |
976ec823 | 1164 | } |
c75a6a9e | 1165 | |
e8e12e8c | 1166 | if (!comboWriter->d_mdp.d_header.rd) { |
bd3a0469 OM |
1167 | if (g_allowNoRD) { |
1168 | resolver.setCacheOnly(); | |
1169 | } | |
1170 | else { | |
1171 | ret.clear(); | |
1172 | res = RCode::Refused; | |
1173 | goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto) | |
1174 | } | |
37a919d4 RG |
1175 | } |
1176 | ||
e8e12e8c OM |
1177 | if (comboWriter->d_luaContext) { |
1178 | comboWriter->d_luaContext->prerpz(dnsQuestion, res, resolver.d_eventTrace); | |
0a273054 RG |
1179 | } |
1180 | ||
16649cb0 | 1181 | // Check if the client has a policy attached to it |
99ced1e1 | 1182 | if (wantsRPZ && !appliedPolicy.wasHit()) { |
baaed53b | 1183 | |
e8e12e8c OM |
1184 | if (luaconfsLocal->dfe.getClientPolicy(comboWriter->d_source, resolver.d_discardedPolicies, appliedPolicy)) { |
1185 | mergePolicyTags(comboWriter->d_policyTags, appliedPolicy.getTags()); | |
b502d522 | 1186 | } |
0a273054 | 1187 | } |
644dd1da | 1188 | |
d4f08082 RG |
1189 | /* If we already have an answer generated from gettag_ffi, let's see if the filtering policies |
1190 | should be applied to it */ | |
e8e12e8c | 1191 | if (comboWriter->d_rcode != boost::none) { |
d4f08082 RG |
1192 | |
1193 | bool policyOverride = false; | |
1194 | /* Unless we already matched on the client IP, time to check the qname. | |
1195 | We normally check it in beginResolve() but it will be bypassed since we already have an answer */ | |
1196 | if (wantsRPZ && appliedPolicy.policyOverridesGettag()) { | |
1197 | if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) { | |
1198 | // Client IP already matched | |
1199 | } | |
1200 | else { | |
1201 | // no match on the client IP, check the qname | |
e8e12e8c | 1202 | if (luaconfsLocal->dfe.getQueryPolicy(comboWriter->d_mdp.d_qname, resolver.d_discardedPolicies, appliedPolicy)) { |
d4f08082 | 1203 | // got a match |
e8e12e8c | 1204 | mergePolicyTags(comboWriter->d_policyTags, appliedPolicy.getTags()); |
d4f08082 RG |
1205 | } |
1206 | } | |
1207 | ||
99ced1e1 | 1208 | if (appliedPolicy.wasHit()) { |
d4f08082 | 1209 | policyOverride = true; |
baaed53b OM |
1210 | } |
1211 | } | |
644dd1da | 1212 | |
995884ff | 1213 | if (!policyOverride) { |
d4f08082 | 1214 | /* No RPZ or gettag overrides it anyway */ |
e8e12e8c OM |
1215 | ret = std::move(comboWriter->d_records); |
1216 | res = *comboWriter->d_rcode; | |
1217 | if (res == RCode::NoError && comboWriter->d_followCNAMERecords) { | |
1218 | res = followCNAMERecords(ret, QType(comboWriter->d_mdp.d_qtype), res); | |
96491e6c | 1219 | } |
e8e12e8c | 1220 | goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto) |
96491e6c OM |
1221 | } |
1222 | } | |
1223 | ||
54be222b | 1224 | // if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve |
e8e12e8c | 1225 | if (!comboWriter->d_luaContext || !comboWriter->d_luaContext->preresolve(dnsQuestion, res, resolver.d_eventTrace)) { |
ef3ee606 | 1226 | |
e8e12e8c OM |
1227 | if (!g_dns64PrefixReverse.empty() && dnsQuestion.qtype == QType::PTR && dnsQuestion.qname.isPartOf(g_dns64PrefixReverse)) { |
1228 | res = getFakePTRRecords(dnsQuestion.qname, ret); | |
1229 | goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto) | |
ef3ee606 | 1230 | } |
b8470add | 1231 | |
e8e12e8c | 1232 | resolver.setWantsRPZ(wantsRPZ); |
acf86ed7 | 1233 | |
b502d522 | 1234 | if (wantsRPZ && appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { |
acf86ed7 | 1235 | |
e8e12e8c | 1236 | if (comboWriter->d_luaContext && comboWriter->d_luaContext->policyHitEventFilter(comboWriter->d_source, comboWriter->d_mdp.d_qname, QType(comboWriter->d_mdp.d_qtype), comboWriter->d_tcp, appliedPolicy, comboWriter->d_policyTags, resolver.d_discardedPolicies)) { |
acf86ed7 RG |
1237 | /* reset to no match */ |
1238 | appliedPolicy = DNSFilterEngine::Policy(); | |
b502d522 | 1239 | } |
acf86ed7 | 1240 | else { |
e8e12e8c | 1241 | auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard); |
acf86ed7 | 1242 | if (policyResult == PolicyResult::HaveAnswer) { |
e8e12e8c OM |
1243 | if (g_dns64Prefix && dnsQuestion.qtype == QType::AAAA && dns64Candidate(comboWriter->d_mdp.d_qtype, res, ret)) { |
1244 | res = getFakeAAAARecords(dnsQuestion.qname, *g_dns64Prefix, ret); | |
92f829c4 RG |
1245 | shouldNotValidate = true; |
1246 | } | |
e8e12e8c | 1247 | goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto) |
acf86ed7 RG |
1248 | } |
1249 | else if (policyResult == PolicyResult::Drop) { | |
1250 | return; | |
1251 | } | |
b8470add | 1252 | } |
db486de5 PL |
1253 | } |
1254 | ||
acf86ed7 | 1255 | // Query did not get handled for Client IP or QNAME Policy reasons, now actually go out to find an answer |
44971ca0 | 1256 | try { |
e8e12e8c OM |
1257 | resolver.d_appliedPolicy = appliedPolicy; |
1258 | resolver.d_policyTags = std::move(comboWriter->d_policyTags); | |
d6fd3cb8 | 1259 | |
e8e12e8c OM |
1260 | if (!comboWriter->d_routingTag.empty()) { |
1261 | resolver.d_routingTag = comboWriter->d_routingTag; | |
d6fd3cb8 OM |
1262 | } |
1263 | ||
16649cb0 | 1264 | ret.clear(); // policy might have filled it with custom records but we decided not to use them |
e8e12e8c OM |
1265 | res = resolver.beginResolve(comboWriter->d_mdp.d_qname, QType(comboWriter->d_mdp.d_qtype), comboWriter->d_mdp.d_qclass, ret); |
1266 | shouldNotValidate = resolver.wasOutOfBand(); | |
44971ca0 | 1267 | } |
d4f08082 | 1268 | catch (const ImmediateQueryDropException& e) { |
acf86ed7 | 1269 | // XXX We need to export a protobuf message (and do a NOD lookup) if requested! |
7d3d2f4f | 1270 | t_Counters.at(rec::Counter::policyDrops)++; |
e8e12e8c OM |
1271 | SLOG(g_log << Logger::Debug << "Dropping query because of a filtering policy " << makeLoginfo(comboWriter) << endl, |
1272 | resolver.d_slog->info(Logr::Debug, "Dropping query because of a filtering policy")); | |
d4f08082 RG |
1273 | return; |
1274 | } | |
8c1cb8aa OM |
1275 | catch (const ImmediateServFailException& e) { |
1276 | if (g_logCommonErrors) { | |
e8e12e8c OM |
1277 | SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of '" << comboWriter->d_mdp.d_qname << "' because: " << e.reason << endl, |
1278 | resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve")); | |
124dd1d4 | 1279 | } |
44971ca0 PD |
1280 | res = RCode::ServFail; |
1281 | } | |
15e973d6 OM |
1282 | catch (const pdns::validation::TooManySEC3IterationsException& e) { |
1283 | if (g_logCommonErrors) { | |
1284 | SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl, | |
1285 | resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve")); | |
1286 | } | |
1287 | res = RCode::ServFail; | |
1288 | } | |
d4f08082 RG |
1289 | catch (const SendTruncatedAnswerException& e) { |
1290 | ret.clear(); | |
e8e12e8c | 1291 | resolver.d_appliedPolicy.addSOAtoRPZResult(ret); |
d4f08082 | 1292 | res = RCode::NoError; |
e8e12e8c | 1293 | packetWriter.getHeader()->tc = 1; |
d4f08082 RG |
1294 | } |
1295 | catch (const PolicyHitException& e) { | |
124dd1d4 RG |
1296 | res = -2; |
1297 | } | |
e8e12e8c OM |
1298 | dnsQuestion.validationState = resolver.getValidationState(); |
1299 | appliedPolicy = resolver.d_appliedPolicy; | |
1300 | comboWriter->d_policyTags = std::move(resolver.d_policyTags); | |
3211bbaf | 1301 | |
af231ceb | 1302 | if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None && appliedPolicy.d_zoneData && appliedPolicy.d_zoneData->d_extendedErrorCode) { |
e8e12e8c OM |
1303 | comboWriter->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode; |
1304 | comboWriter->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra; | |
af231ceb | 1305 | } |
1921a4c2 | 1306 | |
b8470add PL |
1307 | // During lookup, an NSDNAME or NSIP trigger was hit in RPZ |
1308 | if (res == -2) { // XXX This block should be macro'd, it is repeated post-resolve. | |
b502d522 RG |
1309 | if (appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction) { |
1310 | throw PDNSException("NoAction policy returned while a NSDNAME or NSIP trigger was hit"); | |
1311 | } | |
e8e12e8c | 1312 | auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard); |
b502d522 | 1313 | if (policyResult == PolicyResult::HaveAnswer) { |
e8e12e8c | 1314 | goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto) |
b502d522 RG |
1315 | } |
1316 | else if (policyResult == PolicyResult::Drop) { | |
1317 | return; | |
b8470add PL |
1318 | } |
1319 | } | |
1320 | ||
3e5551f3 | 1321 | bool luaHookHandled = false; |
e8e12e8c | 1322 | if (comboWriter->d_luaContext) { |
8f62a0cd | 1323 | PolicyResult policyResult = PolicyResult::NoAction; |
fa5f61e9 | 1324 | if (SyncRes::answerIsNOData(comboWriter->d_mdp.d_qtype, res, ret)) { |
e8e12e8c | 1325 | if (comboWriter->d_luaContext->nodata(dnsQuestion, res, resolver.d_eventTrace)) { |
3e5551f3 | 1326 | luaHookHandled = true; |
eae91c4a | 1327 | shouldNotValidate = true; |
e8e12e8c | 1328 | policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard); |
ef3ee606 | 1329 | } |
8c1cb8aa | 1330 | } |
e8e12e8c | 1331 | else if (res == RCode::NXDomain && comboWriter->d_luaContext->nxdomain(dnsQuestion, res, resolver.d_eventTrace)) { |
3e5551f3 | 1332 | luaHookHandled = true; |
3ca4e735 | 1333 | shouldNotValidate = true; |
e8e12e8c | 1334 | policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard); |
8f62a0cd | 1335 | } |
8f62a0cd | 1336 | if (policyResult == PolicyResult::HaveAnswer) { |
e8e12e8c | 1337 | goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto) |
ef3ee606 | 1338 | } |
8f62a0cd OM |
1339 | else if (policyResult == PolicyResult::Drop) { |
1340 | return; | |
ef3ee606 | 1341 | } |
eae91c4a | 1342 | } // dc->d_luaContext |
db486de5 | 1343 | |
e8e12e8c OM |
1344 | if (!luaHookHandled && g_dns64Prefix && comboWriter->d_mdp.d_qtype == QType::AAAA && (shouldNotValidate || !resolver.isDNSSECValidationRequested() || !vStateIsBogus(dnsQuestion.validationState)) && dns64Candidate(comboWriter->d_mdp.d_qtype, res, ret)) { |
1345 | res = getFakeAAAARecords(dnsQuestion.qname, *g_dns64Prefix, ret); | |
eae91c4a OM |
1346 | shouldNotValidate = true; |
1347 | } | |
db486de5 | 1348 | |
e8e12e8c | 1349 | if (comboWriter->d_luaContext) { |
eae91c4a | 1350 | PolicyResult policyResult = PolicyResult::NoAction; |
e8e12e8c OM |
1351 | if (comboWriter->d_luaContext->d_postresolve_ffi) { |
1352 | RecursorLua4::PostResolveFFIHandle handle(dnsQuestion); | |
1353 | resolver.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI); | |
1354 | bool prResult = comboWriter->d_luaContext->postresolve_ffi(handle); | |
1355 | resolver.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI, prResult, false); | |
1356 | if (prResult) { | |
bb3596da | 1357 | shouldNotValidate = true; |
e8e12e8c | 1358 | policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard); |
d33306db | 1359 | } |
ef3ee606 | 1360 | } |
e8e12e8c | 1361 | else if (comboWriter->d_luaContext->postresolve(dnsQuestion, res, resolver.d_eventTrace)) { |
cc05899c | 1362 | shouldNotValidate = true; |
e8e12e8c | 1363 | policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard); |
cc05899c | 1364 | } |
8f62a0cd | 1365 | if (policyResult == PolicyResult::HaveAnswer) { |
e8e12e8c | 1366 | goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto) |
8f62a0cd OM |
1367 | } |
1368 | else if (policyResult == PolicyResult::Drop) { | |
1369 | return; | |
1370 | } | |
1371 | } // dc->d_luaContext | |
4485aa35 | 1372 | } |
e8e12e8c | 1373 | else if (comboWriter->d_luaContext) { |
ce51bfe2 O |
1374 | // preresolve returned true |
1375 | shouldNotValidate = true; | |
e8e12e8c | 1376 | auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard); |
ce51bfe2 O |
1377 | // haveAnswer case redundant |
1378 | if (policyResult == PolicyResult::Drop) { | |
1379 | return; | |
1380 | } | |
1381 | } | |
d4f08082 | 1382 | |
644dd1da | 1383 | haveAnswer:; |
e8e12e8c OM |
1384 | if (tracedQuery || res == -1 || res == RCode::ServFail || packetWriter.getHeader()->rcode == static_cast<unsigned>(RCode::ServFail)) { |
1385 | dumpTrace(resolver.getTrace(), resolver.d_fixednow); | |
85ffbc53 | 1386 | } |
3ddb9247 | 1387 | |
8c1cb8aa | 1388 | if (res == -1) { |
e8e12e8c | 1389 | packetWriter.getHeader()->rcode = RCode::ServFail; |
0fe1d080 | 1390 | // no commit here, because no record |
7d3d2f4f | 1391 | ++t_Counters.at(rec::Counter::servFails); |
0fe1d080 | 1392 | } |
288f4aa9 | 1393 | else { |
e8e12e8c | 1394 | packetWriter.getHeader()->rcode = res; |
92011b8f | 1395 | |
f3fe4ae6 | 1396 | // Does the validation mode or query demand validation? |
e8e12e8c | 1397 | if (!shouldNotValidate && resolver.isDNSSECValidationRequested()) { |
b25cae9a | 1398 | try { |
e8e12e8c | 1399 | auto state = resolver.getValidationState(); |
4d2be65d | 1400 | |
a1b95336 | 1401 | string x_marker; |
78a8a91b | 1402 | std::shared_ptr<Logr::Logger> log; |
e8e12e8c | 1403 | if (resolver.doLog() || vStateIsBogus(state)) { |
78a8a91b | 1404 | // Only create logging object if needed below, beware if you change the logging logic! |
e8e12e8c | 1405 | log = resolver.d_slog->withValues("vstate", Logging::Loggable(state)); |
a1b95336 | 1406 | auto xdnssec = g_xdnssec.getLocal(); |
e8e12e8c | 1407 | if (xdnssec->check(comboWriter->d_mdp.d_qname)) { |
78a8a91b | 1408 | log = log->withValues("in-x-dnssec-names", Logging::Loggable(1)); |
a1b95336 O |
1409 | x_marker = " [in x-dnssec-names]"; |
1410 | } | |
2e921ec6 | 1411 | } |
8c1cb8aa | 1412 | if (state == vState::Secure) { |
e8e12e8c OM |
1413 | if (resolver.doLog()) { |
1414 | SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates correctly" << endl, | |
91092a9f | 1415 | log->info(Logr::Info, "Validates Correctly")); |
2e921ec6 | 1416 | } |
a1b95336 | 1417 | |
b25cae9a | 1418 | // Is the query source interested in the value of the ad-bit? |
e8e12e8c OM |
1419 | if (comboWriter->d_mdp.d_header.ad || DNSSECOK) { |
1420 | packetWriter.getHeader()->ad = 1; | |
1421 | } | |
b25cae9a | 1422 | } |
8c1cb8aa | 1423 | else if (state == vState::Insecure) { |
e8e12e8c OM |
1424 | if (resolver.doLog()) { |
1425 | SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates as Insecure" << endl, | |
91092a9f | 1426 | log->info(Logr::Info, "Validates as Insecure")); |
12ce523e | 1427 | } |
a1b95336 | 1428 | |
e8e12e8c | 1429 | packetWriter.getHeader()->ad = 0; |
f3fe4ae6 | 1430 | } |
fd870915 | 1431 | else if (vStateIsBogus(state)) { |
e8e12e8c OM |
1432 | if (t_bogusremotes) { |
1433 | t_bogusremotes->push_back(comboWriter->d_source); | |
1434 | } | |
1435 | if (t_bogusqueryring) { | |
1436 | t_bogusqueryring->push_back({comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype}); | |
1437 | } | |
1438 | if (g_dnssecLogBogus || resolver.doLog() || g_dnssecmode == DNSSECMode::ValidateForLog) { | |
1439 | SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates as " << vStateToString(state) << endl, | |
91092a9f | 1440 | log->info(Logr::Notice, "Validates as Bogus")); |
b25cae9a | 1441 | } |
a1b95336 | 1442 | |
b25cae9a | 1443 | // Does the query or validation mode sending out a SERVFAIL on validation errors? |
e8e12e8c OM |
1444 | if (!packetWriter.getHeader()->cd && (g_dnssecmode == DNSSECMode::ValidateAll || comboWriter->d_mdp.d_header.ad || DNSSECOK)) { |
1445 | if (resolver.doLog()) { | |
1446 | SLOG(g_log << Logger::Warning << "Sending out SERVFAIL for " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << " because recursor or query demands it for Bogus results" << endl, | |
91092a9f | 1447 | log->info(Logr::Notice, "Sending out SERVFAIL because recursor or query demands it for Bogus results")); |
b25cae9a | 1448 | } |
a1b95336 | 1449 | |
e8e12e8c OM |
1450 | packetWriter.getHeader()->rcode = RCode::ServFail; |
1451 | goto sendit; // NOLINT(cppcoreguidelines-avoid-goto) | |
8c1cb8aa OM |
1452 | } |
1453 | else { | |
e8e12e8c OM |
1454 | if (resolver.doLog()) { |
1455 | SLOG(g_log << Logger::Warning << "Not sending out SERVFAIL for " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " Bogus validation since neither config nor query demands this" << endl, | |
91092a9f | 1456 | log->info(Logr::Notice, "Sending out SERVFAIL because recursor or query demands it for Bogus results")); |
b25cae9a | 1457 | } |
1458 | } | |
1459 | } | |
1460 | } | |
8c1cb8aa | 1461 | catch (const ImmediateServFailException& e) { |
e8e12e8c OM |
1462 | if (g_logCommonErrors) { |
1463 | SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during validation of '" << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << "' because: " << e.reason << endl, | |
1464 | resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during validation", "exception", Logging::Loggable("ImmediateServFailException"))); | |
1465 | } | |
1466 | goto sendit; // NOLINT(cppcoreguidelines-avoid-goto) | |
f3fe4ae6 | 1467 | } |
15e973d6 OM |
1468 | catch (const pdns::validation::TooManySEC3IterationsException& e) { |
1469 | if (g_logCommonErrors) { | |
1470 | SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during validation of '" << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << "' because: " << e.what() << endl, | |
1471 | resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during validation", "exception", Logging::Loggable("TooManySEC3IterationsException"))); | |
1472 | } | |
1473 | goto sendit; // NOLINT(cppcoreguidelines-avoid-goto) | |
1474 | } | |
b3f0ed10 | 1475 | } |
1476 | ||
e8e12e8c | 1477 | if (!ret.empty()) { |
88da1249 | 1478 | pdns::orderAndShuffle(ret, false); |
e8e12e8c OM |
1479 | if (auto listToSort = luaconfsLocal->sortlist.getOrderCmp(comboWriter->d_source)) { |
1480 | stable_sort(ret.begin(), ret.end(), *listToSort); | |
8c1cb8aa OM |
1481 | variableAnswer = true; |
1482 | } | |
8e079f3a | 1483 | } |
0afa32d4 RG |
1484 | |
1485 | bool needCommit = false; | |
22577619 | 1486 | for (const auto& record : ret) { |
e8e12e8c | 1487 | if (!DNSSECOK && (record.d_type == QType::NSEC3 || ((record.d_type == QType::RRSIG || record.d_type == QType::NSEC) && ((comboWriter->d_mdp.d_qtype != record.d_type && comboWriter->d_mdp.d_qtype != QType::ANY) || (record.d_place != DNSResourceRecord::ANSWER && record.d_place != DNSResourceRecord::ADDITIONAL))))) { |
2e921ec6 | 1488 | continue; |
3e80ebce KM |
1489 | } |
1490 | ||
e8e12e8c | 1491 | if (!addRecordToPacket(packetWriter, record, minTTL, comboWriter->d_ttlCap, maxanswersize, seenAuthSOA)) { |
97c6d7e5 RG |
1492 | needCommit = false; |
1493 | break; | |
1494 | } | |
1495 | needCommit = true; | |
1496 | ||
8c1cb8aa | 1497 | bool udr = false; |
d58249ee | 1498 | #ifdef NOD_ENABLED |
8c1cb8aa | 1499 | if (g_udrEnabled) { |
e8e12e8c | 1500 | udr = udrCheckUniqueDNSRecord(nodlogger, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, record); |
9489e2b5 | 1501 | if (!hasUDR && udr) { |
ca2526f5 | 1502 | hasUDR = true; |
9489e2b5 | 1503 | } |
8c1cb8aa | 1504 | } |
d58249ee | 1505 | #endif /* NOD ENABLED */ |
41c542ec | 1506 | |
babe9430 | 1507 | if (t_protobufServers.servers) { |
4c0c4371 OM |
1508 | // Max size is 64k, but we're conservative here, as other fields are added after the answers have been added |
1509 | // If a single answer causes a too big protobuf message, it wil be dropped by queueData() | |
e8b59a65 | 1510 | // But note addRR has code to prevent that |
4c0c4371 | 1511 | if (pbMessage.size() < std::numeric_limits<uint16_t>::max() / 2) { |
e8e12e8c | 1512 | pbMessage.addRR(record, luaconfsLocal->protobufExportConfig.exportTypes, udr); |
4c0c4371 | 1513 | } |
aa7929a3 | 1514 | } |
ea634573 | 1515 | } |
9489e2b5 | 1516 | if (needCommit) { |
e8e12e8c | 1517 | packetWriter.commit(); |
9489e2b5 CHB |
1518 | } |
1519 | #ifdef NOD_ENABLED | |
1520 | #ifdef HAVE_FSTRM | |
1521 | if (hasUDR) { | |
1522 | if (isEnabledForUDRs(t_nodFrameStreamServersInfo.servers)) { | |
22577619 OM |
1523 | struct timespec timeSpec |
1524 | { | |
1525 | }; | |
9489e2b5 | 1526 | std::string str; |
e8e12e8c OM |
1527 | if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) { |
1528 | TIMEVAL_TO_TIMESPEC(&comboWriter->d_kernelTimestamp, &timeSpec); // NOLINT | |
9489e2b5 CHB |
1529 | } |
1530 | else { | |
e8e12e8c | 1531 | TIMEVAL_TO_TIMESPEC(&comboWriter->d_now, &timeSpec); // NOLINT |
9489e2b5 | 1532 | } |
94219a42 CHB |
1533 | DnstapMessage message(std::move(str), DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &comboWriter->d_source, &comboWriter->d_destination, comboWriter->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &timeSpec, nullptr, comboWriter->d_mdp.d_qname); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
1534 | str = message.getBuffer(); | |
9489e2b5 CHB |
1535 | for (auto& logger : *(t_nodFrameStreamServersInfo.servers)) { |
1536 | if (logger->logUDRs()) { | |
1537 | remoteLoggerQueueData(*logger, str); | |
1538 | } | |
1539 | } | |
1540 | } | |
1541 | } | |
1542 | #endif // HAVE_FSTRM | |
1543 | #endif // NOD_ENABLED | |
288f4aa9 | 1544 | } |
10321a98 | 1545 | sendit:; |
b3f0ed10 | 1546 | |
e8e12e8c OM |
1547 | if (g_useIncomingECS && comboWriter->d_ecsFound && !resolver.wasVariable() && !variableAnswer) { |
1548 | EDNSSubnetOpts ednsOptions; | |
1549 | ednsOptions.source = comboWriter->d_ednssubnet.source; | |
1550 | ComboAddress sourceAddr; | |
1551 | sourceAddr.reset(); | |
1552 | sourceAddr.sin4.sin_family = ednsOptions.source.getNetwork().sin4.sin_family; | |
1553 | ednsOptions.scope = Netmask(sourceAddr, 0); | |
1554 | auto ecsPayload = makeEDNSSubnetOptsString(ednsOptions); | |
5a7f99b4 | 1555 | |
f49ae8cf RG |
1556 | // if we don't have enough space available let's just not set that scope of zero, |
1557 | // it will prevent some caching, mostly from dnsdist, but that's fine | |
e8e12e8c | 1558 | if (packetWriter.size() < maxanswersize && (maxanswersize - packetWriter.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size())) { |
94e2a9b0 | 1559 | |
f49ae8cf RG |
1560 | maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size(); |
1561 | ||
e32a8d46 | 1562 | returnedEdnsOptions.emplace_back(EDNSOptionCode::ECS, std::move(ecsPayload)); |
f49ae8cf | 1563 | } |
5a7f99b4 | 1564 | } |
1565 | ||
7917914b | 1566 | if (haveEDNS && addPaddingToResponse) { |
e8e12e8c | 1567 | size_t currentSize = packetWriter.getSizeWithOpts(returnedEdnsOptions); |
8ba588d0 RG |
1568 | /* we don't use maxawnswersize because it accounts for some EDNS options, but |
1569 | not all of them (for example ECS) */ | |
1570 | size_t maxSize = min(static_cast<uint16_t>(edo.d_packetsize >= 512 ? edo.d_packetsize : 512), g_udpTruncationThreshold); | |
1571 | ||
1572 | if (currentSize < (maxSize - 4)) { | |
1573 | size_t remaining = maxSize - (currentSize + 4); | |
53604c2a RG |
1574 | /* from rfc8647, "4.1. Recommended Strategy: Block-Length Padding": |
1575 | If a server receives a query that includes the EDNS(0) "Padding" | |
1576 | option, it MUST pad the corresponding response (see Section 4 of | |
1577 | RFC 7830) and SHOULD pad the corresponding response to a | |
1578 | multiple of 468 octets (see below). | |
1579 | */ | |
926c72f5 | 1580 | const size_t blockSize = 468; |
53604c2a RG |
1581 | size_t modulo = (currentSize + 4) % blockSize; |
1582 | size_t padSize = 0; | |
1583 | if (modulo > 0) { | |
1584 | padSize = std::min(blockSize - modulo, remaining); | |
1585 | } | |
e32a8d46 | 1586 | returnedEdnsOptions.emplace_back(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize)); |
53604c2a RG |
1587 | } |
1588 | } | |
1589 | ||
97c6d7e5 | 1590 | if (haveEDNS) { |
e8e12e8c OM |
1591 | auto state = resolver.getValidationState(); |
1592 | if (comboWriter->d_extendedErrorCode || resolver.d_extendedError || (SyncRes::s_addExtendedResolutionDNSErrors && vStateIsBogus(state))) { | |
1593 | EDNSExtendedError::code code = EDNSExtendedError::code::Other; | |
e95b2a7c | 1594 | std::string extra; |
94e2a9b0 | 1595 | |
e8e12e8c OM |
1596 | if (comboWriter->d_extendedErrorCode) { |
1597 | code = static_cast<EDNSExtendedError::code>(*comboWriter->d_extendedErrorCode); | |
1598 | extra = std::move(comboWriter->d_extendedErrorExtra); | |
e95b2a7c | 1599 | } |
e8e12e8c OM |
1600 | else if (resolver.d_extendedError) { |
1601 | code = static_cast<EDNSExtendedError::code>(resolver.d_extendedError->infoCode); | |
1602 | extra = std::move(resolver.d_extendedError->extraText); | |
3e5667f9 | 1603 | } |
e95b2a7c RG |
1604 | else { |
1605 | switch (state) { | |
1606 | case vState::BogusNoValidDNSKEY: | |
1607 | code = EDNSExtendedError::code::DNSKEYMissing; | |
1608 | break; | |
1609 | case vState::BogusInvalidDenial: | |
1610 | code = EDNSExtendedError::code::NSECMissing; | |
1611 | break; | |
1612 | case vState::BogusUnableToGetDSs: | |
1613 | code = EDNSExtendedError::code::DNSSECBogus; | |
1614 | break; | |
1615 | case vState::BogusUnableToGetDNSKEYs: | |
1616 | code = EDNSExtendedError::code::DNSKEYMissing; | |
1617 | break; | |
1618 | case vState::BogusSelfSignedDS: | |
1619 | code = EDNSExtendedError::code::DNSSECBogus; | |
1620 | break; | |
1621 | case vState::BogusNoRRSIG: | |
1622 | code = EDNSExtendedError::code::RRSIGsMissing; | |
1623 | break; | |
1624 | case vState::BogusNoValidRRSIG: | |
1625 | code = EDNSExtendedError::code::DNSSECBogus; | |
1626 | break; | |
1627 | case vState::BogusMissingNegativeIndication: | |
1628 | code = EDNSExtendedError::code::NSECMissing; | |
1629 | break; | |
1630 | case vState::BogusSignatureNotYetValid: | |
1631 | code = EDNSExtendedError::code::SignatureNotYetValid; | |
1632 | break; | |
1633 | case vState::BogusSignatureExpired: | |
1634 | code = EDNSExtendedError::code::SignatureExpired; | |
1635 | break; | |
1636 | case vState::BogusUnsupportedDNSKEYAlgo: | |
1637 | code = EDNSExtendedError::code::UnsupportedDNSKEYAlgorithm; | |
1638 | break; | |
1639 | case vState::BogusUnsupportedDSDigestType: | |
1640 | code = EDNSExtendedError::code::UnsupportedDSDigestType; | |
1641 | break; | |
1642 | case vState::BogusNoZoneKeyBitSet: | |
1643 | code = EDNSExtendedError::code::NoZoneKeyBitSet; | |
1644 | break; | |
1645 | case vState::BogusRevokedDNSKEY: | |
e95b2a7c RG |
1646 | case vState::BogusInvalidDNSKEYProtocol: |
1647 | code = EDNSExtendedError::code::DNSSECBogus; | |
1648 | break; | |
1649 | default: | |
1650 | throw std::runtime_error("Bogus validation state not handled: " + vStateToString(state)); | |
1651 | } | |
94e2a9b0 RG |
1652 | } |
1653 | ||
1654 | EDNSExtendedError eee; | |
1655 | eee.infoCode = static_cast<uint16_t>(code); | |
e95b2a7c RG |
1656 | eee.extraText = std::move(extra); |
1657 | ||
e8e12e8c | 1658 | if (packetWriter.size() < maxanswersize && (maxanswersize - packetWriter.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + sizeof(eee.infoCode) + eee.extraText.size())) { |
e32a8d46 | 1659 | returnedEdnsOptions.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee)); |
e95b2a7c | 1660 | } |
94e2a9b0 RG |
1661 | } |
1662 | ||
97c6d7e5 RG |
1663 | /* we try to add the EDNS OPT RR even for truncated answers, |
1664 | as rfc6891 states: | |
1665 | "The minimal response MUST be the DNS header, question section, and an | |
1666 | OPT record. This MUST also occur when a truncated response (using | |
1667 | the DNS header's TC bit) is returned." | |
1668 | */ | |
e8e12e8c OM |
1669 | packetWriter.addOpt(512, ednsExtRCode, DNSSECOK ? EDNSOpts::DNSSECOK : 0, returnedEdnsOptions); |
1670 | packetWriter.commit(); | |
97c6d7e5 RG |
1671 | } |
1672 | ||
e8e12e8c OM |
1673 | t_Counters.at(rec::ResponseStats::responseStats).submitResponse(comboWriter->d_mdp.d_qtype, packet.size(), packetWriter.getHeader()->rcode); |
1674 | updateResponseStats(res, comboWriter->d_source, packet.size(), &comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype); | |
ff4d391d NC |
1675 | #ifdef NOD_ENABLED |
1676 | bool nod = false; | |
1677 | if (g_nodEnabled) { | |
e8e12e8c | 1678 | if (nodCheckNewDomain(nodlogger, comboWriter->d_mdp.d_qname)) { |
ff4d391d | 1679 | nod = true; |
9489e2b5 CHB |
1680 | #ifdef HAVE_FSTRM |
1681 | if (isEnabledForNODs(t_nodFrameStreamServersInfo.servers)) { | |
22577619 OM |
1682 | struct timespec timeSpec |
1683 | { | |
1684 | }; | |
9489e2b5 | 1685 | std::string str; |
e8e12e8c OM |
1686 | if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) { |
1687 | TIMEVAL_TO_TIMESPEC(&comboWriter->d_kernelTimestamp, &timeSpec); // NOLINT | |
9489e2b5 CHB |
1688 | } |
1689 | else { | |
e8e12e8c | 1690 | TIMEVAL_TO_TIMESPEC(&comboWriter->d_now, &timeSpec); // NOLINT |
9489e2b5 | 1691 | } |
94219a42 CHB |
1692 | DnstapMessage message(std::move(str), DnstapMessage::MessageType::client_query, SyncRes::s_serverID, &comboWriter->d_source, &comboWriter->d_destination, comboWriter->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, nullptr, 0, &timeSpec, nullptr, comboWriter->d_mdp.d_qname); |
1693 | str = message.getBuffer(); | |
9489e2b5 CHB |
1694 | |
1695 | for (auto& logger : *(t_nodFrameStreamServersInfo.servers)) { | |
1696 | if (logger->logNODs()) { | |
1697 | remoteLoggerQueueData(*logger, str); | |
1698 | } | |
1699 | } | |
1700 | } | |
1701 | #endif // HAVE_FSTRM | |
3bd27e4a | 1702 | } |
ff4d391d NC |
1703 | } |
1704 | #endif /* NOD_ENABLED */ | |
0638d698 | 1705 | |
e8e12e8c | 1706 | if (variableAnswer || resolver.wasVariable()) { |
7d3d2f4f | 1707 | t_Counters.at(rec::Counter::variableResponses)++; |
0638d698 | 1708 | } |
0fedcaf8 | 1709 | |
e8e12e8c | 1710 | if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && comboWriter->d_policyTags.empty())) { |
a44a8d66 | 1711 | // Start constructing embedded DNSResponse object |
e8e12e8c | 1712 | pbMessage.setResponseCode(packetWriter.getHeader()->rcode); |
b502d522 | 1713 | if (!appliedPolicy.getName().empty()) { |
83b261a7 OM |
1714 | pbMessage.setAppliedPolicy(appliedPolicy.getName()); |
1715 | pbMessage.setAppliedPolicyType(appliedPolicy.d_type); | |
1716 | pbMessage.setAppliedPolicyTrigger(appliedPolicy.d_trigger); | |
1717 | pbMessage.setAppliedPolicyHit(appliedPolicy.d_hit); | |
b3c20df8 | 1718 | pbMessage.setAppliedPolicyKind(appliedPolicy.d_kind); |
0a273054 | 1719 | } |
83b261a7 | 1720 | pbMessage.setInBytes(packet.size()); |
e8e12e8c | 1721 | pbMessage.setValidationState(resolver.getValidationState()); |
a89c3250 | 1722 | // See if we want to store the policyTags into the PC |
a4d0f523 | 1723 | addPolicyTagsToPBMessageIfNeeded(*comboWriter, pbMessage); |
a44a8d66 | 1724 | |
47a6825e | 1725 | // Take s snap of the current protobuf buffer state to store in the PC |
ac10822e | 1726 | pbDataForCache = boost::make_optional(RecursorPacketCache::PBData{ |
83b261a7 OM |
1727 | pbMessage.getMessageBuf(), |
1728 | pbMessage.getResponseBuf(), | |
e8e12e8c | 1729 | !appliedPolicy.getName().empty() || !comboWriter->d_policyTags.empty()}); |
47a6825e | 1730 | #ifdef NOD_ENABLED |
d78bcadc | 1731 | // if (g_udrEnabled) ?? |
83b261a7 | 1732 | pbMessage.clearUDR(pbDataForCache->d_response); |
47a6825e | 1733 | #endif |
0fedcaf8 O |
1734 | } |
1735 | ||
e8e12e8c | 1736 | const bool intoPC = g_packetCache && !variableAnswer && !resolver.wasVariable(); |
2d46fe5d | 1737 | if (intoPC) { |
e8e12e8c OM |
1738 | minTTL = capPacketCacheTTL(*packetWriter.getHeader(), minTTL, seenAuthSOA); |
1739 | g_packetCache->insertResponsePacket(comboWriter->d_tag, comboWriter->d_qhash, std::move(comboWriter->d_query), comboWriter->d_mdp.d_qname, | |
1740 | comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass, | |
22577619 | 1741 | string(reinterpret_cast<const char*>(&*packet.begin()), packet.size()), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
0638d698 O |
1742 | g_now.tv_sec, |
1743 | minTTL, | |
e8e12e8c OM |
1744 | dnsQuestion.validationState, |
1745 | std::move(pbDataForCache), comboWriter->d_tcp); | |
0638d698 | 1746 | } |
b5676bc1 OM |
1747 | |
1748 | if (g_regressionTestMode) { | |
1749 | t_Counters.updateSnap(g_regressionTestMode); | |
1750 | } | |
1751 | ||
e8e12e8c | 1752 | if (!comboWriter->d_tcp) { |
22577619 OM |
1753 | struct msghdr msgh |
1754 | { | |
1755 | }; | |
1756 | struct iovec iov | |
1757 | { | |
1758 | }; | |
e8e12e8c | 1759 | cmsgbuf_aligned cbuf{}; |
22577619 | 1760 | fillMSGHdr(&msgh, &iov, &cbuf, 0, reinterpret_cast<char*>(&*packet.begin()), packet.size(), &comboWriter->d_remote); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
e8e12e8c | 1761 | msgh.msg_control = nullptr; |
0638d698 | 1762 | |
e8e12e8c OM |
1763 | if (g_fromtosockets.count(comboWriter->d_socket) > 0) { |
1764 | addCMsgSrcAddr(&msgh, &cbuf, &comboWriter->d_local, 0); | |
0638d698 | 1765 | } |
e8e12e8c OM |
1766 | int sendErr = sendOnNBSocket(comboWriter->d_socket, &msgh); |
1767 | if (sendErr != 0 && g_logCommonErrors) { | |
1768 | SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << comboWriter->getRemote() << " failed with: " | |
1769 | << stringerror(sendErr) << endl, | |
91092a9f | 1770 | g_slogudpin->error(Logr::Warning, sendErr, "Sending UDP reply to client failed")); |
0638d698 | 1771 | } |
0638d698 O |
1772 | } |
1773 | else { | |
e8e12e8c OM |
1774 | bool hadError = sendResponseOverTCP(comboWriter, packet); |
1775 | finishTCPReply(comboWriter, hadError, true); | |
6b425e61 | 1776 | tcpGuard.setHandled(); |
0638d698 O |
1777 | } |
1778 | ||
e8e12e8c | 1779 | resolver.d_eventTrace.add(RecEventTrace::AnswerSent); |
0638d698 | 1780 | |
0fedcaf8 | 1781 | // Now do the per query changing part ot the protobuf message |
e8e12e8c | 1782 | if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && comboWriter->d_policyTags.empty())) { |
ac10822e | 1783 | // Below are the fields that are not stored in the packet cache and will be appended here and on a cache hit |
e8e12e8c OM |
1784 | if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) { |
1785 | pbMessage.setQueryTime(comboWriter->d_kernelTimestamp.tv_sec, comboWriter->d_kernelTimestamp.tv_usec); | |
c29d820c RG |
1786 | } |
1787 | else { | |
e8e12e8c | 1788 | pbMessage.setQueryTime(comboWriter->d_now.tv_sec, comboWriter->d_now.tv_usec); |
c29d820c | 1789 | } |
e8e12e8c OM |
1790 | pbMessage.setMessageIdentity(comboWriter->d_uuid); |
1791 | pbMessage.setSocketProtocol(comboWriter->d_tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP); | |
e81063e5 OM |
1792 | |
1793 | if (!luaconfsLocal->protobufExportConfig.logMappedFrom) { | |
e8e12e8c OM |
1794 | pbMessage.setSocketFamily(comboWriter->d_source.sin4.sin_family); |
1795 | Netmask requestorNM(comboWriter->d_source, comboWriter->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); | |
e81063e5 OM |
1796 | ComboAddress requestor = requestorNM.getMaskedNetwork(); |
1797 | pbMessage.setFrom(requestor); | |
e8e12e8c | 1798 | pbMessage.setFromPort(comboWriter->d_source.getPort()); |
e81063e5 OM |
1799 | } |
1800 | else { | |
e8e12e8c OM |
1801 | pbMessage.setSocketFamily(comboWriter->d_mappedSource.sin4.sin_family); |
1802 | Netmask requestorNM(comboWriter->d_mappedSource, comboWriter->d_mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); | |
e81063e5 OM |
1803 | ComboAddress requestor = requestorNM.getMaskedNetwork(); |
1804 | pbMessage.setFrom(requestor); | |
e8e12e8c | 1805 | pbMessage.setFromPort(comboWriter->d_mappedSource.getPort()); |
e81063e5 OM |
1806 | } |
1807 | ||
e8e12e8c OM |
1808 | pbMessage.setTo(comboWriter->d_destination); |
1809 | pbMessage.setId(comboWriter->d_mdp.d_header.id); | |
83b261a7 OM |
1810 | |
1811 | pbMessage.setTime(); | |
e8e12e8c OM |
1812 | pbMessage.setEDNSSubnet(comboWriter->d_ednssubnet.source, comboWriter->d_ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); |
1813 | pbMessage.setRequestorId(dnsQuestion.requestorId); | |
1814 | pbMessage.setDeviceId(dnsQuestion.deviceId); | |
1815 | pbMessage.setDeviceName(dnsQuestion.deviceName); | |
1816 | pbMessage.setToPort(comboWriter->d_destination.getPort()); | |
a4d0f523 | 1817 | pbMessage.addPolicyTags(comboWriter->d_gettagPolicyTags); |
3211bbaf | 1818 | |
e8e12e8c OM |
1819 | for (const auto& metaValue : dnsQuestion.meta) { |
1820 | pbMessage.setMeta(metaValue.first, metaValue.second.stringVal, metaValue.second.intVal); | |
3211bbaf | 1821 | } |
41c542ec NC |
1822 | #ifdef NOD_ENABLED |
1823 | if (g_nodEnabled) { | |
ca2526f5 | 1824 | if (nod) { |
8c1cb8aa | 1825 | pbMessage.setNewlyObservedDomain(true); |
83b261a7 | 1826 | pbMessage.addPolicyTag(g_nod_pbtag); |
ca2526f5 NC |
1827 | } |
1828 | if (hasUDR) { | |
83b261a7 | 1829 | pbMessage.addPolicyTag(g_udr_pbtag); |
ca2526f5 | 1830 | } |
41c542ec NC |
1831 | } |
1832 | #endif /* NOD_ENABLED */ | |
e8e12e8c OM |
1833 | if (resolver.d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) != 0) { |
1834 | pbMessage.addEvents(resolver.d_eventTrace); | |
4cdc62f5 | 1835 | } |
e8e12e8c | 1836 | if (comboWriter->d_logResponse) { |
83b261a7 | 1837 | protobufLogResponse(pbMessage); |
406b722e | 1838 | } |
aa7929a3 | 1839 | } |
00b3e94a | 1840 | |
e8e12e8c OM |
1841 | if (resolver.d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) != 0) { |
1842 | SLOG(g_log << Logger::Info << resolver.d_eventTrace.toString() << endl, | |
1843 | resolver.d_slog->info(Logr::Info, resolver.d_eventTrace.toString())); // Maybe we want it to be more fancy? | |
9c495589 | 1844 | } |
3bd27e4a | 1845 | |
a71665e5 | 1846 | // Originally this code used a mix of floats, doubles, uint64_t with different units. |
02d23ff9 | 1847 | // Now it always uses an integral number of microseconds, except for averages, which use doubles |
e8e12e8c | 1848 | uint64_t spentUsec = uSec(resolver.getNow() - comboWriter->d_now); |
3bd27e4a | 1849 | if (!g_quiet) { |
91092a9f | 1850 | if (!g_slogStructured) { |
e8e12e8c OM |
1851 | g_log << Logger::Error << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] answer to " << (comboWriter->d_mdp.d_header.rd ? "" : "non-rd ") << "question '" << comboWriter->d_mdp.d_qname << "|" << DNSRecordContent::NumberToType(comboWriter->d_mdp.d_qtype); |
1852 | g_log << "': " << ntohs(packetWriter.getHeader()->ancount) << " answers, " << ntohs(packetWriter.getHeader()->arcount) << " additional, took " << resolver.d_outqueries << " packets, " << resolver.d_totUsec / 1000.0 << " netw ms, " << static_cast<double>(spentUsec) / 1000.0 << " tot ms, " << resolver.d_throttledqueries << " throttled, " << resolver.d_timeouts << " timeouts, " << resolver.d_tcpoutqueries << "/" << resolver.d_dotoutqueries << " tcp/dot connections, rcode=" << res; | |
2c9119cd | 1853 | |
e8e12e8c OM |
1854 | if (!shouldNotValidate && resolver.isDNSSECValidationRequested()) { |
1855 | g_log << ", dnssec=" << resolver.getValidationState(); | |
91092a9f | 1856 | } |
e8e12e8c OM |
1857 | g_log << " answer-is-variable=" << resolver.wasVariable() << ", into-packetcache=" << intoPC; |
1858 | g_log << " maxdepth=" << resolver.d_maxdepth; | |
91092a9f OM |
1859 | g_log << endl; |
1860 | } | |
1861 | else { | |
e8e12e8c | 1862 | resolver.d_slog->info(Logr::Info, "Answer", "rd", Logging::Loggable(comboWriter->d_mdp.d_header.rd), |
22577619 OM |
1863 | "answers", Logging::Loggable(ntohs(packetWriter.getHeader()->ancount)), |
1864 | "additional", Logging::Loggable(ntohs(packetWriter.getHeader()->arcount)), | |
1865 | "outqueries", Logging::Loggable(resolver.d_outqueries), | |
1866 | "netms", Logging::Loggable(resolver.d_totUsec / 1000.0), | |
1867 | "totms", Logging::Loggable(static_cast<double>(spentUsec) / 1000.0), | |
1868 | "throttled", Logging::Loggable(resolver.d_throttledqueries), | |
1869 | "timeouts", Logging::Loggable(resolver.d_timeouts), | |
1870 | "tcpout", Logging::Loggable(resolver.d_tcpoutqueries), | |
1871 | "dotout", Logging::Loggable(resolver.d_dotoutqueries), | |
1872 | "rcode", Logging::Loggable(res), | |
1873 | "validationState", Logging::Loggable(resolver.getValidationState()), | |
1874 | "answer-is-variable", Logging::Loggable(resolver.wasVariable()), | |
1875 | "into-packetcache", Logging::Loggable(intoPC), | |
1876 | "maxdepth", Logging::Loggable(resolver.d_maxdepth)); | |
2c9119cd | 1877 | } |
c75a6a9e | 1878 | } |
b23b8614 | 1879 | |
e8e12e8c | 1880 | if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Query)) { |
b4374c3c | 1881 | if (resolver.d_outqueries != 0 || resolver.d_throttledqueries != 0 || resolver.d_authzonequeries != 0) { |
7b2c4969 | 1882 | g_recCache->incCacheMisses(); |
0fd8cf21 O |
1883 | } |
1884 | else { | |
7b2c4969 | 1885 | g_recCache->incCacheHits(); |
0fd8cf21 | 1886 | } |
f7b8cffa | 1887 | } |
2c9119cd | 1888 | |
7d3d2f4f OM |
1889 | t_Counters.at(rec::Histogram::answers)(spentUsec); |
1890 | t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec); | |
fe213470 | 1891 | |
e8e12e8c | 1892 | auto newLat = static_cast<double>(spentUsec); |
7ab9f171 | 1893 | newLat = min(newLat, g_networkTimeoutMsec * 1000.0); // outliers of several minutes exist.. |
7d3d2f4f | 1894 | t_Counters.at(rec::DoubleWAvgCounter::avgLatencyUsec).addToRollingAvg(newLat, g_latencyStatSize); |
0a6b1027 | 1895 | // no worries, we do this for packet cache hits elsewhere |
19178da9 | 1896 | |
e8e12e8c OM |
1897 | if (spentUsec >= resolver.d_totUsec) { |
1898 | uint64_t ourtime = spentUsec - resolver.d_totUsec; | |
7d3d2f4f | 1899 | t_Counters.at(rec::Histogram::ourtime)(ourtime); |
e8e12e8c | 1900 | newLat = static_cast<double>(ourtime); // usec |
7d3d2f4f | 1901 | t_Counters.at(rec::DoubleWAvgCounter::avgLatencyOursUsec).addToRollingAvg(newLat, g_latencyStatSize); |
042da1a1 | 1902 | } |
3bd27e4a RG |
1903 | |
1904 | #ifdef NOD_ENABLED | |
1905 | if (nod) { | |
e8e12e8c | 1906 | sendNODLookup(nodlogger, comboWriter->d_mdp.d_qname); |
3bd27e4a RG |
1907 | } |
1908 | #endif /* NOD_ENABLED */ | |
1909 | ||
c6d04bdc | 1910 | // cout<<dc->d_mdp.d_qname<<"\t"<<MT->getUsec()<<"\t"<<sr.d_outqueries<<endl; |
288f4aa9 | 1911 | } |
8c1cb8aa | 1912 | catch (const PDNSException& ae) { |
e8e12e8c OM |
1913 | SLOG(g_log << Logger::Error << "startDoResolve problem " << makeLoginfo(comboWriter) << ": " << ae.reason << endl, |
1914 | resolver.d_slog->error(Logr::Error, ae.reason, "startDoResolve problem", "exception", Logging::Loggable("PDNSException"))); | |
288f4aa9 | 1915 | } |
8c1cb8aa | 1916 | catch (const MOADNSException& mde) { |
e8e12e8c OM |
1917 | SLOG(g_log << Logger::Error << "DNS parser error " << makeLoginfo(comboWriter) << ": " << comboWriter->d_mdp.d_qname << ", " << mde.what() << endl, |
1918 | resolver.d_slog->error(Logr::Error, mde.what(), "DNS parser error")); | |
7b1469bb | 1919 | } |
d4f08082 | 1920 | catch (const std::exception& e) { |
e8e12e8c | 1921 | SLOG(g_log << Logger::Error << "STL error " << makeLoginfo(comboWriter) << ": " << e.what(), |
c42e8958 | 1922 | resolver.d_slog->error(Logr::Error, e.what(), "Exception in resolver context", "exception", Logging::Loggable("std::exception"))); |
068c7634 PD |
1923 | |
1924 | // Luawrapper nests the exception from Lua, so we unnest it here | |
1925 | try { | |
8c1cb8aa OM |
1926 | std::rethrow_if_nested(e); |
1927 | } | |
1928 | catch (const std::exception& ne) { | |
91092a9f | 1929 | SLOG(g_log << ". Extra info: " << ne.what(), |
e8e12e8c | 1930 | resolver.d_slog->error(Logr::Error, ne.what(), "Nested exception in resolver context", Logging::Loggable("std::exception"))); |
8c1cb8aa OM |
1931 | } |
1932 | catch (...) { | |
1933 | } | |
91092a9f OM |
1934 | if (!g_slogStructured) { |
1935 | g_log << endl; | |
1936 | } | |
c154c8a4 | 1937 | } |
8c1cb8aa | 1938 | catch (...) { |
e8e12e8c OM |
1939 | SLOG(g_log << Logger::Error << "Any other exception in a resolver context " << makeLoginfo(comboWriter) << endl, |
1940 | resolver.d_slog->info(Logr::Error, "Any other exception in a resolver context")); | |
288f4aa9 | 1941 | } |
3ddb9247 | 1942 | |
87c4554a OM |
1943 | runTaskOnce(g_logCommonErrors); |
1944 | ||
a7f7ebdf | 1945 | static const size_t stackSizeThreshold = 9 * ::arg().asNum("stack-size") / 10; |
e8e12e8c OM |
1946 | if (g_multiTasker->getMaxStackUsage() >= stackSizeThreshold) { |
1947 | SLOG(g_log << Logger::Error << "Reached mthread stack usage of 90%: " << g_multiTasker->getMaxStackUsage() << " " << makeLoginfo(comboWriter) << " after " << resolver.d_outqueries << " out queries, " << resolver.d_tcpoutqueries << " TCP out queries, " << resolver.d_dotoutqueries << " DoT out queries" << endl, | |
1948 | resolver.d_slog->info(Logr::Error, "Reached mthread stack usage of 90%", | |
22577619 OM |
1949 | "stackUsage", Logging::Loggable(g_multiTasker->getMaxStackUsage()), |
1950 | "outqueries", Logging::Loggable(resolver.d_outqueries), | |
1951 | "netms", Logging::Loggable(resolver.d_totUsec / 1000.0), | |
1952 | "throttled", Logging::Loggable(resolver.d_throttledqueries), | |
1953 | "timeouts", Logging::Loggable(resolver.d_timeouts), | |
1954 | "tcpout", Logging::Loggable(resolver.d_tcpoutqueries), | |
1955 | "dotout", Logging::Loggable(resolver.d_dotoutqueries), | |
1956 | "validationState", Logging::Loggable(resolver.getValidationState()))); | |
e8e12e8c OM |
1957 | } |
1958 | t_Counters.at(rec::Counter::maxMThreadStackUsage) = max(g_multiTasker->getMaxStackUsage(), t_Counters.at(rec::Counter::maxMThreadStackUsage)); | |
7d3d2f4f | 1959 | t_Counters.updateSnap(g_regressionTestMode); |
288f4aa9 BH |
1960 | } |
1961 | ||
bc7a7b24 | 1962 | void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, |
fcd32fe8 | 1963 | bool& foundECS, EDNSSubnetOpts* ednssubnet, EDNSOptionViewMap* options) |
02b47f43 | 1964 | { |
5cc8371b | 1965 | const bool lookForECS = ednssubnet != nullptr; |
325a18bf | 1966 | const dnsheader_aligned dnshead(question.data()); |
e8e12e8c | 1967 | const dnsheader* dhPointer = dnshead.get(); |
02b47f43 | 1968 | size_t questionLen = question.length(); |
8c1cb8aa | 1969 | unsigned int consumed = 0; |
e8e12e8c | 1970 | *dnsname = DNSName(question.c_str(), static_cast<int>(questionLen), sizeof(dnsheader), false, qtype, qclass, &consumed); |
02b47f43 | 1971 | |
8c1cb8aa | 1972 | size_t pos = sizeof(dnsheader) + consumed + 4; |
5cc8371b | 1973 | const size_t headerSize = /* root */ 1 + sizeof(dnsrecordheader); |
e8e12e8c | 1974 | const uint16_t arcount = ntohs(dhPointer->arcount); |
5cc8371b | 1975 | |
fcd32fe8 | 1976 | for (uint16_t arpos = 0; arpos < arcount && questionLen > (pos + headerSize) && (lookForECS && !foundECS); arpos++) { |
5cc8371b | 1977 | if (question.at(pos) != 0) { |
fcd32fe8 | 1978 | /* not an OPT, bye. */ |
5cc8371b RG |
1979 | return; |
1980 | } | |
1981 | ||
1982 | pos += 1; | |
22577619 | 1983 | const auto* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
5cc8371b RG |
1984 | pos += sizeof(dnsrecordheader); |
1985 | ||
1986 | if (pos >= questionLen) { | |
1987 | return; | |
1988 | } | |
1989 | ||
02b47f43 | 1990 | /* OPT root label (1) followed by type (2) */ |
8c1cb8aa | 1991 | if (lookForECS && ntohs(drh->d_type) == QType::OPT) { |
e8e12e8c | 1992 | if (options == nullptr) { |
341d2553 | 1993 | size_t ecsStartPosition = 0; |
00b8cadc | 1994 | size_t ecsLen = 0; |
5cc8371b | 1995 | /* we need to pass the record len */ |
22577619 | 1996 | int res = getEDNSOption(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + sizeof(drh->d_clen), EDNSOptionCode::ECS, &ecsStartPosition, &ecsLen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
00b8cadc RG |
1997 | if (res == 0 && ecsLen > 4) { |
1998 | EDNSSubnetOpts eso; | |
8c1cb8aa OM |
1999 | if (getEDNSSubnetOptsFromString(&question.at(pos - sizeof(drh->d_clen) + ecsStartPosition + 4), ecsLen - 4, &eso)) { |
2000 | *ednssubnet = eso; | |
5cc8371b | 2001 | foundECS = true; |
00b8cadc RG |
2002 | } |
2003 | } | |
2004 | } | |
2005 | else { | |
5cc8371b | 2006 | /* we need to pass the record len */ |
22577619 | 2007 | int res = getEDNSOptions(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + (sizeof(drh->d_clen)), *options); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
00b8cadc | 2008 | if (res == 0) { |
e8e12e8c OM |
2009 | const auto& iter = options->find(EDNSOptionCode::ECS); |
2010 | if (iter != options->end() && !iter->second.values.empty() && iter->second.values.at(0).content != nullptr && iter->second.values.at(0).size > 0) { | |
00b8cadc | 2011 | EDNSSubnetOpts eso; |
e8e12e8c | 2012 | if (getEDNSSubnetOptsFromString(iter->second.values.at(0).content, iter->second.values.at(0).size, &eso)) { |
8c1cb8aa | 2013 | *ednssubnet = eso; |
5cc8371b | 2014 | foundECS = true; |
00b8cadc RG |
2015 | } |
2016 | } | |
02b47f43 RG |
2017 | } |
2018 | } | |
2019 | } | |
5cc8371b RG |
2020 | |
2021 | pos += ntohs(drh->d_clen); | |
02b47f43 RG |
2022 | } |
2023 | } | |
2024 | ||
bc7a7b24 | 2025 | bool checkForCacheHit(bool qnameParsed, unsigned int tag, const string& data, |
8c1cb8aa OM |
2026 | DNSName& qname, uint16_t& qtype, uint16_t& qclass, |
2027 | const struct timeval& now, | |
2028 | string& response, uint32_t& qhash, | |
c375521b | 2029 | RecursorPacketCache::OptPBData& pbData, bool tcp, const ComboAddress& source, const ComboAddress& mappedSource) |
d7c99a70 | 2030 | { |
39e42424 | 2031 | if (!g_packetCache) { |
5abc5e10 OM |
2032 | return false; |
2033 | } | |
d7c99a70 | 2034 | bool cacheHit = false; |
e8e12e8c OM |
2035 | uint32_t age = 0; |
2036 | vState valState = vState::Indeterminate; | |
8c1cb8aa | 2037 | |
d7c99a70 | 2038 | if (qnameParsed) { |
39e42424 | 2039 | cacheHit = g_packetCache->getResponsePacket(tag, data, qname, qtype, qclass, now.tv_sec, &response, &age, &valState, &qhash, &pbData, tcp); |
8c1cb8aa OM |
2040 | } |
2041 | else { | |
39e42424 | 2042 | cacheHit = g_packetCache->getResponsePacket(tag, data, qname, &qtype, &qclass, now.tv_sec, &response, &age, &valState, &qhash, &pbData, tcp); |
d7c99a70 O |
2043 | } |
2044 | ||
2045 | if (cacheHit) { | |
2046 | if (vStateIsBogus(valState)) { | |
2047 | if (t_bogusremotes) { | |
2048 | t_bogusremotes->push_back(source); | |
2049 | } | |
2050 | if (t_bogusqueryring) { | |
e32a8d46 | 2051 | t_bogusqueryring->push_back({qname, qtype}); |
d7c99a70 O |
2052 | } |
2053 | } | |
2054 | ||
c375521b OM |
2055 | // This is only to get the proxyMapping suffixMatch stats right i the case of a PC hit |
2056 | if (t_proxyMapping && source != mappedSource) { | |
005c00d5 OM |
2057 | if (const auto* found = t_proxyMapping->lookup(source)) { |
2058 | if (found->second.suffixMatchNode) { | |
2059 | if (found->second.suffixMatchNode->check(qname)) { | |
2060 | ++found->second.stats.suffixMatches; | |
c375521b OM |
2061 | } |
2062 | } | |
2063 | } | |
2064 | } | |
2065 | ||
7d3d2f4f OM |
2066 | t_Counters.at(rec::Counter::packetCacheHits)++; |
2067 | t_Counters.at(rec::Counter::syncresqueries)++; // XXX | |
d7c99a70 | 2068 | if (response.length() >= sizeof(struct dnsheader)) { |
100ff9c7 OM |
2069 | dnsheader_aligned dh_aligned(response.data()); |
2070 | ageDNSPacket(response, age, dh_aligned); | |
2071 | const auto* dhp = dh_aligned.get(); | |
2072 | updateResponseStats(dhp->rcode, source, response.length(), nullptr, 0); | |
2073 | t_Counters.at(rec::ResponseStats::responseStats).submitResponse(qtype, response.length(), dhp->rcode); | |
d7c99a70 | 2074 | } |
6ab8d5ea | 2075 | |
7d3d2f4f OM |
2076 | // we assume 0 usec |
2077 | t_Counters.at(rec::DoubleWAvgCounter::avgLatencyUsec).addToRollingAvg(0.0, g_latencyStatSize); | |
2078 | t_Counters.at(rec::DoubleWAvgCounter::avgLatencyOursUsec).addToRollingAvg(0.0, g_latencyStatSize); | |
a71665e5 O |
2079 | #if 0 |
2080 | // XXX changes behaviour compared to old code! | |
7d3d2f4f OM |
2081 | t_Counters.at(rec::Counter::answers)(0); |
2082 | t_Counters.at(rec::Counter::ourtime)(0); | |
a71665e5 | 2083 | #endif |
d7c99a70 | 2084 | } |
3211bbaf | 2085 | |
d7c99a70 O |
2086 | return cacheHit; |
2087 | } | |
2088 | ||
35189b7d KF |
2089 | static void* pleaseWipeCaches(const DNSName& canon, bool subtree, uint16_t qtype) |
2090 | { | |
2091 | auto res = wipeCaches(canon, subtree, qtype); | |
91092a9f OM |
2092 | SLOG(g_log << Logger::Info << "Wiped caches for " << canon << ": " << res.record_count << " records; " << res.negative_record_count << " negative records; " << res.packet_count << " packets" << endl, |
2093 | g_slog->withName("runtime")->info(Logr::Info, "Wiped cache", "qname", Logging::Loggable(canon), "records", Logging::Loggable(res.record_count), "negrecords", Logging::Loggable(res.negative_record_count), "packets", Logging::Loggable(res.packet_count))); | |
35189b7d KF |
2094 | return nullptr; |
2095 | } | |
2096 | ||
bc7a7b24 | 2097 | void requestWipeCaches(const DNSName& canon) |
35189b7d KF |
2098 | { |
2099 | // send a message to the handler thread asking it | |
2100 | // to wipe all of the caches | |
e8e12e8c | 2101 | ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: pointer owner |
8c1cb8aa | 2102 | tmsg->func = [=] { return pleaseWipeCaches(canon, true, 0xffff); }; |
35189b7d | 2103 | tmsg->wantAnswer = false; |
78199711 | 2104 | if (write(RecThreadInfo::info(0).getPipes().writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // NOLINT: correct sizeof |
e8e12e8c | 2105 | delete tmsg; // NOLINT: pointer owner |
35189b7d KF |
2106 | |
2107 | unixDie("write to thread pipe returned wrong size or error"); | |
2108 | } | |
7db35c6e | 2109 | // coverity[leaked_storage] |
35189b7d KF |
2110 | } |
2111 | ||
bc7a7b24 | 2112 | bool expectProxyProtocol(const ComboAddress& from) |
5216ddcc RG |
2113 | { |
2114 | return g_proxyProtocolACL.match(from); | |
2115 | } | |
2116 | ||
e81063e5 OM |
2117 | // fromaddr: the address the query is coming from |
2118 | // destaddr: the address the query was received on | |
2119 | // source: the address we assume the query is coming from, might be set by proxy protocol | |
2120 | // destination: the address we assume the query was sent to, might be set by proxy protocol | |
2121 | // mappedSource: the address we assume the query is coming from. Differs from source if table based mapping has been applied | |
e8e12e8c | 2122 | static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, const ComboAddress& mappedSource, struct timeval tval, int fileDesc, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791 |
1bc3c142 | 2123 | { |
78199711 | 2124 | RecThreadInfo::self().incNumberOfDistributedQueries(); |
3e4e2d9d | 2125 | gettimeofday(&g_now, nullptr); |
e8e12e8c OM |
2126 | if (tval.tv_sec != 0) { |
2127 | struct timeval diff = g_now - tval; | |
2128 | double delta = (static_cast<double>(diff.tv_sec) * 1000 + static_cast<double>(diff.tv_usec) / 1000.0); | |
183eb877 | 2129 | |
8c1cb8aa | 2130 | if (delta > 1000.0) { |
7d3d2f4f | 2131 | t_Counters.at(rec::Counter::tooOldDrops)++; |
c29d820c RG |
2132 | return nullptr; |
2133 | } | |
b71b60ee | 2134 | } |
2135 | ||
7d3d2f4f OM |
2136 | ++t_Counters.at(rec::Counter::qcounter); |
2137 | ||
e8e12e8c | 2138 | if (fromaddr.sin4.sin_family == AF_INET6) { |
7d3d2f4f | 2139 | t_Counters.at(rec::Counter::ipv6qcounter)++; |
e8e12e8c | 2140 | } |
1bc3c142 BH |
2141 | |
2142 | string response; | |
325a18bf | 2143 | const dnsheader_aligned headerdata(question.data()); |
e8e12e8c | 2144 | const dnsheader* dnsheader = headerdata.get(); |
8c1cb8aa | 2145 | unsigned int ctag = 0; |
f57486f1 | 2146 | uint32_t qhash = 0; |
12aff2e5 | 2147 | bool needECS = false; |
b502d522 | 2148 | std::unordered_set<std::string> policyTags; |
634dd69e | 2149 | std::map<std::string, RecursorLua4::MetaValue> meta; |
5fd2577f | 2150 | LuaContext::LuaObject data; |
67e31ebe | 2151 | string requestorId; |
590388d2 | 2152 | string deviceId; |
0a6a45c8 | 2153 | string deviceName; |
163ed916 | 2154 | string routingTag; |
16bbc6e3 | 2155 | bool logQuery = false; |
406b722e | 2156 | bool logResponse = false; |
e8e12e8c | 2157 | boost::uuids::uuid uniqueId{}; |
02b47f43 | 2158 | auto luaconfsLocal = g_luaconfs.getLocal(); |
babe9430 OM |
2159 | const auto pbExport = checkProtobufExport(luaconfsLocal); |
2160 | const auto outgoingbExport = checkOutgoingProtobufExport(luaconfsLocal); | |
2161 | if (pbExport || outgoingbExport) { | |
2162 | if (pbExport) { | |
2163 | needECS = true; | |
2164 | } | |
d61aa945 | 2165 | uniqueId = getUniqueID(); |
02b47f43 | 2166 | } |
babe9430 OM |
2167 | logQuery = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logQueries; |
2168 | logResponse = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logResponses; | |
b9fa43e0 | 2169 | #ifdef HAVE_FSTRM |
9489e2b5 | 2170 | checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo); |
12aff2e5 | 2171 | #endif |
b40562da RG |
2172 | EDNSSubnetOpts ednssubnet; |
2173 | bool ecsFound = false; | |
2174 | bool ecsParsed = false; | |
37a919d4 | 2175 | std::vector<DNSRecord> records; |
e95b2a7c | 2176 | std::string extendedErrorExtra; |
37a919d4 | 2177 | boost::optional<int> rcode = boost::none; |
e95b2a7c | 2178 | boost::optional<uint16_t> extendedErrorCode{boost::none}; |
70fb28d9 RG |
2179 | uint32_t ttlCap = std::numeric_limits<uint32_t>::max(); |
2180 | bool variable = false; | |
37a919d4 | 2181 | bool followCNAMEs = false; |
7917914b | 2182 | bool responsePaddingDisabled = false; |
35189b7d | 2183 | DNSName qname; |
1bc3c142 | 2184 | try { |
8c1cb8aa OM |
2185 | uint16_t qtype = 0; |
2186 | uint16_t qclass = 0; | |
2187 | bool qnameParsed = false; | |
8f7473d7 | 2188 | #ifdef MALLOC_TRACE |
2189 | /* | |
2190 | static uint64_t last=0; | |
2191 | if(!last) | |
2192 | g_mtracer->clearAllocators(); | |
2193 | cout<<g_mtracer->getAllocs()-last<<" "<<g_mtracer->getNumOut()<<" -- BEGIN TRACE"<<endl; | |
2194 | last=g_mtracer->getAllocs(); | |
2195 | cout<<g_mtracer->topAllocatorsString()<<endl; | |
2196 | g_mtracer->clearAllocators(); | |
2197 | */ | |
2198 | #endif | |
55a1378f | 2199 | |
0c0d5c26 | 2200 | // We do not have a SyncRes specific Lua context at this point yet, so ok to use t_pdl |
e8e12e8c | 2201 | if (needECS || (t_pdl && (t_pdl->d_gettag || t_pdl->d_gettag_ffi)) || dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) { |
b2eacd67 | 2202 | try { |
29e6303a | 2203 | EDNSOptionViewMap ednsOptions; |
5cc8371b RG |
2204 | |
2205 | ecsFound = false; | |
2206 | ||
2207 | getQNameAndSubnet(question, &qname, &qtype, &qclass, | |
fcd32fe8 | 2208 | ecsFound, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr); |
5cc8371b | 2209 | |
c15ff3df RG |
2210 | qnameParsed = true; |
2211 | ecsParsed = true; | |
12aff2e5 | 2212 | |
8c1cb8aa | 2213 | if (t_pdl) { |
12aff2e5 | 2214 | try { |
70fb28d9 | 2215 | if (t_pdl->d_gettag_ffi) { |
634dd69e | 2216 | RecursorLua4::FFIParams params(qname, qtype, destination, source, ednssubnet.source, data, policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, false, logQuery, logResponse, followCNAMEs, extendedErrorCode, extendedErrorExtra, responsePaddingDisabled, meta); |
cca6d6a8 | 2217 | |
25ab0839 | 2218 | eventTrace.add(RecEventTrace::LuaGetTagFFI); |
cca6d6a8 | 2219 | ctag = t_pdl->gettag_ffi(params); |
0f14442c | 2220 | eventTrace.add(RecEventTrace::LuaGetTagFFI, ctag, false); |
70fb28d9 RG |
2221 | } |
2222 | else if (t_pdl->d_gettag) { | |
0f14442c | 2223 | eventTrace.add(RecEventTrace::LuaGetTag); |
163ed916 | 2224 | ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName, routingTag, proxyProtocolValues); |
0f14442c | 2225 | eventTrace.add(RecEventTrace::LuaGetTag, ctag, false); |
70fb28d9 | 2226 | } |
12aff2e5 | 2227 | } |
8c1cb8aa | 2228 | catch (const std::exception& e) { |
cca6d6a8 | 2229 | if (g_logCommonErrors) { |
91092a9f OM |
2230 | SLOG(g_log << Logger::Warning << "Error parsing a query packet qname='" << qname << "' for tag determination, setting tag=0: " << e.what() << endl, |
2231 | g_slogudpin->error(Logr::Warning, e.what(), "Error parsing a query packet for tag determination, setting tag=0", "qname", Logging::Loggable(qname), "remote", Logging::Loggable(fromaddr), "exception", Logging::Loggable("std;:exception"))); | |
cca6d6a8 | 2232 | } |
12aff2e5 | 2233 | } |
8ea8c302 | 2234 | } |
b2eacd67 | 2235 | } |
8c1cb8aa | 2236 | catch (const std::exception& e) { |
cca6d6a8 | 2237 | if (g_logCommonErrors) { |
91092a9f | 2238 | SLOG(g_log << Logger::Warning << "Error parsing a query packet for tag determination, setting tag=0: " << e.what() << endl, |
7d0727b1 | 2239 | g_slogudpin->error(Logr::Warning, e.what(), "Error parsing a query packet for tag determination, setting tag=0", "remote", Logging::Loggable(fromaddr), "exception", Logging::Loggable("std;:exception"))); |
cca6d6a8 | 2240 | } |
12aff2e5 | 2241 | } |
12ce523e | 2242 | } |
3ddb9247 | 2243 | |
ac10822e | 2244 | RecursorPacketCache::OptPBData pbData{boost::none}; |
babe9430 | 2245 | if (t_protobufServers.servers) { |
845cbf4c | 2246 | if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && policyTags.empty())) { |
e8e12e8c | 2247 | protobufLogQuery(luaconfsLocal, uniqueId, source, destination, mappedSource, ednssubnet.source, false, dnsheader->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta); |
b790ef3d | 2248 | } |
d9d3f9c1 | 2249 | } |
02b47f43 | 2250 | |
7917914b | 2251 | if (ctag == 0 && !responsePaddingDisabled && g_paddingFrom.match(fromaddr)) { |
53604c2a RG |
2252 | ctag = g_paddingTag; |
2253 | } | |
2c0af54f | 2254 | |
e8e12e8c | 2255 | if (dnsheader->opcode == static_cast<unsigned>(Opcode::Query)) { |
35189b7d KF |
2256 | /* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable, |
2257 | but it means that the hash would not be computed. If some script decides at a later time to mark back the answer | |
2258 | as cacheable we would cache it with a wrong tag, so better safe than sorry. */ | |
2259 | eventTrace.add(RecEventTrace::PCacheCheck); | |
c375521b | 2260 | bool cacheHit = checkForCacheHit(qnameParsed, ctag, question, qname, qtype, qclass, g_now, response, qhash, pbData, false, source, mappedSource); |
35189b7d KF |
2261 | eventTrace.add(RecEventTrace::PCacheCheck, cacheHit, false); |
2262 | if (cacheHit) { | |
2263 | if (!g_quiet) { | |
91092a9f OM |
2264 | SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " question answered from packet cache tag=" << ctag << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl, |
2265 | g_slogudpin->info(Logr::Notice, "Question answered from packet cache", "tag", Logging::Loggable(ctag), | |
2266 | "qname", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype)), | |
2267 | "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr))); | |
35189b7d | 2268 | } |
22577619 OM |
2269 | struct msghdr msgh |
2270 | { | |
2271 | }; | |
2272 | struct iovec iov | |
2273 | { | |
2274 | }; | |
e8e12e8c | 2275 | cmsgbuf_aligned cbuf{}; |
22577619 | 2276 | fillMSGHdr(&msgh, &iov, &cbuf, 0, reinterpret_cast<char*>(response.data()), response.length(), const_cast<ComboAddress*>(&fromaddr)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) |
e8e12e8c | 2277 | msgh.msg_control = nullptr; |
35189b7d | 2278 | |
e8e12e8c | 2279 | if (g_fromtosockets.count(fileDesc) != 0) { |
35189b7d KF |
2280 | addCMsgSrcAddr(&msgh, &cbuf, &destaddr, 0); |
2281 | } | |
e8e12e8c | 2282 | int sendErr = sendOnNBSocket(fileDesc, &msgh); |
35189b7d | 2283 | eventTrace.add(RecEventTrace::AnswerSent); |
4cdc62f5 | 2284 | |
e8e12e8c | 2285 | if (t_protobufServers.servers && logResponse && (!luaconfsLocal->protobufExportConfig.taggedOnly || !pbData || pbData->d_tagged)) { |
c837140e | 2286 | protobufLogResponse(dnsheader, luaconfsLocal, pbData, tval, false, source, destination, mappedSource, ednssubnet, uniqueId, requestorId, deviceId, deviceName, meta, eventTrace, policyTags); |
35189b7d | 2287 | } |
0638d698 | 2288 | |
e8e12e8c | 2289 | if (eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) != 0) { |
91092a9f OM |
2290 | SLOG(g_log << Logger::Info << eventTrace.toString() << endl, |
2291 | g_slogudpin->info(Logr::Info, eventTrace.toString())); // Do we want more fancy logging here? | |
35189b7d | 2292 | } |
e8e12e8c | 2293 | if (sendErr != 0 && g_logCommonErrors) { |
91092a9f | 2294 | SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << source.toStringWithPort() |
7d0727b1 | 2295 | << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " failed with: " |
e8e12e8c | 2296 | << stringerror(sendErr) << endl, |
91092a9f | 2297 | g_slogudpin->error(Logr::Error, sendErr, "Sending UDP reply to client failed", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr))); |
35189b7d | 2298 | } |
22577619 OM |
2299 | struct timeval now |
2300 | { | |
2301 | }; | |
35189b7d | 2302 | Utility::gettimeofday(&now, nullptr); |
e8e12e8c | 2303 | uint64_t spentUsec = uSec(now - tval); |
7d3d2f4f OM |
2304 | t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec); |
2305 | t_Counters.updateSnap(g_regressionTestMode); | |
e8e12e8c | 2306 | return nullptr; |
a2a81d42 | 2307 | } |
1bc3c142 | 2308 | } |
3ddb9247 | 2309 | } |
7917914b RG |
2310 | catch (const std::exception& e) { |
2311 | if (g_logCommonErrors) { | |
91092a9f OM |
2312 | SLOG(g_log << Logger::Error << "Error processing or aging answer packet: " << e.what() << endl, |
2313 | g_slogudpin->error(Logr::Error, e.what(), "Error processing or aging answer packet", "exception", Logging::Loggable("std::exception"))); | |
7917914b | 2314 | } |
e8e12e8c | 2315 | return nullptr; |
1bc3c142 | 2316 | } |
3ddb9247 | 2317 | |
7917914b | 2318 | if (t_pdl) { |
e8e12e8c | 2319 | bool ipf = t_pdl->ipfilter(source, destination, *dnsheader, eventTrace); |
0f14442c | 2320 | if (ipf) { |
7917914b | 2321 | if (!g_quiet) { |
e8e12e8c | 2322 | SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " based on policy" << endl, |
91092a9f | 2323 | g_slogudpin->info(Logr::Notice, "Dropped question based on policy", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr))); |
7917914b | 2324 | } |
7d3d2f4f | 2325 | t_Counters.at(rec::Counter::policyDrops)++; |
e8e12e8c | 2326 | return nullptr; |
4ea94941 | 2327 | } |
2328 | } | |
2329 | ||
e8e12e8c | 2330 | if (dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) { |
8c1cb8aa OM |
2331 | if (!isAllowNotifyForZone(qname)) { |
2332 | if (!g_quiet) { | |
e8e12e8c | 2333 | SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP NOTIFY from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl, |
7d0727b1 | 2334 | g_slogudpin->info(Logr::Notice, "Dropping UDP NOTIFY, zone not matched by allow-notify-for", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr))); |
35189b7d KF |
2335 | } |
2336 | ||
7d3d2f4f | 2337 | t_Counters.at(rec::Counter::zoneDisallowedNotify)++; |
e8e12e8c | 2338 | return nullptr; |
35189b7d KF |
2339 | } |
2340 | ||
2341 | if (!g_quiet) { | |
91092a9f OM |
2342 | SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl, |
2343 | g_slogudpin->info(Logr::Notice, "Got NOTIFY", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr), "qname", Logging::Loggable(qname))); | |
35189b7d | 2344 | } |
046b69a5 OM |
2345 | if (!notifyRPZTracker(qname)) { |
2346 | // It wasn't an RPZ | |
2347 | requestWipeCaches(qname); | |
2348 | } | |
35189b7d KF |
2349 | |
2350 | // the operation will now be treated as a Query, generating | |
2351 | // a normal response, as the rest of the code does not | |
2352 | // check dh->opcode, but we need to ensure that the response | |
2353 | // to this request does not get put into the packet cache | |
2354 | variable = true; | |
2355 | } | |
2356 | ||
e8e12e8c OM |
2357 | if (g_multiTasker->numProcesses() > g_maxMThreads) { |
2358 | if (!g_quiet) { | |
2359 | SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", over capacity" << endl, | |
7d0727b1 | 2360 | g_slogudpin->info(Logr::Notice, "Dropped question, over capacity", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr))); |
e8e12e8c | 2361 | } |
7d3d2f4f | 2362 | t_Counters.at(rec::Counter::overCapacityDrops)++; |
e8e12e8c OM |
2363 | return nullptr; |
2364 | } | |
2365 | ||
2366 | auto comboWriter = std::make_unique<DNSComboWriter>(question, g_now, std::move(policyTags), t_pdl, std::move(data), std::move(records)); | |
2367 | ||
2368 | comboWriter->setSocket(fileDesc); | |
2369 | comboWriter->d_tag = ctag; | |
2370 | comboWriter->d_qhash = qhash; | |
2371 | comboWriter->setRemote(fromaddr); // the address the query is coming from | |
2372 | comboWriter->setSource(source); // the address we assume the query is coming from, might be set by proxy protocol | |
2373 | comboWriter->setLocal(destaddr); // the address the query was received on | |
2374 | comboWriter->setDestination(destination); // the address we assume the query is sent to, might be set by proxy protocol | |
2375 | comboWriter->setMappedSource(mappedSource); // the address we assume the query is coming from. Differs from source if table-based mapping has been applied | |
2376 | comboWriter->d_tcp = false; | |
2377 | comboWriter->d_ecsFound = ecsFound; | |
2378 | comboWriter->d_ecsParsed = ecsParsed; | |
2379 | comboWriter->d_ednssubnet = ednssubnet; | |
2380 | comboWriter->d_ttlCap = ttlCap; | |
2381 | comboWriter->d_variable = variable; | |
2382 | comboWriter->d_followCNAMERecords = followCNAMEs; | |
2383 | comboWriter->d_rcode = rcode; | |
2384 | comboWriter->d_logResponse = logResponse; | |
babe9430 | 2385 | if (t_protobufServers.servers || t_outgoingProtobufServers.servers) { |
e8e12e8c OM |
2386 | comboWriter->d_uuid = uniqueId; |
2387 | } | |
4c5a50dc OM |
2388 | comboWriter->d_requestorId = std::move(requestorId); |
2389 | comboWriter->d_deviceId = std::move(deviceId); | |
2390 | comboWriter->d_deviceName = std::move(deviceName); | |
e8e12e8c OM |
2391 | comboWriter->d_kernelTimestamp = tval; |
2392 | comboWriter->d_proxyProtocolValues = std::move(proxyProtocolValues); | |
2393 | comboWriter->d_routingTag = std::move(routingTag); | |
2394 | comboWriter->d_extendedErrorCode = extendedErrorCode; | |
2395 | comboWriter->d_extendedErrorExtra = std::move(extendedErrorExtra); | |
2396 | comboWriter->d_responsePaddingDisabled = responsePaddingDisabled; | |
2397 | comboWriter->d_meta = std::move(meta); | |
2398 | ||
2399 | comboWriter->d_eventTrace = std::move(eventTrace); | |
2400 | g_multiTasker->makeThread(startDoResolve, (void*)comboWriter.release()); // deletes dc | |
0f14442c | 2401 | |
e8e12e8c | 2402 | return nullptr; |
3ddb9247 PD |
2403 | } |
2404 | ||
e8e12e8c | 2405 | static void handleNewUDPQuestion(int fileDesc, FDMultiplexer::funcparam_t& /* var */) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791 |
5db529f8 | 2406 | { |
5216ddcc | 2407 | static const size_t maxIncomingQuerySize = g_proxyProtocolACL.empty() ? 512 : (512 + g_proxyProtocolMaximumSize); |
04896b99 | 2408 | static thread_local std::string data; |
e81063e5 OM |
2409 | ComboAddress fromaddr; // the address the query is coming from |
2410 | ComboAddress source; // the address we assume the query is coming from, might be set by proxy protocol | |
2411 | ComboAddress destination; // the address we assume the query was sent to, might be set by proxy protocol | |
22577619 OM |
2412 | struct msghdr msgh |
2413 | { | |
2414 | }; | |
2415 | struct iovec iov | |
2416 | { | |
2417 | }; | |
7bec330a | 2418 | cmsgbuf_aligned cbuf; |
390f1dab | 2419 | bool firstQuery = true; |
5216ddcc | 2420 | std::vector<ProxyProtocolValue> proxyProtocolValues; |
0f14442c | 2421 | RecEventTrace eventTrace; |
b71b60ee | 2422 | |
b35191d7 | 2423 | for (size_t queriesCounter = 0; queriesCounter < g_maxUDPQueriesPerRound; queriesCounter++) { |
5216ddcc | 2424 | bool proxyProto = false; |
b2b8ccea | 2425 | proxyProtocolValues.clear(); |
c0a00acd | 2426 | data.resize(maxIncomingQuerySize); |
8c1cb8aa | 2427 | fromaddr.sin6.sin6_family = AF_INET6; // this makes sure fromaddr is big enough |
e8e12e8c | 2428 | fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), data.data(), data.size(), &fromaddr); |
b71b60ee | 2429 | |
199e75c7 | 2430 | if (ssize_t len = recvmsg(fileDesc, &msgh, 0); len >= 0) { |
0f14442c | 2431 | eventTrace.clear(); |
e8e12e8c | 2432 | eventTrace.setEnabled(SyncRes::s_event_trace_enabled != 0); |
ade7a5e6 | 2433 | eventTrace.add(RecEventTrace::ReqRecv); |
390f1dab | 2434 | |
c0a00acd | 2435 | firstQuery = false; |
6b8829d5 | 2436 | |
e8e12e8c | 2437 | if ((msgh.msg_flags & MSG_TRUNC) != 0) { |
7d3d2f4f | 2438 | t_Counters.at(rec::Counter::truncatedDrops)++; |
6b8829d5 | 2439 | if (!g_quiet) { |
91092a9f OM |
2440 | SLOG(g_log << Logger::Error << "Ignoring truncated query from " << fromaddr.toString() << endl, |
2441 | g_slogudpin->info(Logr::Error, "Ignoring truncated query", "remote", Logging::Loggable(fromaddr))); | |
6b8829d5 RG |
2442 | } |
2443 | return; | |
2444 | } | |
2445 | ||
5216ddcc RG |
2446 | data.resize(static_cast<size_t>(len)); |
2447 | ||
2448 | if (expectProxyProtocol(fromaddr)) { | |
e8e12e8c | 2449 | bool tcp = false; |
8c73c703 | 2450 | ssize_t used = parseProxyHeader(data, proxyProto, source, destination, tcp, proxyProtocolValues); |
5216ddcc | 2451 | if (used <= 0) { |
7d3d2f4f | 2452 | ++t_Counters.at(rec::Counter::proxyProtocolInvalidCount); |
5216ddcc | 2453 | if (!g_quiet) { |
91092a9f OM |
2454 | SLOG(g_log << Logger::Error << "Ignoring invalid proxy protocol (" << std::to_string(len) << ", " << std::to_string(used) << ") query from " << fromaddr.toStringWithPort() << endl, |
2455 | g_slogudpin->info(Logr::Error, "Ignoring invalid proxy protocol query", "length", Logging::Loggable(len), | |
7d0727b1 | 2456 | "used", Logging::Loggable(used), "remote", Logging::Loggable(fromaddr))); |
5216ddcc RG |
2457 | } |
2458 | return; | |
2459 | } | |
e8e12e8c | 2460 | if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) { |
95f851d6 | 2461 | if (g_quiet) { |
91092a9f OM |
2462 | SLOG(g_log << Logger::Error << "Proxy protocol header in UDP packet from " << fromaddr.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping" << endl, |
2463 | g_slogudpin->info(Logr::Error, "Proxy protocol header in UDP packet is larger than proxy-protocol-maximum-size", | |
7d0727b1 | 2464 | "used", Logging::Loggable(used), "remote", Logging::Loggable(fromaddr))); |
95f851d6 | 2465 | } |
7d3d2f4f | 2466 | ++t_Counters.at(rec::Counter::proxyProtocolInvalidCount); |
95f851d6 RG |
2467 | return; |
2468 | } | |
2469 | ||
5216ddcc RG |
2470 | data.erase(0, used); |
2471 | } | |
6b8829d5 RG |
2472 | else if (len > 512) { |
2473 | /* we only allow UDP packets larger than 512 for those with a proxy protocol header */ | |
7d3d2f4f | 2474 | t_Counters.at(rec::Counter::truncatedDrops)++; |
8c1cb8aa | 2475 | if (!g_quiet) { |
91092a9f OM |
2476 | SLOG(g_log << Logger::Error << "Ignoring truncated query from " << fromaddr.toStringWithPort() << endl, |
2477 | g_slogudpin->info(Logr::Error, "Ignoring truncated query", "remote", Logging::Loggable(fromaddr))); | |
6b8829d5 RG |
2478 | } |
2479 | return; | |
5216ddcc | 2480 | } |
390f1dab | 2481 | |
5216ddcc | 2482 | if (data.size() < sizeof(dnsheader)) { |
7d3d2f4f | 2483 | t_Counters.at(rec::Counter::ignoredCount)++; |
c0a00acd | 2484 | if (!g_quiet) { |
91092a9f OM |
2485 | SLOG(g_log << Logger::Error << "Ignoring too-short (" << std::to_string(data.size()) << ") query from " << fromaddr.toString() << endl, |
2486 | g_slogudpin->info(Logr::Error, "Ignoring too-short query", "length", Logging::Loggable(data.size()), | |
7d0727b1 | 2487 | "remote", Logging::Loggable(fromaddr))); |
c0a00acd RG |
2488 | } |
2489 | return; | |
04896b99 | 2490 | } |
04896b99 | 2491 | |
6b8829d5 RG |
2492 | if (!proxyProto) { |
2493 | source = fromaddr; | |
ba892c7f | 2494 | } |
e81063e5 OM |
2495 | ComboAddress mappedSource = source; |
2496 | if (t_proxyMapping) { | |
22577619 | 2497 | if (const auto* iter = t_proxyMapping->lookup(source)) { |
e8e12e8c OM |
2498 | mappedSource = iter->second.address; |
2499 | ++iter->second.stats.netmaskMatches; | |
e81063e5 OM |
2500 | } |
2501 | } | |
8c1cb8aa | 2502 | if (t_remotes) { |
c0a00acd RG |
2503 | t_remotes->push_back(fromaddr); |
2504 | } | |
81859ba5 | 2505 | |
e81063e5 | 2506 | if (t_allowFrom && !t_allowFrom->match(&mappedSource)) { |
8c1cb8aa | 2507 | if (!g_quiet) { |
e8e12e8c | 2508 | SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP query from " << mappedSource.toString() << ", address not matched by allow-from" << endl, |
91092a9f | 2509 | g_slogudpin->info(Logr::Error, "Dropping UDP query, address not matched by allow-from", "source", Logging::Loggable(mappedSource))); |
c0a00acd | 2510 | } |
3ddb9247 | 2511 | |
7d3d2f4f | 2512 | t_Counters.at(rec::Counter::unauthorizedUDP)++; |
c0a00acd | 2513 | return; |
5db529f8 | 2514 | } |
3bdc4508 | 2515 | |
c0a00acd | 2516 | BOOST_STATIC_ASSERT(offsetof(sockaddr_in, sin_port) == offsetof(sockaddr_in6, sin6_port)); |
e8e12e8c | 2517 | if (fromaddr.sin4.sin_port == 0) { // also works for IPv6 |
8c1cb8aa | 2518 | if (!g_quiet) { |
e8e12e8c | 2519 | SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP query from " << fromaddr.toStringWithPort() << ", can't deal with port 0" << endl, |
91092a9f | 2520 | g_slogudpin->info(Logr::Error, "Dropping UDP query can't deal with port 0", "remote", Logging::Loggable(fromaddr))); |
c0a00acd RG |
2521 | } |
2522 | ||
7d3d2f4f | 2523 | t_Counters.at(rec::Counter::clientParseError)++; // not quite the best place to put it, but needs to go somewhere |
c0a00acd | 2524 | return; |
3abcdab2 | 2525 | } |
c0a00acd RG |
2526 | |
2527 | try { | |
325a18bf | 2528 | const dnsheader_aligned headerdata(data.data()); |
e8e12e8c | 2529 | const dnsheader* dnsheader = headerdata.get(); |
c0a00acd | 2530 | |
e8e12e8c | 2531 | if (dnsheader->qr) { |
7d3d2f4f | 2532 | t_Counters.at(rec::Counter::ignoredCount)++; |
8c1cb8aa | 2533 | if (g_logCommonErrors) { |
91092a9f | 2534 | SLOG(g_log << Logger::Error << "Ignoring answer from " << fromaddr.toString() << " on server socket!" << endl, |
7d0727b1 | 2535 | g_slogudpin->info(Logr::Error, "Ignoring answer on server socket", "remote", Logging::Loggable(fromaddr))); |
c0a00acd RG |
2536 | } |
2537 | } | |
e8e12e8c | 2538 | else if (dnsheader->opcode != static_cast<unsigned>(Opcode::Query) && dnsheader->opcode != static_cast<unsigned>(Opcode::Notify)) { |
7d3d2f4f | 2539 | t_Counters.at(rec::Counter::ignoredCount)++; |
8c1cb8aa | 2540 | if (g_logCommonErrors) { |
e8e12e8c OM |
2541 | SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(dnsheader->opcode) << " from " << fromaddr.toString() << " on server socket!" << endl, |
2542 | g_slogudpin->info(Logr::Error, "Ignoring unsupported opcode server socket", "remote", Logging::Loggable(fromaddr), "opcode", Logging::Loggable(Opcode::to_s(dnsheader->opcode)))); | |
c0a00acd | 2543 | } |
a6147cd2 | 2544 | } |
e8e12e8c | 2545 | else if (dnsheader->qdcount == 0U) { |
7d3d2f4f | 2546 | t_Counters.at(rec::Counter::emptyQueriesCount)++; |
8c1cb8aa | 2547 | if (g_logCommonErrors) { |
91092a9f OM |
2548 | SLOG(g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << fromaddr.toString() << " on server socket!" << endl, |
2549 | g_slogudpin->info(Logr::Error, "Ignoring empty (qdcount == 0) query on server socket!", "remote", Logging::Loggable(fromaddr))); | |
c0f9be19 RG |
2550 | } |
2551 | } | |
a6147cd2 | 2552 | else { |
e8e12e8c | 2553 | if (dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) { |
e81063e5 | 2554 | if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(&mappedSource)) { |
8c1cb8aa | 2555 | if (!g_quiet) { |
e8e12e8c | 2556 | SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP NOTIFY from " << mappedSource.toString() << ", address not matched by allow-notify-from" << endl, |
91092a9f | 2557 | g_slogudpin->info(Logr::Error, "Dropping UDP NOTIFY from address not matched by allow-notify-from", |
7d0727b1 | 2558 | "source", Logging::Loggable(mappedSource))); |
35189b7d KF |
2559 | } |
2560 | ||
7d3d2f4f | 2561 | t_Counters.at(rec::Counter::sourceDisallowedNotify)++; |
35189b7d KF |
2562 | return; |
2563 | } | |
2564 | } | |
2565 | ||
e8e12e8c OM |
2566 | struct timeval tval = {0, 0}; |
2567 | HarvestTimestamp(&msgh, &tval); | |
2568 | ComboAddress destaddr; // the address the query was sent to to | |
2569 | destaddr.reset(); // this makes sure we ignore this address if not returned by recvmsg above | |
22577619 | 2570 | const auto* loc = rplookup(g_listenSocketsAddresses, fileDesc); |
e8e12e8c | 2571 | if (HarvestDestinationAddress(&msgh, &destaddr)) { |
c0a00acd | 2572 | // but.. need to get port too |
e8e12e8c OM |
2573 | if (loc != nullptr) { |
2574 | destaddr.sin4.sin_port = loc->sin4.sin_port; | |
c0a00acd | 2575 | } |
a6147cd2 | 2576 | } |
2577 | else { | |
e8e12e8c OM |
2578 | if (loc != nullptr) { |
2579 | destaddr = *loc; | |
c0a00acd RG |
2580 | } |
2581 | else { | |
e8e12e8c OM |
2582 | destaddr.sin4.sin_family = fromaddr.sin4.sin_family; |
2583 | socklen_t slen = destaddr.getSocklen(); | |
2584 | getsockname(fileDesc, reinterpret_cast<sockaddr*>(&destaddr), &slen); // if this fails, we're ok with it // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) | |
c0a00acd RG |
2585 | } |
2586 | } | |
5216ddcc | 2587 | if (!proxyProto) { |
e8e12e8c | 2588 | destination = destaddr; |
5216ddcc | 2589 | } |
c0a00acd | 2590 | |
048607b4 | 2591 | if (RecThreadInfo::weDistributeQueries()) { |
6e983628 | 2592 | std::string localdata = data; |
42d6b18e | 2593 | distributeAsyncFunction(data, [localdata = std::move(localdata), fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace]() mutable { |
e8e12e8c | 2594 | return doProcessUDPQuestion(localdata, fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace); |
0f14442c | 2595 | }); |
c0a00acd RG |
2596 | } |
2597 | else { | |
e8e12e8c | 2598 | doProcessUDPQuestion(data, fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace); |
a6147cd2 | 2599 | } |
2600 | } | |
c0a00acd | 2601 | } |
8c1cb8aa | 2602 | catch (const MOADNSException& mde) { |
7d3d2f4f | 2603 | t_Counters.at(rec::Counter::clientParseError)++; |
8c1cb8aa | 2604 | if (g_logCommonErrors) { |
91092a9f OM |
2605 | SLOG(g_log << Logger::Error << "Unable to parse packet from remote UDP client " << fromaddr.toString() << ": " << mde.what() << endl, |
2606 | g_slogudpin->error(Logr::Error, mde.what(), "Unable to parse packet from remote UDP client", "remote", Logging::Loggable(fromaddr), "exception", Logging::Loggable("MOADNSException"))); | |
c0a00acd RG |
2607 | } |
2608 | } | |
8c1cb8aa | 2609 | catch (const std::runtime_error& e) { |
7d3d2f4f | 2610 | t_Counters.at(rec::Counter::clientParseError)++; |
8c1cb8aa | 2611 | if (g_logCommonErrors) { |
91092a9f | 2612 | SLOG(g_log << Logger::Error << "Unable to parse packet from remote UDP client " << fromaddr.toString() << ": " << e.what() << endl, |
7d0727b1 | 2613 | g_slogudpin->error(Logr::Error, e.what(), "Unable to parse packet from remote UDP client", "remote", Logging::Loggable(fromaddr), "exception", Logging::Loggable("std::runtime_error"))); |
c0a00acd | 2614 | } |
5db529f8 BH |
2615 | } |
2616 | } | |
c0a00acd RG |
2617 | else { |
2618 | // cerr<<t_id<<" had error: "<<stringerror()<<endl; | |
8c1cb8aa | 2619 | if (firstQuery && errno == EAGAIN) { |
7d3d2f4f | 2620 | t_Counters.at(rec::Counter::noPacketError)++; |
c0a00acd | 2621 | } |
390f1dab | 2622 | |
c0a00acd RG |
2623 | break; |
2624 | } | |
ac0e821b | 2625 | } |
7d3d2f4f | 2626 | t_Counters.updateSnap(g_regressionTestMode); |
5db529f8 BH |
2627 | } |
2628 | ||
d61b8a01 | 2629 | void makeUDPServerSockets(deferredAdd_t& deferredAdds, Logr::log_t log) |
288f4aa9 | 2630 | { |
8c1cb8aa | 2631 | int one = 1; |
99fad7f4 FM |
2632 | vector<string> localAddresses; |
2633 | stringtok(localAddresses, ::arg()["local-address"], " ,"); | |
288f4aa9 | 2634 | |
99fad7f4 | 2635 | if (localAddresses.empty()) { |
3f81d239 | 2636 | throw PDNSException("No local address specified"); |
99fad7f4 | 2637 | } |
3ddb9247 | 2638 | |
94fc1dc5 | 2639 | const uint16_t defaultLocalPort = ::arg().asNum("local-port"); |
99fad7f4 | 2640 | for (const auto& localAddress : localAddresses) { |
94fc1dc5 | 2641 | ComboAddress address{localAddress, defaultLocalPort}; |
99fad7f4 FM |
2642 | const int socketFd = socket(address.sin4.sin_family, SOCK_DGRAM, 0); |
2643 | if (socketFd < 0) { | |
8c1cb8aa | 2644 | throw PDNSException("Making a UDP server socket for resolver: " + stringerror()); |
d3b4137e | 2645 | } |
99fad7f4 | 2646 | if (!setSocketTimestamps(socketFd)) { |
d8d1d955 | 2647 | SLOG(g_log << Logger::Warning << "Unable to enable timestamp reporting for socket" << endl, |
6cb5db59 | 2648 | log->info(Logr::Warning, "Unable to enable timestamp reporting for socket")); |
d8d1d955 | 2649 | } |
99fad7f4 FM |
2650 | if (IsAnyAddress(address)) { |
2651 | if (address.sin4.sin_family == AF_INET) { | |
e8e12e8c | 2652 | if (setsockopt(socketFd, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)) == 0) { // linux supports this, so why not - might fail on other systems |
99fad7f4 FM |
2653 | g_fromtosockets.insert(socketFd); |
2654 | } | |
2655 | } | |
757d3179 | 2656 | #ifdef IPV6_RECVPKTINFO |
99fad7f4 | 2657 | if (address.sin4.sin_family == AF_INET6) { |
e8e12e8c | 2658 | if (setsockopt(socketFd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) == 0) { |
99fad7f4 FM |
2659 | g_fromtosockets.insert(socketFd); |
2660 | } | |
2661 | } | |
757d3179 | 2662 | #endif |
99fad7f4 | 2663 | if (address.sin6.sin6_family == AF_INET6 && setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) < 0) { |
a2a81d42 | 2664 | int err = errno; |
c42e8958 OM |
2665 | SLOG(g_log << Logger::Warning << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << stringerror(err) << endl, |
2666 | log->error(Logr::Warning, "Failed to set IPv6 socket to IPv6 only, continuing anyhow")); | |
0dfa94ab | 2667 | } |
b71b60ee | 2668 | } |
99fad7f4 FM |
2669 | if (::arg().mustDo("non-local-bind")) { |
2670 | Utility::setBindAny(AF_INET6, socketFd); | |
2671 | } | |
fec7dd5a | 2672 | |
99fad7f4 | 2673 | setCloseOnExec(socketFd); |
a903b39c | 2674 | |
f402f388 | 2675 | try { |
99fad7f4 | 2676 | setSocketReceiveBuffer(socketFd, 250000); |
f402f388 RG |
2677 | } |
2678 | catch (const std::exception& e) { | |
62b191dc | 2679 | SLOG(g_log << Logger::Error << e.what() << endl, |
e5d6a4e1 | 2680 | log->error(Logr::Error, e.what(), "Exception while setting socket buffer size")); |
f402f388 | 2681 | } |
37d3f960 | 2682 | |
665821e1 RG |
2683 | if (g_reusePort) { |
2684 | #if defined(SO_REUSEPORT_LB) | |
2685 | try { | |
398bb0f1 | 2686 | SSetsockopt(socketFd, SOL_SOCKET, SO_REUSEPORT_LB, 1); |
665821e1 RG |
2687 | } |
2688 | catch (const std::exception& e) { | |
2689 | throw PDNSException(std::string("SO_REUSEPORT_LB: ") + e.what()); | |
2690 | } | |
2691 | #elif defined(SO_REUSEPORT) | |
2692 | try { | |
99fad7f4 | 2693 | SSetsockopt(socketFd, SOL_SOCKET, SO_REUSEPORT, 1); |
665821e1 RG |
2694 | } |
2695 | catch (const std::exception& e) { | |
2696 | throw PDNSException(std::string("SO_REUSEPORT: ") + e.what()); | |
2697 | } | |
2332f42d | 2698 | #endif |
665821e1 | 2699 | } |
90f9fbc0 | 2700 | |
db63b4b6 | 2701 | try { |
99fad7f4 | 2702 | setSocketIgnorePMTU(socketFd, address.sin4.sin_family); |
db63b4b6 | 2703 | } |
8c1cb8aa | 2704 | catch (const std::exception& e) { |
d8d1d955 OM |
2705 | SLOG(g_log << Logger::Warning << "Failed to set IP_MTU_DISCOVER on UDP server socket: " << e.what() << endl, |
2706 | log->error(Logr::Warning, e.what(), "Failed to set IP_MTU_DISCOVER on UDP server socket")); | |
90f9fbc0 RG |
2707 | } |
2708 | ||
99fad7f4 | 2709 | socklen_t socklen = address.getSocklen(); |
22577619 | 2710 | if (::bind(socketFd, reinterpret_cast<struct sockaddr*>(&address), socklen) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
94fc1dc5 | 2711 | throw PDNSException("Resolver binding to server socket on " + address.toStringWithPort() + ": " + stringerror()); |
99fad7f4 | 2712 | } |
3ddb9247 | 2713 | |
99fad7f4 | 2714 | setNonBlocking(socketFd); |
c2136bf0 | 2715 | |
99fad7f4 FM |
2716 | deferredAdds.emplace_back(socketFd, handleNewUDPQuestion); |
2717 | g_listenSocketsAddresses[socketFd] = address; // this is written to only from the startup thread, not from the workers | |
2718 | SLOG(g_log << Logger::Info << "Listening for UDP queries on " << address.toStringWithPort() << endl, | |
2719 | log->info(Logr::Info, "Listening for queries", "proto", Logging::Loggable("UDP"), "address", Logging::Loggable(address))); | |
f28307ad | 2720 | } |
c836dc19 | 2721 | } |
caa6eefa | 2722 | |
592d7ade | 2723 | static bool trySendingQueryToWorker(unsigned int target, ThreadMSG* tmsg) |
00c9b8c1 | 2724 | { |
4f6e00dc | 2725 | auto& targetInfo = RecThreadInfo::info(target); |
69b39198 | 2726 | if (!targetInfo.isWorker()) { |
91092a9f OM |
2727 | SLOG(g_log << Logger::Error << "distributeAsyncFunction() tried to assign a query to a non-worker thread" << endl, |
2728 | g_slog->withName("runtime")->info(Logr::Error, "distributeAsyncFunction() tried to assign a query to a non-worker thread")); | |
ce715f38 | 2729 | _exit(1); |
00c9b8c1 | 2730 | } |
d77abca1 | 2731 | |
78199711 | 2732 | const auto& tps = targetInfo.getPipes(); |
3ddb9247 | 2733 | |
e8e12e8c | 2734 | ssize_t written = write(tps.writeQueriesToThread, &tmsg, sizeof(tmsg)); // NOLINT: correct sizeof |
cf8cda18 | 2735 | if (written > 0) { |
e8e12e8c OM |
2736 | if (static_cast<size_t>(written) != sizeof(tmsg)) { // NOLINT: correct sizeof |
2737 | delete tmsg; // NOLINT: pointer ownership | |
cf8cda18 RG |
2738 | unixDie("write to thread pipe returned wrong size or error"); |
2739 | } | |
2740 | } | |
2741 | else { | |
2742 | int error = errno; | |
cf8cda18 | 2743 | if (error == EAGAIN || error == EWOULDBLOCK) { |
592d7ade | 2744 | return false; |
8c1cb8aa | 2745 | } |
e8e12e8c OM |
2746 | delete tmsg; // NOLINT: pointer ownership |
2747 | unixDie("write to thread pipe returned wrong size or error:" + std::to_string(error)); | |
b841314c | 2748 | } |
592d7ade RG |
2749 | |
2750 | return true; | |
2751 | } | |
2752 | ||
144040be RG |
2753 | static unsigned int getWorkerLoad(size_t workerIdx) |
2754 | { | |
78199711 | 2755 | const auto* multiThreader = RecThreadInfo::info(RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + workerIdx).getMT(); |
e8e12e8c OM |
2756 | if (multiThreader != nullptr) { |
2757 | return multiThreader->numProcesses(); | |
144040be RG |
2758 | } |
2759 | return 0; | |
2760 | } | |
2761 | ||
2762 | static unsigned int selectWorker(unsigned int hash) | |
2763 | { | |
a808ca69 | 2764 | assert(RecThreadInfo::numUDPWorkers() != 0); // NOLINT: assert implementation |
b35191d7 | 2765 | if (g_balancingFactor == 0) { |
a808ca69 | 2766 | return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + (hash % RecThreadInfo::numUDPWorkers()); |
144040be RG |
2767 | } |
2768 | ||
2769 | /* we start with one, representing the query we are currently handling */ | |
2770 | double currentLoad = 1; | |
a808ca69 OM |
2771 | std::vector<unsigned int> load(RecThreadInfo::numUDPWorkers()); |
2772 | for (size_t idx = 0; idx < RecThreadInfo::numUDPWorkers(); idx++) { | |
144040be RG |
2773 | load[idx] = getWorkerLoad(idx); |
2774 | currentLoad += load[idx]; | |
144040be RG |
2775 | } |
2776 | ||
a808ca69 | 2777 | double targetLoad = (currentLoad / RecThreadInfo::numUDPWorkers()) * g_balancingFactor; |
144040be | 2778 | |
a808ca69 | 2779 | unsigned int worker = hash % RecThreadInfo::numUDPWorkers(); |
1b9d2d46 | 2780 | /* at least one server has to be at or below the average load */ |
596bf482 | 2781 | if (load[worker] > targetLoad) { |
7d3d2f4f | 2782 | ++t_Counters.at(rec::Counter::rebalancedQueries); |
596bf482 | 2783 | do { |
a808ca69 | 2784 | worker = (worker + 1) % RecThreadInfo::numUDPWorkers(); |
8c1cb8aa | 2785 | } while (load[worker] > targetLoad); |
144040be RG |
2786 | } |
2787 | ||
048607b4 | 2788 | return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + worker; |
144040be RG |
2789 | } |
2790 | ||
592d7ade RG |
2791 | // This function is only called by the distributor threads, when pdns-distributes-queries is set |
2792 | void distributeAsyncFunction(const string& packet, const pipefunc_t& func) | |
2793 | { | |
69b39198 | 2794 | if (!RecThreadInfo::self().isDistributor()) { |
91092a9f | 2795 | SLOG(g_log << Logger::Error << "distributeAsyncFunction() has been called by a worker (" << RecThreadInfo::id() << ")" << endl, |
c42e8958 | 2796 | g_slog->withName("runtime")->info(Logr::Error, "distributeAsyncFunction() has been called by a worker")); // tid will be added |
ce715f38 | 2797 | _exit(1); |
592d7ade RG |
2798 | } |
2799 | ||
e8e12e8c | 2800 | bool hashOK = false; |
22577619 | 2801 | unsigned int hash = hashQuestion(reinterpret_cast<const uint8_t*>(packet.data()), packet.length(), g_disthashseed, hashOK); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
e8e12e8c | 2802 | if (!hashOK) { |
538c11f9 | 2803 | // hashQuestion does detect invalid names, so we might as well punt here instead of in the worker thread |
7d3d2f4f | 2804 | t_Counters.at(rec::Counter::ignoredCount)++; |
538c11f9 O |
2805 | throw MOADNSException("too-short (" + std::to_string(packet.length()) + ") or invalid name"); |
2806 | } | |
144040be | 2807 | unsigned int target = selectWorker(hash); |
592d7ade | 2808 | |
e8e12e8c | 2809 | ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: pointer ownership |
592d7ade RG |
2810 | tmsg->func = func; |
2811 | tmsg->wantAnswer = false; | |
2812 | ||
2813 | if (!trySendingQueryToWorker(target, tmsg)) { | |
2814 | /* if this function failed but did not raise an exception, it means that the pipe | |
2815 | was full, let's try another one */ | |
2816 | unsigned int newTarget = 0; | |
2817 | do { | |
a808ca69 | 2818 | newTarget = RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + dns_random(RecThreadInfo::numUDPWorkers()); |
592d7ade RG |
2819 | } while (newTarget == target); |
2820 | ||
2821 | if (!trySendingQueryToWorker(newTarget, tmsg)) { | |
7d3d2f4f | 2822 | t_Counters.at(rec::Counter::queryPipeFullDrops)++; |
e8e12e8c | 2823 | delete tmsg; // NOLINT: pointer ownership |
592d7ade RG |
2824 | } |
2825 | } | |
7db35c6e | 2826 | // coverity[leaked_storage] |
00c9b8c1 | 2827 | } |
3427fa8a | 2828 | |
34801ab1 | 2829 | // resend event to everybody chained onto it |
a2eef000 | 2830 | static void doResends(MT_t::waiters_t::iterator& iter, const std::shared_ptr<PacketID>& resend, const PacketBuffer& content) |
34801ab1 | 2831 | { |
c647a254 | 2832 | // We close the chain for new entries, since they won't be processed anyway |
a2eef000 | 2833 | iter->key->closed = true; |
c647a254 | 2834 | |
e8e12e8c | 2835 | if (iter->key->chain.empty()) { |
34801ab1 | 2836 | return; |
e8e12e8c OM |
2837 | } |
2838 | for (auto i = iter->key->chain.begin(); i != iter->key->chain.end(); ++i) { | |
2839 | auto packetID = std::make_shared<PacketID>(*resend); | |
2840 | packetID->fd = -1; | |
2841 | packetID->id = *i; | |
2842 | g_multiTasker->sendEvent(packetID, &content); | |
7d3d2f4f | 2843 | t_Counters.at(rec::Counter::chainResends)++; |
34801ab1 BH |
2844 | } |
2845 | } | |
2846 | ||
e8e12e8c | 2847 | static void handleUDPServerResponse(int fileDesc, FDMultiplexer::funcparam_t& var) |
09e6702a | 2848 | { |
e8e12e8c | 2849 | auto pid = boost::any_cast<std::shared_ptr<PacketID>>(var); |
2a17f6c6 | 2850 | PacketBuffer packet; |
fae8fe07 | 2851 | packet.resize(g_outgoingEDNSBufsize); |
996c89cc | 2852 | ComboAddress fromaddr; |
8c1cb8aa | 2853 | socklen_t addrlen = sizeof(fromaddr); |
09e6702a | 2854 | |
22577619 | 2855 | ssize_t len = recvfrom(fileDesc, &packet.at(0), packet.size(), 0, reinterpret_cast<sockaddr*>(&fromaddr), &addrlen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
c1da7976 | 2856 | |
5f5cd2fe | 2857 | const ssize_t signed_sizeof_sdnsheader = sizeof(dnsheader); |
34801ab1 | 2858 | |
5f5cd2fe OM |
2859 | if (len < 0) { |
2860 | // len < 0: error on socket | |
e8e12e8c | 2861 | t_udpclientsocks->returnSocket(fileDesc); |
34801ab1 | 2862 | |
5f5cd2fe | 2863 | PacketBuffer empty; |
2e8bc0d8 OM |
2864 | auto iter = g_multiTasker->getWaiters().find(pid); |
2865 | if (iter != g_multiTasker->getWaiters().end()) { | |
34801ab1 | 2866 | doResends(iter, pid, empty); |
5f5cd2fe | 2867 | } |
e8e12e8c | 2868 | g_multiTasker->sendEvent(pid, &empty); // this denotes error (does retry lookup using other NS) |
5f5cd2fe OM |
2869 | return; |
2870 | } | |
3ddb9247 | 2871 | |
5f5cd2fe OM |
2872 | if (len < signed_sizeof_sdnsheader) { |
2873 | // We have received a packet that cannot be a valid DNS packet, as it has no complete header | |
2874 | // Drop it, but continue to wait for other packets | |
2875 | t_Counters.at(rec::Counter::serverParseError)++; | |
2876 | if (g_logCommonErrors) { | |
2877 | SLOG(g_log << Logger::Error << "Unable to parse too short packet from remote UDP server " << fromaddr.toString() << ": packet smaller than DNS header" << endl, | |
2878 | g_slogout->info(Logr::Error, "Unable to parse too short packet from remote UDP server", "from", Logging::Loggable(fromaddr))); | |
2879 | } | |
998a4334 | 2880 | return; |
3ddb9247 | 2881 | } |
998a4334 | 2882 | |
5f5cd2fe | 2883 | // We have at least a full header |
fae8fe07 | 2884 | packet.resize(len); |
e8e12e8c OM |
2885 | dnsheader dnsheader{}; |
2886 | memcpy(&dnsheader, &packet.at(0), sizeof(dnsheader)); | |
3ddb9247 | 2887 | |
a2eef000 O |
2888 | auto pident = std::make_shared<PacketID>(); |
2889 | pident->remote = fromaddr; | |
e8e12e8c OM |
2890 | pident->id = dnsheader.id; |
2891 | pident->fd = fileDesc; | |
34801ab1 | 2892 | |
e8e12e8c | 2893 | if (!dnsheader.qr && g_logCommonErrors) { |
91092a9f OM |
2894 | SLOG(g_log << Logger::Notice << "Not taking data from question on outgoing socket from " << fromaddr.toStringWithPort() << endl, |
2895 | g_slogout->info(Logr::Error, "Not taking data from question on outgoing socket", "from", Logging::Loggable(fromaddr))); | |
6da3b3ad PD |
2896 | } |
2897 | ||
e8e12e8c OM |
2898 | if (dnsheader.qdcount == 0U || // UPC, Nominum, very old BIND on FormErr, NSD |
2899 | dnsheader.qr == 0U) { // one weird server | |
a2eef000 O |
2900 | pident->domain.clear(); |
2901 | pident->type = 0; | |
6da3b3ad PD |
2902 | } |
2903 | else { | |
2904 | try { | |
5f5cd2fe | 2905 | if (len > signed_sizeof_sdnsheader) { |
e8e12e8c | 2906 | pident->domain = DNSName(reinterpret_cast<const char*>(packet.data()), static_cast<int>(len), static_cast<int>(sizeof(dnsheader)), false, &pident->type); // don't copy this from above - we need to do the actual read // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) |
5f5cd2fe OM |
2907 | } |
2908 | else { | |
2909 | // len == sizeof(dnsheader), only header case | |
2910 | // We will do a full scan search later to see if we can match this reply even without a domain | |
2911 | pident->domain.clear(); | |
2912 | pident->type = 0; | |
2913 | } | |
6da3b3ad | 2914 | } |
8c1cb8aa | 2915 | catch (std::exception& e) { |
5f5cd2fe | 2916 | // Parse error, continue waiting for other packets |
7d3d2f4f | 2917 | t_Counters.at(rec::Counter::serverParseError)++; // won't be fed to lwres.cc, so we have to increment |
91092a9f | 2918 | SLOG(g_log << Logger::Warning << "Error in packet from remote nameserver " << fromaddr.toStringWithPort() << ": " << e.what() << endl, |
9b686b05 | 2919 | g_slogudpin->error(Logr::Warning, e.what(), "Error in packet from remote nameserver", "from", Logging::Loggable(fromaddr))); |
6da3b3ad | 2920 | return; |
34801ab1 | 2921 | } |
6da3b3ad | 2922 | } |
34801ab1 | 2923 | |
5f5cd2fe | 2924 | if (!pident->domain.empty()) { |
2e8bc0d8 OM |
2925 | auto iter = g_multiTasker->getWaiters().find(pident); |
2926 | if (iter != g_multiTasker->getWaiters().end()) { | |
5f5cd2fe OM |
2927 | doResends(iter, pident, packet); |
2928 | } | |
6da3b3ad | 2929 | } |
c1da7976 | 2930 | |
6da3b3ad | 2931 | retryWithName: |
4957a608 | 2932 | |
e8e12e8c | 2933 | if (pident->domain.empty() || g_multiTasker->sendEvent(pident, &packet) == 0) { |
9ec48f21 RG |
2934 | /* we did not find a match for this response, something is wrong */ |
2935 | ||
6da3b3ad | 2936 | // we do a full scan for outstanding queries on unexpected answers. not too bad since we only accept them on the right port number, which is hard enough to guess |
2e8bc0d8 | 2937 | for (const auto& d_waiter : g_multiTasker->getWaiters()) { |
e8e12e8c | 2938 | if (pident->fd == d_waiter.key->fd && d_waiter.key->remote == pident->remote && d_waiter.key->type == pident->type && pident->domain == d_waiter.key->domain) { |
027e8f47 RG |
2939 | /* we are expecting an answer from that exact source, on that exact port (since we are using connected sockets), for that qname/qtype, |
2940 | but with a different message ID. That smells like a spoofing attempt. For now we will just increase the counter and will deal with | |
2941 | that later. */ | |
e8e12e8c | 2942 | d_waiter.key->nearMisses++; |
998a4334 | 2943 | } |
6da3b3ad PD |
2944 | |
2945 | // be a bit paranoid here since we're weakening our matching | |
e8e12e8c | 2946 | if (pident->domain.empty() && !d_waiter.key->domain.empty() && pident->type == 0 && d_waiter.key->type != 0 && pident->id == d_waiter.key->id && d_waiter.key->remote == pident->remote) { |
6da3b3ad | 2947 | // cerr<<"Empty response, rest matches though, sending to a waiter"<<endl; |
e8e12e8c OM |
2948 | pident->domain = d_waiter.key->domain; |
2949 | pident->type = d_waiter.key->type; | |
2950 | goto retryWithName; // note that this only passes on an error, lwres will still reject the packet NOLINT(cppcoreguidelines-avoid-goto) | |
d4fb76e9 | 2951 | } |
09e6702a | 2952 | } |
7d3d2f4f | 2953 | t_Counters.at(rec::Counter::unexpectedCount)++; // if we made it here, it really is an unexpected answer |
8c1cb8aa | 2954 | if (g_logCommonErrors) { |
2e8bc0d8 | 2955 | SLOG(g_log << Logger::Warning << "Discarding unexpected packet from " << fromaddr.toStringWithPort() << ": " << (pident->domain.empty() ? "<empty>" : pident->domain.toString()) << ", " << pident->type << ", " << g_multiTasker->getWaiters().size() << " waiters" << endl, |
91092a9f | 2956 | g_slogudpin->info(Logr::Warning, "Discarding unexpected packet", "from", Logging::Loggable(fromaddr), |
7d0727b1 | 2957 | "qname", Logging::Loggable(pident->domain), |
91092a9f | 2958 | "qtype", Logging::Loggable(QType(pident->type)), |
2e8bc0d8 | 2959 | "waiters", Logging::Loggable(g_multiTasker->getWaiters().size()))); |
d8f6d49f | 2960 | } |
09e6702a | 2961 | } |
e8e12e8c | 2962 | else if (fileDesc >= 0) { |
9ec48f21 | 2963 | /* we either found a waiter (1) or encountered an issue (-1), it's up to us to clean the socket anyway */ |
e8e12e8c | 2964 | t_udpclientsocks->returnSocket(fileDesc); |
6da3b3ad | 2965 | } |
09e6702a | 2966 | } |