/src/pdns/pdns/dnsdistdist/dnsdist-udp.cc
Line | Count | Source |
1 | | /* |
2 | | * This file is part of PowerDNS or dnsdist. |
3 | | * Copyright -- PowerDNS.COM B.V. and its contributors |
4 | | * |
5 | | * This program is free software; you can redistribute it and/or modify |
6 | | * it under the terms of version 2 of the GNU General Public License as |
7 | | * published by the Free Software Foundation. |
8 | | * |
9 | | * In addition, for the avoidance of any doubt, permission is granted to |
10 | | * link this program with OpenSSL and to (re)distribute the binaries |
11 | | * produced as the result of such linking. |
12 | | * |
13 | | * This program is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU General Public License |
19 | | * along with this program; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
21 | | */ |
22 | | |
23 | | #include "dnsdist.hh" |
24 | | #include "dnsdist-udp.hh" |
25 | | #include "dnsdist-dnscrypt.hh" |
26 | | #include "dnsdist-metrics.hh" |
27 | | #include "dnsparser.hh" |
28 | | #include "dnsdist-dnsparser.hh" |
29 | | #include "dnsdist-ecs.hh" |
30 | | #include "dnsdist-proxy-protocol.hh" |
31 | | #include "threadname.hh" |
32 | | #include "dolog.hh" |
33 | | #include "dnsdist-configuration.hh" |
34 | | #include "xsk.hh" |
35 | | |
36 | | namespace dnsdist::udp |
37 | | { |
38 | | static std::string contextToStr(Context context) |
39 | 0 | { |
40 | 0 | if (context == Context::Frontend) { |
41 | 0 | return "frontend"; |
42 | 0 | } |
43 | 0 | if (context == Context::Backend) { |
44 | 0 | return "backend"; |
45 | 0 | } |
46 | | |
47 | 0 | return ""; |
48 | 0 | } |
49 | | |
50 | | void setUDPSocketBufferSizes(int socketDesc, const Logr::Logger& logger, Context context, const ComboAddress& addr) |
51 | 0 | { |
52 | 0 | const auto& immutableConfig = dnsdist::configuration::getImmutableConfiguration(); |
53 | 0 | if (immutableConfig.d_socketUDPSendBuffer > 0) { |
54 | 0 | try { |
55 | 0 | setSocketSendBuffer(socketDesc, immutableConfig.d_socketUDPSendBuffer); |
56 | 0 | } |
57 | 0 | catch (const std::exception& e) { |
58 | 0 | if (context == Context::Frontend) { |
59 | 0 | SLOG(warnlog(e.what()), |
60 | 0 | logger.error(Logr::Warning, e.what(), "Failed to raise send buffer size on UDP socket", "frontend.address", Logging::Loggable(addr))); |
61 | 0 | } |
62 | 0 | } |
63 | 0 | } |
64 | 0 | else { |
65 | 0 | try { |
66 | 0 | auto result = raiseSocketSendBufferToMax(socketDesc); |
67 | 0 | if (result > 0 && context == Context::Frontend) { |
68 | 0 | SLOG(infolog("Raised send buffer to %u for %s address '%s'", result, contextToStr(context), addr.toStringWithPort()), |
69 | 0 | logger.info(Logr::Info, "Raised send buffer size", "frontend.address", Logging::Loggable(addr), "network.send_buffer_size", Logging::Loggable(result))); |
70 | 0 | } |
71 | 0 | } |
72 | 0 | catch (const std::exception& e) { |
73 | 0 | if (context == Context::Frontend) { |
74 | 0 | SLOG(warnlog(e.what()), |
75 | 0 | logger.error(Logr::Warning, e.what(), "Failed to raise send buffer size on UDP socket", "frontend.address", Logging::Loggable(addr))); |
76 | 0 | } |
77 | 0 | } |
78 | 0 | } |
79 | |
|
80 | 0 | if (immutableConfig.d_socketUDPRecvBuffer > 0) { |
81 | 0 | try { |
82 | 0 | setSocketReceiveBuffer(socketDesc, immutableConfig.d_socketUDPRecvBuffer); |
83 | 0 | } |
84 | 0 | catch (const std::exception& e) { |
85 | 0 | if (context == Context::Frontend) { |
86 | 0 | SLOG(warnlog(e.what()), |
87 | 0 | logger.error(Logr::Warning, e.what(), "Failed to raise receive buffer size on UDP socket", "frontend.address", Logging::Loggable(addr))); |
88 | 0 | } |
89 | 0 | } |
90 | 0 | } |
91 | 0 | else { |
92 | 0 | try { |
93 | 0 | auto result = raiseSocketReceiveBufferToMax(socketDesc); |
94 | 0 | if (result > 0 && context == Context::Frontend) { |
95 | 0 | SLOG(infolog("Raised receive buffer to %u for address '%s'", result, addr.toStringWithPort()), |
96 | 0 | logger.info(Logr::Info, "Raised receive buffer size", "frontend.address", Logging::Loggable(addr), "buffer_size", Logging::Loggable(result))); |
97 | 0 | } |
98 | 0 | } |
99 | 0 | catch (const std::exception& e) { |
100 | 0 | if (context == Context::Frontend) { |
101 | 0 | SLOG(warnlog(e.what()), |
102 | 0 | logger.error(Logr::Warning, e.what(), "Failed to raise receive buffer size on UDP socket", "frontend.address", Logging::Loggable(addr))); |
103 | 0 | } |
104 | 0 | } |
105 | 0 | } |
106 | 0 | } |
107 | | |
108 | | void sendfromto(int sock, const PacketBuffer& buffer, const ComboAddress& from, const ComboAddress& dest) |
109 | 0 | { |
110 | 0 | const int flags = 0; |
111 | 0 | if (from.sin4.sin_family == 0) { |
112 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
113 | 0 | auto ret = sendto(sock, buffer.data(), buffer.size(), flags, reinterpret_cast<const struct sockaddr*>(&dest), dest.getSocklen()); |
114 | 0 | if (ret == -1) { |
115 | 0 | int error = errno; |
116 | 0 | VERBOSESLOG(infolog("Error sending UDP response to %s: %s", dest.toStringWithPort(), stringerror(error)), |
117 | 0 | dnsdist::logging::getTopLogger("sendfromto")->error(Logr::Info, error, "Error sending UDP response", "client.address", Logging::Loggable(dest))); |
118 | 0 | } |
119 | 0 | return; |
120 | 0 | } |
121 | | |
122 | 0 | auto ret = sendMsgWithOptions(sock, buffer.data(), buffer.size(), &dest, &from, 0, 0); |
123 | 0 | if (!ret.has_value()) { |
124 | 0 | VERBOSESLOG(infolog("Error sending UDP response from %s to %s: %s", from.toStringWithPort(), dest.toStringWithPort(), stringerror(ret.error())), |
125 | 0 | dnsdist::logging::getTopLogger("sendfromto")->error(Logr::Info, ret.error(), "Error sending UDP response", "source.address", Logging::Loggable(from), "client.address", Logging::Loggable(dest))); |
126 | 0 | } |
127 | 0 | } |
128 | | |
129 | | void truncateTC(PacketBuffer& packet, size_t maximumSize, unsigned int qnameWireLength, bool addEDNSToSelfGeneratedResponses) |
130 | 0 | { |
131 | 0 | try { |
132 | 0 | bool hadEDNS = false; |
133 | 0 | uint16_t payloadSize = 0; |
134 | 0 | uint16_t zValue = 0; |
135 | |
|
136 | 0 | if (addEDNSToSelfGeneratedResponses) { |
137 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
138 | 0 | hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &zValue); |
139 | 0 | } |
140 | |
|
141 | 0 | packet.resize(static_cast<uint16_t>(sizeof(dnsheader) + qnameWireLength + DNS_TYPE_SIZE + DNS_CLASS_SIZE)); |
142 | 0 | dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) { |
143 | 0 | header.ancount = 0; |
144 | 0 | header.arcount = 0; |
145 | 0 | header.nscount = 0; |
146 | 0 | return true; |
147 | 0 | }); |
148 | |
|
149 | 0 | if (hadEDNS) { |
150 | 0 | addEDNS(packet, maximumSize, (zValue & EDNS_HEADER_FLAG_DO) != 0, payloadSize, 0); |
151 | 0 | } |
152 | 0 | } |
153 | 0 | catch (...) { |
154 | 0 | ++dnsdist::metrics::g_stats.truncFail; |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | void handleResponseTC4UDPClient(DNSQuestion& dnsQuestion, uint16_t udpPayloadSize, PacketBuffer& response) |
159 | 0 | { |
160 | 0 | if (udpPayloadSize != 0 && response.size() > udpPayloadSize) { |
161 | 0 | VERBOSESLOG(infolog("Got a response of size %d while the initial UDP payload size was %d, truncating", response.size(), udpPayloadSize), |
162 | 0 | dnsQuestion.getLogger()->withName("udp-response")->info(Logr::Info, "Got a UDP response larger than the initial UDP payload size, truncating", "dns.response.size", Logging::Loggable(response.size()), "dns.query.udp_payload_size", Logging::Loggable(udpPayloadSize))); |
163 | |
|
164 | 0 | truncateTC(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnsQuestion.ids.qname.wirelength(), dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses); |
165 | 0 | dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) { |
166 | 0 | header.tc = true; |
167 | 0 | return true; |
168 | 0 | }); |
169 | 0 | } |
170 | 0 | else if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_truncateTC && dnsQuestion.getHeader()->tc) { |
171 | 0 | truncateTC(response, dnsQuestion.getMaximumSize(), dnsQuestion.ids.qname.wirelength(), dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses); |
172 | 0 | } |
173 | 0 | } |
174 | | |
175 | | void handleResponseForUDPClient(InternalQueryState& ids, PacketBuffer& response, const std::shared_ptr<DownstreamState>& backend, bool isAsync, bool selfGenerated) |
176 | 0 | { |
177 | 0 | DNSResponse dnsResponse(ids, response, backend); |
178 | |
|
179 | 0 | handleResponseTC4UDPClient(dnsResponse, ids.udpPayloadSize, response); |
180 | | |
181 | | /* when the answer is encrypted in place, we need to get a copy |
182 | | of the original header before encryption to fill the ring buffer */ |
183 | 0 | dnsheader cleartextDH{}; |
184 | 0 | memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH)); |
185 | |
|
186 | 0 | if (!isAsync) { |
187 | 0 | if (!processResponse(response, dnsResponse)) { |
188 | 0 | return; |
189 | 0 | } |
190 | | |
191 | 0 | if (dnsResponse.isAsynchronous()) { |
192 | 0 | return; |
193 | 0 | } |
194 | 0 | } |
195 | | |
196 | 0 | ++dnsdist::metrics::g_stats.responses; |
197 | 0 | if (ids.cs != nullptr) { |
198 | 0 | ++ids.cs->responses; |
199 | 0 | } |
200 | |
|
201 | 0 | if (!ids.isXSK()) { |
202 | 0 | sendUDPResponse(ids.cs->udpFD, response, dnsResponse.ids.delayMsec, ids.hopLocal, ids.hopRemote); |
203 | 0 | } |
204 | |
|
205 | 0 | if (!selfGenerated) { |
206 | 0 | auto latencyUs = ids.queryRealTime.udiff(); |
207 | 0 | if (!ids.isXSK()) { |
208 | 0 | VERBOSESLOG(infolog("Got answer from %s, relayed to %s (UDP), took %d us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), latencyUs), |
209 | 0 | dnsResponse.getLogger()->withName("udp-response")->info(Logr::Info, "Got answer from backend, relayed to client")); |
210 | 0 | } |
211 | 0 | else { |
212 | 0 | VERBOSESLOG(infolog("Got answer from %s, relayed to %s (UDP via XSK), took %d us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), latencyUs), |
213 | 0 | dnsResponse.getLogger()->withName("udp-xsk-response")->info(Logr::Info, "Got answer from backend, relayed to client")); |
214 | 0 | } |
215 | 0 | handleResponseSent(ids, latencyUs, dnsResponse.ids.origRemote, backend->d_config.remote, response.size(), cleartextDH, backend->getProtocol(), true); |
216 | 0 | } |
217 | 0 | else { |
218 | 0 | handleResponseSent(ids, 0., dnsResponse.ids.origRemote, ComboAddress(), response.size(), cleartextDH, dnsdist::Protocol::DoUDP, false); |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | | #if !defined(DISABLE_RECVMMSG) && defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) |
223 | | void queueResponse(const PacketBuffer& response, const ComboAddress& dest, const ComboAddress& remote, struct mmsghdr& outMsg, struct iovec* iov, cmsgbuf_aligned* cbuf) |
224 | 0 | { |
225 | 0 | outMsg.msg_len = 0; |
226 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): API |
227 | 0 | fillMSGHdr(&outMsg.msg_hdr, iov, nullptr, 0, const_cast<char*>(reinterpret_cast<const char*>(&response.at(0))), response.size(), const_cast<ComboAddress*>(&remote)); |
228 | |
|
229 | 0 | if (dest.sin4.sin_family == 0) { |
230 | 0 | outMsg.msg_hdr.msg_control = nullptr; |
231 | 0 | } |
232 | 0 | else { |
233 | 0 | addCMsgSrcAddr(&outMsg.msg_hdr, cbuf, &dest, 0); |
234 | 0 | } |
235 | 0 | } |
236 | | #endif |
237 | | |
238 | | bool isUDPQueryAcceptable(ClientState& clientState, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, bool& expectProxyProtocol) |
239 | 0 | { |
240 | 0 | if ((msgh->msg_flags & MSG_TRUNC) != 0) { |
241 | | /* message was too large for our buffer */ |
242 | 0 | VERBOSESLOG(infolog("Dropping message too large for our buffer"), |
243 | 0 | dnsdist::logging::getTopLogger("udp-query")->info(Logr::Info, "Dropping query from client that is too large for our buffer", "client.address", Logging::Loggable(remote), "destination.address", Logging::Loggable(dest), "frontend.address", Logging::Loggable(clientState.local))); |
244 | 0 | ++dnsdist::metrics::g_stats.nonCompliantQueries; |
245 | 0 | ++clientState.nonCompliantQueries; |
246 | 0 | return false; |
247 | 0 | } |
248 | | |
249 | 0 | expectProxyProtocol = clientState.d_enableProxyProtocol && expectProxyProtocolFrom(remote); |
250 | 0 | if (!dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL.match(remote) && !expectProxyProtocol) { |
251 | 0 | VERBOSESLOG(infolog("Query from %s dropped because of ACL", remote.toStringWithPort()), |
252 | 0 | dnsdist::logging::getTopLogger("udp-query")->info(Logr::Info, "Query dropped because of ACL", "source.address", Logging::Loggable(dest))); |
253 | 0 | ++dnsdist::metrics::g_stats.aclDrops; |
254 | 0 | return false; |
255 | 0 | } |
256 | | |
257 | 0 | if (HarvestDestinationAddress(msgh, &dest)) { |
258 | | /* so it turns out that sometimes the kernel lies to us: |
259 | | the address is set to 0.0.0.0:0 which makes our sendfromto() use |
260 | | the wrong address. In that case it's better to let the kernel |
261 | | do the work by itself and use sendto() instead. |
262 | | This is indicated by setting the family to 0 which is acted upon |
263 | | in sendUDPResponse() and DelayedPacket::(). |
264 | | */ |
265 | 0 | static const ComboAddress bogusV4("0.0.0.0:0"); |
266 | 0 | static const ComboAddress bogusV6("[::]:0"); |
267 | 0 | if ((dest.sin4.sin_family == AF_INET && dest == bogusV4) || (dest.sin4.sin_family == AF_INET6 && dest == bogusV6)) { |
268 | 0 | dest.sin4.sin_family = 0; |
269 | 0 | } |
270 | 0 | else { |
271 | | /* we don't get the port, only the address */ |
272 | 0 | dest.sin4.sin_port = clientState.local.sin4.sin_port; |
273 | 0 | } |
274 | 0 | } |
275 | 0 | else { |
276 | 0 | dest.sin4.sin_family = 0; |
277 | 0 | } |
278 | |
|
279 | 0 | ++clientState.queries; |
280 | 0 | ++dnsdist::metrics::g_stats.queries; |
281 | |
|
282 | 0 | return true; |
283 | 0 | } |
284 | | |
285 | | void processUDPQuery(ClientState& clientState, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, PacketBuffer& query, std::vector<mmsghdr>* responsesVect, unsigned int* queuedResponses, struct iovec* respIOV, cmsgbuf_aligned* respCBuf) |
286 | 0 | { |
287 | 0 | assert(responsesVect == nullptr || (queuedResponses != nullptr && respIOV != nullptr && respCBuf != nullptr)); |
288 | 0 | uint16_t queryId = 0; |
289 | 0 | InternalQueryState ids; |
290 | |
|
291 | 0 | auto closer = ids.getCloser(__func__, SpanID{}); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) |
292 | |
|
293 | 0 | ids.cs = &clientState; |
294 | 0 | ids.origRemote = remote; |
295 | 0 | ids.hopRemote = remote; |
296 | 0 | ids.protocol = dnsdist::Protocol::DoUDP; |
297 | |
|
298 | 0 | try { |
299 | 0 | bool expectProxyProtocol = false; |
300 | 0 | if (!isUDPQueryAcceptable(clientState, msgh, remote, dest, expectProxyProtocol)) { |
301 | 0 | return; |
302 | 0 | } |
303 | | /* dest might have been updated, if we managed to harvest the destination address */ |
304 | 0 | if (dest.sin4.sin_family != 0) { |
305 | 0 | ids.origDest = dest; |
306 | 0 | ids.hopLocal = dest; |
307 | 0 | } |
308 | 0 | else { |
309 | | /* if we have not been able to harvest the destination address, |
310 | | we do NOT want to update dest or hopLocal, to let the kernel |
311 | | pick the less terrible option, but we want to update origDest |
312 | | which is used by rules and actions to at least the correct |
313 | | address family */ |
314 | 0 | ids.origDest = clientState.local; |
315 | 0 | ids.hopLocal.sin4.sin_family = 0; |
316 | 0 | } |
317 | |
|
318 | 0 | std::vector<ProxyProtocolValue> proxyProtocolValues; |
319 | 0 | if (expectProxyProtocol && !handleProxyProtocol(remote, false, dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL, query, ids.origRemote, ids.origDest, proxyProtocolValues)) { |
320 | 0 | return; |
321 | 0 | } |
322 | | |
323 | 0 | ids.queryRealTime.start(); |
324 | |
|
325 | 0 | auto dnsCryptResponse = dnsdist::dnscrypt::checkDNSCryptQuery(clientState, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false); |
326 | 0 | if (dnsCryptResponse) { |
327 | 0 | sendUDPResponse(clientState.udpFD, query, 0, dest, remote); |
328 | 0 | return; |
329 | 0 | } |
330 | | |
331 | 0 | { |
332 | | /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */ |
333 | 0 | const dnsheader_aligned dnsHeader(query.data()); |
334 | 0 | queryId = ntohs(dnsHeader->id); |
335 | |
|
336 | 0 | if (!checkQueryHeaders(*dnsHeader, clientState)) { |
337 | 0 | return; |
338 | 0 | } |
339 | | |
340 | 0 | if (dnsHeader->qdcount == 0) { |
341 | 0 | dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) { |
342 | 0 | header.rcode = RCode::NotImp; |
343 | 0 | header.qr = true; |
344 | 0 | return true; |
345 | 0 | }); |
346 | |
|
347 | 0 | sendUDPResponse(clientState.udpFD, query, 0, dest, remote); |
348 | 0 | return; |
349 | 0 | } |
350 | 0 | } |
351 | | |
352 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
353 | 0 | ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass); |
354 | 0 | if (ids.dnsCryptQuery) { |
355 | 0 | ids.protocol = dnsdist::Protocol::DNSCryptUDP; |
356 | 0 | } |
357 | 0 | DNSQuestion dnsQuestion(ids, query); |
358 | 0 | const uint16_t* flags = getFlagsFromDNSHeader(dnsQuestion.getHeader().get()); |
359 | 0 | ids.origFlags = *flags; |
360 | |
|
361 | 0 | if (!proxyProtocolValues.empty()) { |
362 | 0 | dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues)); |
363 | 0 | } |
364 | | |
365 | | // save UDP payload size from origin query |
366 | 0 | uint16_t udpPayloadSize = 0; |
367 | 0 | uint16_t zValue = 0; |
368 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
369 | 0 | getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue); |
370 | 0 | if (!ids.dnssecOK) { |
371 | 0 | ids.dnssecOK = (zValue & EDNS_HEADER_FLAG_DO) != 0; |
372 | 0 | } |
373 | 0 | if (udpPayloadSize < 512) { |
374 | 0 | udpPayloadSize = 512; |
375 | 0 | } |
376 | |
|
377 | 0 | std::shared_ptr<DownstreamState> backend{nullptr}; |
378 | 0 | auto result = processQuery(dnsQuestion, backend); |
379 | |
|
380 | 0 | if (result == ProcessQueryResult::Drop || result == ProcessQueryResult::Asynchronous) { |
381 | 0 | return; |
382 | 0 | } |
383 | | |
384 | | // the buffer might have been invalidated by now (resized) |
385 | 0 | const auto dnsHeader = dnsQuestion.getHeader(); |
386 | 0 | if (result == ProcessQueryResult::SendAnswer) { |
387 | | /* ensure payload size is not exceeded */ |
388 | 0 | handleResponseTC4UDPClient(dnsQuestion, udpPayloadSize, query); |
389 | 0 | #ifndef DISABLE_RECVMMSG |
390 | 0 | #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) |
391 | 0 | if (dnsQuestion.ids.delayMsec == 0 && responsesVect != nullptr) { |
392 | 0 | queueResponse(query, dest, remote, (*responsesVect)[*queuedResponses], respIOV, respCBuf); |
393 | 0 | (*queuedResponses)++; |
394 | 0 | handleResponseSent(std::move(dnsQuestion.ids.qname), dnsQuestion.ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false); |
395 | 0 | return; |
396 | 0 | } |
397 | 0 | #endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */ |
398 | 0 | #endif /* DISABLE_RECVMMSG */ |
399 | | /* we use dest, always, because we don't want to use the listening address to send a response since it could be 0.0.0.0 */ |
400 | 0 | sendUDPResponse(clientState.udpFD, query, dnsQuestion.ids.delayMsec, dest, remote); |
401 | 0 | handleResponseSent(std::move(dnsQuestion.ids.qname), dnsQuestion.ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false); |
402 | 0 | return; |
403 | 0 | } |
404 | | |
405 | 0 | if (result != ProcessQueryResult::PassToBackend || backend == nullptr) { |
406 | 0 | return; |
407 | 0 | } |
408 | | |
409 | 0 | if (backend->isTCPOnly()) { |
410 | 0 | std::string proxyProtocolPayload; |
411 | | /* we need to do this _before_ creating the cross protocol query because |
412 | | after that the buffer will have been moved */ |
413 | 0 | if (backend->d_config.useProxyProtocol) { |
414 | 0 | proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion); |
415 | 0 | } |
416 | |
|
417 | 0 | ids.origID = dnsHeader->id; |
418 | 0 | auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), backend); |
419 | 0 | cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload); |
420 | |
|
421 | 0 | backend->passCrossProtocolQuery(std::move(cpq)); |
422 | 0 | return; |
423 | 0 | } |
424 | | |
425 | 0 | assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query); |
426 | 0 | } |
427 | 0 | catch (const std::exception& e) { |
428 | 0 | VERBOSESLOG(infolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", ids.origRemote.toStringWithPort(), queryId, e.what()), |
429 | 0 | dnsdist::logging::getTopLogger("udp-frontend")->error(Logr::Info, e.what(), "Got an error in UDP question thread while parsing a query", "source.address", Logging::Loggable(ids.origRemote), "dns.question.id", Logging::Loggable(queryId))); |
430 | 0 | } |
431 | 0 | } |
432 | | |
433 | | size_t getInitialUDPPacketBufferSize(bool expectProxyProtocol) |
434 | 0 | { |
435 | 0 | static_assert(dnsdist::configuration::s_udpIncomingBufferSize <= dnsdist::udp::s_initialUDPPacketBufferSize, "The incoming buffer size should not be larger than s_initialUDPPacketBufferSize"); |
436 | |
|
437 | 0 | const auto& runtimeConfig = dnsdist::configuration::getCurrentRuntimeConfiguration(); |
438 | 0 | if (!expectProxyProtocol || runtimeConfig.d_proxyProtocolACL.empty()) { |
439 | 0 | return s_initialUDPPacketBufferSize; |
440 | 0 | } |
441 | | |
442 | 0 | return s_initialUDPPacketBufferSize + runtimeConfig.d_proxyProtocolMaximumSize; |
443 | 0 | } |
444 | | |
445 | | size_t getMaximumIncomingPacketSize(const ClientState& clientState) |
446 | 0 | { |
447 | 0 | if (clientState.dnscryptCtx) { |
448 | 0 | return getInitialUDPPacketBufferSize(clientState.d_enableProxyProtocol); |
449 | 0 | } |
450 | | |
451 | 0 | const auto& runtimeConfig = dnsdist::configuration::getCurrentRuntimeConfiguration(); |
452 | 0 | if (!clientState.d_enableProxyProtocol || runtimeConfig.d_proxyProtocolACL.empty()) { |
453 | 0 | return dnsdist::configuration::s_udpIncomingBufferSize; |
454 | 0 | } |
455 | | |
456 | 0 | return dnsdist::configuration::s_udpIncomingBufferSize + runtimeConfig.d_proxyProtocolMaximumSize; |
457 | 0 | } |
458 | | |
459 | | static void processUDPResponseFromBackend(const Logr::Logger& logger, std::shared_ptr<DownstreamState>& backend, PacketBuffer& response, ssize_t got, size_t initialBufferSize, int sockDesc) |
460 | 0 | { |
461 | 0 | uint16_t queryId = 0; |
462 | 0 | try { |
463 | 0 | if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader) || static_cast<size_t>(got) >= (initialBufferSize + 1)) { |
464 | 0 | return; |
465 | 0 | } |
466 | | |
467 | 0 | response.resize(static_cast<size_t>(got)); |
468 | 0 | const dnsheader_aligned dnsHeader(response.data()); |
469 | 0 | queryId = dnsHeader->id; |
470 | |
|
471 | 0 | auto ids = backend->getState(queryId); |
472 | 0 | if (!ids) { |
473 | 0 | return; |
474 | 0 | } |
475 | | |
476 | 0 | if (!ids->isXSK() && sockDesc != ids->backendFD) { |
477 | 0 | backend->restoreState(queryId, std::move(*ids)); |
478 | 0 | return; |
479 | 0 | } |
480 | | |
481 | 0 | dnsdist::configuration::refreshLocalRuntimeConfiguration(); |
482 | 0 | if (processResponderPacket(backend, response, std::move(*ids)) && ids->isXSK() && ids->cs->xskInfoResponder) { |
483 | | #ifdef HAVE_XSK |
484 | | auto& xskInfo = ids->cs->xskInfoResponder; |
485 | | auto xskPacket = xskInfo->getEmptyFrame(); |
486 | | if (!xskPacket) { |
487 | | return; |
488 | | } |
489 | | xskPacket->setHeader(ids->xskPacketHeader); |
490 | | if (!xskPacket->setPayload(response)) { |
491 | | } |
492 | | if (ids->delayMsec > 0) { |
493 | | xskPacket->addDelay(ids->delayMsec); |
494 | | } |
495 | | xskPacket->updatePacket(); |
496 | | xskInfo->pushToSendQueue(*xskPacket); |
497 | | xskInfo->notifyXskSocket(); |
498 | | #endif /* HAVE_XSK */ |
499 | 0 | } |
500 | 0 | } |
501 | 0 | catch (const std::exception& e) { |
502 | 0 | VERBOSESLOG(infolog("Got an error in UDP responder thread while parsing a response from %s, id %d: %s", backend->d_config.remote.toStringWithPort(), queryId, e.what()), |
503 | 0 | logger.error(Logr::Info, e.what(), "Got an error in UDP responder thread while parsing a response", "dns.response.id", Logging::Loggable(queryId))); |
504 | 0 | } |
505 | 0 | } |
506 | | |
507 | | #ifndef DISABLE_RECVMMSG |
508 | | #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) |
509 | | static void MultipleMessagesUDPClientThread(ClientState* clientState) |
510 | 0 | { |
511 | 0 | struct MMReceiver |
512 | 0 | { |
513 | 0 | PacketBuffer packet; |
514 | 0 | ComboAddress remote; |
515 | 0 | ComboAddress dest; |
516 | 0 | iovec iov{}; |
517 | | /* used by HarvestDestinationAddress */ |
518 | 0 | cmsgbuf_aligned cbuf{}; |
519 | 0 | }; |
520 | 0 | const size_t vectSize = dnsdist::configuration::getImmutableConfiguration().d_udpVectorSize; |
521 | |
|
522 | 0 | if (vectSize > std::numeric_limits<uint16_t>::max()) { |
523 | 0 | throw std::runtime_error("The value of setUDPMultipleMessagesVectorSize is too high, the maximum value is " + std::to_string(std::numeric_limits<uint16_t>::max())); |
524 | 0 | } |
525 | | |
526 | 0 | auto recvData = std::vector<MMReceiver>(vectSize); |
527 | 0 | auto msgVec = std::vector<mmsghdr>(vectSize); |
528 | 0 | auto outMsgVec = std::vector<mmsghdr>(vectSize); |
529 | | |
530 | | /* the actual buffer is larger because: |
531 | | - we may have to add EDNS and/or ECS |
532 | | - we use it for self-generated responses (from rule or cache) |
533 | | but we only accept incoming payloads up to that size |
534 | | */ |
535 | 0 | const size_t initialBufferSize = getInitialUDPPacketBufferSize(clientState->d_enableProxyProtocol); |
536 | 0 | const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*clientState); |
537 | | |
538 | | /* initialize the structures needed to receive our messages */ |
539 | 0 | for (size_t idx = 0; idx < vectSize; idx++) { |
540 | 0 | auto& slot = recvData[idx]; |
541 | 0 | slot.remote.sin4.sin_family = clientState->local.sin4.sin_family; |
542 | 0 | slot.packet.resize(initialBufferSize); |
543 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
544 | 0 | fillMSGHdr(&msgVec[idx].msg_hdr, &slot.iov, &slot.cbuf, sizeof(decltype(slot.cbuf)), reinterpret_cast<char*>(slot.packet.data()), maxIncomingPacketSize, &slot.remote); |
545 | 0 | } |
546 | |
|
547 | 0 | int msgsGot = static_cast<int>(vectSize); |
548 | | /* go now */ |
549 | 0 | for (;;) { |
550 | | |
551 | | /* reset the IO vector, since it's also used to send the vector of responses |
552 | | to avoid having to copy the data around. |
553 | | No need to reset the parts that have not been used, though. */ |
554 | 0 | for (int idx = 0; idx < msgsGot; idx++) { |
555 | 0 | auto& slot = recvData[idx]; |
556 | | /* only resize if the buffer is actually smaller than expected */ |
557 | 0 | if (slot.packet.size() < initialBufferSize) { |
558 | 0 | slot.packet.resize(initialBufferSize); |
559 | 0 | } |
560 | | /* but we need to set the IOv pointer and size anyway, |
561 | | because if the buffer has been resized at any point |
562 | | during the processing (to add EDNS options, for example) |
563 | | the pointer might now be invalid */ |
564 | 0 | slot.iov.iov_base = &slot.packet.at(0); |
565 | 0 | slot.iov.iov_len = slot.packet.size(); |
566 | 0 | } |
567 | | |
568 | | /* block until we have at least one message ready, but return |
569 | | as many as possible to save the syscall costs */ |
570 | 0 | msgsGot = recvmmsg(clientState->udpFD, msgVec.data(), vectSize, MSG_WAITFORONE, nullptr); |
571 | 0 | if (msgsGot <= 0) { |
572 | 0 | int savederrno = errno; |
573 | 0 | VERBOSESLOG(infolog("Getting UDP messages via recvmmsg() failed with: %s", stringerror(savederrno)), |
574 | 0 | dnsdist::logging::getTopLogger("udp-recvmmsg-frontend")->error(Logr::Info, savederrno, "Getting UDP messages via recvmmsg failed", "frontend.address", Logging::Loggable(clientState->local))); |
575 | 0 | msgsGot = 0; |
576 | 0 | continue; |
577 | 0 | } |
578 | | |
579 | 0 | unsigned int msgsToSend = 0; |
580 | | |
581 | | /* process the received messages */ |
582 | 0 | for (int msgIdx = 0; msgIdx < msgsGot; msgIdx++) { |
583 | 0 | auto& msg = msgVec[msgIdx]; |
584 | 0 | const struct msghdr* msgh = &msg.msg_hdr; |
585 | 0 | unsigned int got = msg.msg_len; |
586 | 0 | const ComboAddress& remote = recvData[msgIdx].remote; |
587 | |
|
588 | 0 | if (static_cast<size_t>(got) < sizeof(struct dnsheader)) { |
589 | 0 | ++dnsdist::metrics::g_stats.nonCompliantQueries; |
590 | 0 | ++clientState->nonCompliantQueries; |
591 | 0 | continue; |
592 | 0 | } |
593 | | |
594 | 0 | if ((msgh->msg_flags & MSG_TRUNC) != 0) { |
595 | | /* message was too large for our buffer */ |
596 | 0 | ++dnsdist::metrics::g_stats.nonCompliantQueries; |
597 | 0 | ++clientState->nonCompliantQueries; |
598 | 0 | continue; |
599 | 0 | } |
600 | | |
601 | 0 | auto& data = recvData[msgIdx]; |
602 | 0 | data.packet.resize(got); |
603 | 0 | dnsdist::configuration::refreshLocalRuntimeConfiguration(); |
604 | 0 | processUDPQuery(*clientState, msgh, remote, data.dest, data.packet, &outMsgVec, &msgsToSend, &data.iov, &data.cbuf); |
605 | 0 | } |
606 | | |
607 | | /* immediate (not delayed or sent to a backend) responses (mostly from a rule, dynamic block |
608 | | or the cache) can be sent in batch too */ |
609 | |
|
610 | 0 | if (msgsToSend > 0 && msgsToSend <= static_cast<unsigned int>(msgsGot)) { |
611 | 0 | int sent = sendmmsg(clientState->udpFD, outMsgVec.data(), msgsToSend, 0); |
612 | |
|
613 | 0 | if (sent < 0 || static_cast<unsigned int>(sent) != msgsToSend) { |
614 | 0 | int savederrno = errno; |
615 | 0 | VERBOSESLOG(infolog("Error sending responses with sendmmsg() (%d on %u): %s", sent, msgsToSend, stringerror(savederrno)), |
616 | 0 | dnsdist::logging::getTopLogger("udp-sendmmsg-frontend")->error(Logr::Info, savederrno, "Error sending responses with sendmmsg()", "address", Logging::Loggable(clientState->local), "dnsdist.sendmmsg.messages_sent", Logging::Loggable(sent), "dnsdist.sendmmsg.messages_to_send", Logging::Loggable(msgsToSend))); |
617 | 0 | } |
618 | 0 | } |
619 | 0 | } |
620 | 0 | } |
621 | | |
622 | | static void MultipleMessagesUDPResponseFromBackendThread(std::shared_ptr<DownstreamState> dss) |
623 | 0 | { |
624 | 0 | auto responderLogger = dnsdist::logging::getTopLogger("udp-response")->withValues("backend.address", Logging::Loggable(dss->d_config.remote)); |
625 | |
|
626 | 0 | try { |
627 | 0 | setThreadName("dnsdist/respond"); |
628 | 0 | const size_t initialBufferSize = getInitialUDPPacketBufferSize(false); |
629 | 0 | std::vector<int> sockets; |
630 | 0 | sockets.reserve(dss->sockets.size()); |
631 | |
|
632 | 0 | struct MMReceiver |
633 | 0 | { |
634 | 0 | PacketBuffer packet; |
635 | 0 | ComboAddress remote; |
636 | 0 | iovec iov{}; |
637 | | /* used by HarvestDestinationAddress */ |
638 | 0 | cmsgbuf_aligned cbuf{}; |
639 | 0 | }; |
640 | 0 | const size_t vectSize = dnsdist::configuration::getImmutableConfiguration().d_udpVectorSize; |
641 | |
|
642 | 0 | if (vectSize > std::numeric_limits<uint16_t>::max()) { |
643 | 0 | throw std::runtime_error("The value of setUDPMultipleMessagesVectorSize is too high, the maximum value is " + std::to_string(std::numeric_limits<uint16_t>::max())); |
644 | 0 | } |
645 | | |
646 | 0 | auto recvData = std::vector<MMReceiver>(vectSize); |
647 | 0 | auto msgVec = std::vector<mmsghdr>(vectSize); |
648 | | |
649 | | /* initialize the structures needed to receive our messages */ |
650 | 0 | for (size_t idx = 0; idx < vectSize; idx++) { |
651 | 0 | auto& slot = recvData[idx]; |
652 | 0 | slot.packet.resize(initialBufferSize + 1); |
653 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
654 | 0 | fillMSGHdr(&msgVec[idx].msg_hdr, &slot.iov, &slot.cbuf, sizeof(decltype(slot.cbuf)), reinterpret_cast<char*>(slot.packet.data()), initialBufferSize + 1, &slot.remote); |
655 | 0 | } |
656 | |
|
657 | 0 | int msgsGot = static_cast<int>(vectSize); |
658 | | /* go now */ |
659 | 0 | for (;;) { |
660 | 0 | if (dss->isStopped()) { |
661 | 0 | break; |
662 | 0 | } |
663 | | |
664 | 0 | if (!dss->connected) { |
665 | | /* the sockets are not connected yet, likely because we detected a problem, |
666 | | tried to reconnect and it failed. We will try to reconnect after the next |
667 | | successful health-check (unless reconnectOnUp is false), or when trying |
668 | | to send in the UDP listener thread, but until then we simply need to wait. */ |
669 | 0 | dss->waitUntilConnected(); |
670 | 0 | continue; |
671 | 0 | } |
672 | | |
673 | 0 | dss->pickSocketsReadyForReceiving(sockets); |
674 | | |
675 | | /* check a second time here because we might have waited quite a bit |
676 | | since the first check */ |
677 | 0 | if (dss->isStopped()) { |
678 | 0 | break; |
679 | 0 | } |
680 | | |
681 | 0 | for (const auto& sockDesc : sockets) { |
682 | | /* reset the IO vector. |
683 | | No need to reset the parts that have not been used, though. */ |
684 | 0 | for (int idx = 0; idx < msgsGot; idx++) { |
685 | 0 | auto& slot = recvData[idx]; |
686 | | /* only resize if the buffer is actually smaller than expected */ |
687 | 0 | if (slot.packet.size() < (initialBufferSize + 1)) { |
688 | 0 | slot.packet.resize((initialBufferSize + 1)); |
689 | 0 | } |
690 | | /* but we need to set the IOv pointer and size anyway, |
691 | | because if the buffer has been resized at any point |
692 | | during the processing (to add EDNS options, for example) |
693 | | the pointer might now be invalid */ |
694 | 0 | slot.iov.iov_base = &slot.packet.at(0); |
695 | 0 | slot.iov.iov_len = slot.packet.size(); |
696 | 0 | } |
697 | | |
698 | | /* block until we have at least one message ready, but return |
699 | | as many as possible to save the syscall costs */ |
700 | 0 | msgsGot = recvmmsg(sockDesc, msgVec.data(), vectSize, MSG_WAITFORONE, nullptr); |
701 | 0 | if (msgsGot <= 0) { |
702 | 0 | int savederrno = errno; |
703 | 0 | if (dss->isStopped()) { |
704 | 0 | break; |
705 | 0 | } |
706 | 0 | VERBOSESLOG(infolog("Getting UDP messages from backend via recvmmsg() failed with: %s", stringerror(savederrno)), |
707 | 0 | responderLogger->error(Logr::Info, savederrno, "Getting UDP messages via recvmmsg failed")); |
708 | 0 | msgsGot = 0; |
709 | 0 | continue; |
710 | 0 | } |
711 | | |
712 | | /* process the received messages */ |
713 | 0 | for (int msgIdx = 0; msgIdx < msgsGot; msgIdx++) { |
714 | 0 | try { |
715 | 0 | auto& msg = msgVec.at(msgIdx); |
716 | 0 | unsigned int got = msg.msg_len; |
717 | 0 | const struct msghdr* msgh = &msg.msg_hdr; |
718 | 0 | if ((msgh->msg_flags & MSG_TRUNC) != 0) { |
719 | 0 | continue; |
720 | 0 | } |
721 | | |
722 | 0 | processUDPResponseFromBackend(*responderLogger, dss, recvData.at(msgIdx).packet, got, initialBufferSize, sockDesc); |
723 | 0 | } |
724 | 0 | catch (const std::exception& e) { |
725 | 0 | VERBOSESLOG(infolog("Got an error in UDP responder thread while parsing a response from %s: %s", dss->d_config.remote.toStringWithPort(), e.what()), |
726 | 0 | responderLogger->error(Logr::Info, e.what(), "Got an error in UDP responder thread while parsing a response")); |
727 | 0 | } |
728 | 0 | } |
729 | 0 | } |
730 | 0 | } |
731 | 0 | } |
732 | 0 | catch (const std::exception& e) { |
733 | 0 | SLOG(errlog("UDP responder thread died because of exception: %s", e.what()), |
734 | 0 | responderLogger->error(Logr::Error, e.what(), "UDP responder thread died because of an exception")); |
735 | 0 | } |
736 | 0 | catch (...) { |
737 | 0 | SLOG(errlog("UDP responder thread died because of an exception: %s", "unknown"), |
738 | 0 | responderLogger->info(Logr::Error, "UDP responder thread died because of an unknown exception")); |
739 | 0 | } |
740 | 0 | } |
741 | | #endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */ |
742 | | #endif /* DISABLE_RECVMMSG */ |
743 | | |
744 | | // listens to incoming queries, sends out to downstream servers, noting the intended return path |
745 | | void udpClientThread(std::vector<ClientState*> states) |
746 | 0 | { |
747 | 0 | try { |
748 | 0 | setThreadName("dnsdist/udpClie"); |
749 | 0 | #ifndef DISABLE_RECVMMSG |
750 | 0 | #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) |
751 | 0 | if (dnsdist::configuration::getImmutableConfiguration().d_udpVectorSize > 1) { |
752 | 0 | MultipleMessagesUDPClientThread(states.at(0)); |
753 | 0 | } |
754 | 0 | else |
755 | 0 | #endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */ |
756 | 0 | #endif /* DISABLE_RECVMMSG */ |
757 | 0 | { |
758 | | /* the actual buffer is larger because: |
759 | | - we may have to add EDNS and/or ECS |
760 | | - we use it for self-generated responses (from rule or cache) |
761 | | but we only accept incoming payloads up to that size |
762 | | */ |
763 | 0 | struct UDPStateParam |
764 | 0 | { |
765 | 0 | ClientState* cs{nullptr}; |
766 | 0 | size_t maxIncomingPacketSize{0}; |
767 | 0 | int socket{-1}; |
768 | 0 | }; |
769 | 0 | const size_t initialBufferSize = getInitialUDPPacketBufferSize(true); |
770 | 0 | PacketBuffer packet(initialBufferSize); |
771 | |
|
772 | 0 | msghdr msgh{}; |
773 | 0 | iovec iov{}; |
774 | 0 | ComboAddress remote; |
775 | 0 | ComboAddress dest; |
776 | |
|
777 | 0 | auto handleOnePacket = [&packet, &iov, &msgh, &remote, &dest, initialBufferSize](const UDPStateParam& param) { |
778 | 0 | packet.resize(initialBufferSize); |
779 | 0 | iov.iov_base = &packet.at(0); |
780 | 0 | iov.iov_len = packet.size(); |
781 | |
|
782 | 0 | ssize_t got = recvmsg(param.socket, &msgh, 0); |
783 | |
|
784 | 0 | if (got < 0 || static_cast<size_t>(got) < sizeof(struct dnsheader)) { |
785 | 0 | ++dnsdist::metrics::g_stats.nonCompliantQueries; |
786 | 0 | ++param.cs->nonCompliantQueries; |
787 | 0 | return; |
788 | 0 | } |
789 | | |
790 | 0 | packet.resize(static_cast<size_t>(got)); |
791 | |
|
792 | 0 | dnsdist::configuration::refreshLocalRuntimeConfiguration(); |
793 | 0 | processUDPQuery(*param.cs, &msgh, remote, dest, packet, nullptr, nullptr, nullptr, nullptr); |
794 | 0 | }; |
795 | |
|
796 | 0 | std::vector<UDPStateParam> params; |
797 | 0 | for (auto& state : states) { |
798 | 0 | const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*state); |
799 | 0 | params.emplace_back(UDPStateParam{state, maxIncomingPacketSize, state->udpFD}); |
800 | 0 | } |
801 | |
|
802 | 0 | if (params.size() == 1) { |
803 | 0 | const auto& param = params.at(0); |
804 | 0 | remote.sin4.sin_family = param.cs->local.sin4.sin_family; |
805 | | /* used by HarvestDestinationAddress */ |
806 | 0 | cmsgbuf_aligned cbuf; |
807 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
808 | 0 | fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param.maxIncomingPacketSize, &remote); |
809 | 0 | while (true) { |
810 | 0 | try { |
811 | 0 | handleOnePacket(param); |
812 | 0 | } |
813 | 0 | catch (const std::bad_alloc& e) { |
814 | | /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc) |
815 | | in which case we DO NOT want to log (as it would trigger another memory allocation attempt |
816 | | that might throw as well) but wait a bit (one millisecond) and then try to recover */ |
817 | 0 | usleep(1000); |
818 | 0 | } |
819 | 0 | } |
820 | 0 | } |
821 | 0 | else { |
822 | 0 | auto callback = [&remote, &msgh, &iov, &packet, &handleOnePacket, initialBufferSize](int socket, FDMultiplexer::funcparam_t& funcparam) { |
823 | 0 | (void)socket; |
824 | 0 | const auto* param = boost::any_cast<const UDPStateParam*>(funcparam); |
825 | 0 | try { |
826 | 0 | remote.sin4.sin_family = param->cs->local.sin4.sin_family; |
827 | 0 | packet.resize(initialBufferSize); |
828 | | /* used by HarvestDestinationAddress */ |
829 | 0 | cmsgbuf_aligned cbuf; |
830 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
831 | 0 | fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param->maxIncomingPacketSize, &remote); |
832 | 0 | handleOnePacket(*param); |
833 | 0 | } |
834 | 0 | catch (const std::bad_alloc& e) { |
835 | | /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc) |
836 | | in which case we DO NOT want to log (as it would trigger another memory allocation attempt |
837 | | that might throw as well) but wait a bit (one millisecond) and then try to recover */ |
838 | 0 | usleep(1000); |
839 | 0 | } |
840 | 0 | }; |
841 | 0 | auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size())); |
842 | 0 | for (const auto& param : params) { |
843 | 0 | mplexer->addReadFD(param.socket, callback, ¶m); |
844 | 0 | } |
845 | |
|
846 | 0 | timeval now{}; |
847 | 0 | while (true) { |
848 | 0 | mplexer->run(&now, -1); |
849 | 0 | } |
850 | 0 | } |
851 | 0 | } |
852 | 0 | } |
853 | 0 | catch (const std::exception& e) { |
854 | 0 | SLOG(errlog("UDP client thread died because of exception: %s", e.what()), |
855 | 0 | dnsdist::logging::getTopLogger("udp-frontend")->error(Logr::Error, e.what(), "UDP client thread died because of exception")); |
856 | 0 | } |
857 | 0 | catch (...) { |
858 | 0 | SLOG(errlog("UDP client thread died because of an exception: unknown"), |
859 | 0 | dnsdist::logging::getTopLogger("udp-frontend")->info(Logr::Error, "UDP client thread died because of an unknown exception")); |
860 | 0 | } |
861 | 0 | } |
862 | | |
863 | | std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion) |
864 | 0 | { |
865 | 0 | dnsQuestion.ids.origID = dnsQuestion.getHeader()->id; |
866 | 0 | return std::make_unique<dnsdist::udp::UDPCrossProtocolQuery>(std::move(dnsQuestion.getMutableData()), std::move(dnsQuestion.ids), nullptr); |
867 | 0 | } |
868 | | |
869 | | // listens on a dedicated socket, lobs answers from downstream servers to original requestors |
870 | | void responderThread(std::shared_ptr<DownstreamState> dss) |
871 | 0 | { |
872 | 0 | #ifndef DISABLE_RECVMMSG |
873 | 0 | #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) |
874 | 0 | if (dnsdist::configuration::getImmutableConfiguration().d_udpVectorSize > 1) { |
875 | 0 | MultipleMessagesUDPResponseFromBackendThread(std::move(dss)); |
876 | 0 | return; |
877 | 0 | } |
878 | 0 | #endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */ |
879 | 0 | #endif /* DISABLE_RECVMMSG */ |
880 | | |
881 | 0 | auto responderLogger = dnsdist::logging::getTopLogger("udp-response")->withValues("backend.address", Logging::Loggable(dss->d_config.remote)); |
882 | |
|
883 | 0 | try { |
884 | 0 | setThreadName("dnsdist/respond"); |
885 | 0 | const size_t initialBufferSize = dnsdist::udp::getInitialUDPPacketBufferSize(false); |
886 | | /* allocate one more byte so we can detect truncation */ |
887 | 0 | PacketBuffer response(initialBufferSize + 1); |
888 | 0 | std::vector<int> sockets; |
889 | 0 | sockets.reserve(dss->sockets.size()); |
890 | |
|
891 | 0 | for (;;) { |
892 | 0 | try { |
893 | 0 | if (dss->isStopped()) { |
894 | 0 | break; |
895 | 0 | } |
896 | | |
897 | 0 | if (!dss->connected) { |
898 | | /* the sockets are not connected yet, likely because we detected a problem, |
899 | | tried to reconnect and it failed. We will try to reconnect after the next |
900 | | successful health-check (unless reconnectOnUp is false), or when trying |
901 | | to send in the UDP listener thread, but until then we simply need to wait. */ |
902 | 0 | dss->waitUntilConnected(); |
903 | 0 | continue; |
904 | 0 | } |
905 | | |
906 | 0 | dss->pickSocketsReadyForReceiving(sockets); |
907 | | |
908 | | /* check a second time here because we might have waited quite a bit |
909 | | since the first check */ |
910 | 0 | if (dss->isStopped()) { |
911 | 0 | break; |
912 | 0 | } |
913 | | |
914 | 0 | for (const auto& sockDesc : sockets) { |
915 | | /* allocate one more byte so we can detect truncation */ |
916 | | // NOLINTNEXTLINE(bugprone-use-after-move): resizing a vector has no preconditions so it is valid to do so after moving it |
917 | 0 | response.resize(initialBufferSize + 1); |
918 | 0 | ssize_t got = recv(sockDesc, response.data(), response.size(), 0); |
919 | |
|
920 | 0 | if (got == 0 && dss->isStopped()) { |
921 | 0 | break; |
922 | 0 | } |
923 | | |
924 | 0 | processUDPResponseFromBackend(*responderLogger, dss, response, got, initialBufferSize, sockDesc); |
925 | 0 | } |
926 | 0 | } |
927 | 0 | catch (const std::exception& e) { |
928 | 0 | VERBOSESLOG(infolog("Got an error in UDP responder thread while parsing a response from %s: %s", dss->d_config.remote.toStringWithPort(), e.what()), |
929 | 0 | responderLogger->error(Logr::Info, e.what(), "Got an error in UDP responder thread while parsing a response")); |
930 | 0 | } |
931 | 0 | } |
932 | 0 | } |
933 | 0 | catch (const std::exception& e) { |
934 | 0 | SLOG(errlog("UDP responder thread died because of exception: %s", e.what()), |
935 | 0 | responderLogger->error(Logr::Error, e.what(), "UDP responder thread died because of an exception")); |
936 | 0 | } |
937 | 0 | catch (...) { |
938 | 0 | SLOG(errlog("UDP responder thread died because of an exception: %s", "unknown"), |
939 | 0 | responderLogger->info(Logr::Error, "UDP responder thread died because of an unknown exception")); |
940 | 0 | } |
941 | 0 | } |
942 | | |
943 | | } // namespace dnsdist::udp |