Kea 3.3.3
io_fetch.cc
Go to the documentation of this file.
1// Copyright (C) 2011-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
12#include <asiolink/io_service.h>
14#include <asiolink/tcp_socket.h>
16#include <asiolink/udp_socket.h>
17#include <asiodns/io_fetch.h>
18#include <asiodns/logger.h>
19#include <dns/messagerenderer.h>
20#include <dns/opcode.h>
22#include <dns/rcode.h>
23#include <util/io.h>
24
25#include <boost/scoped_ptr.hpp>
26#include <boost/date_time/posix_time/posix_time_types.hpp>
27
28#include <chrono>
29#include <functional>
30#include <unistd.h> // for some IPC/network system calls
31#include <netinet/in.h>
32#include <stdint.h>
33#include <sys/socket.h>
34
35using namespace isc::asiolink;
36using namespace isc::dns;
37using namespace isc::log;
38using namespace isc::util;
39
40using namespace boost::asio;
41using namespace std;
42
43namespace isc {
44namespace asiodns {
45
46// Log debug verbosity.
47
51
59struct IOFetchData : boost::noncopyable {
60 IOServicePtr io_service_; // The IO service
61 // The first two members are shared pointers to a base class because what is
62 // actually instantiated depends on whether the fetch is over UDP or TCP,
63 // which is not known until construction of the IOFetch. Use of a shared
64 // pointer here is merely to ensure deletion when the data object is deleted.
65 boost::scoped_ptr<IOAsioSocket<IOFetch>> socket; // Socket to use for I/O
66 boost::scoped_ptr<IOEndpoint> remote_snd; // Where the fetch is sent
67 boost::scoped_ptr<IOEndpoint> remote_rcv; // Where the response came from
68 OutputBufferPtr msgbuf; // Wire buffer for question
69 OutputBufferPtr received; // Received data put here
70 IOFetch::Callback* callback; // Called on I/O Completion
71 boost::asio::system_timer timer; // Timer to measure timeouts
72 IOFetch::Protocol protocol; // Protocol being used
73 size_t cumulative; // Cumulative received amount
74 size_t expected; // Expected amount of data
75 size_t offset; // Offset to receive data
76 bool stopped; // Have we stopped running?
77 int timeout; // Timeout in ms
78 bool packet; // true if packet was supplied
79
80 // In case we need to log an error, the origin of the last asynchronous
81 // I/O is recorded. To save time and simplify the code, this is recorded
82 // as the ID of the error message that would be generated if the I/O failed.
83 // This means that we must make sure that all possible "origins" take the
84 // same arguments in their message in the same order.
85 isc::log::MessageID origin; // Origin of last asynchronous I/O
87 // Temporary array for received data
88 isc::dns::qid_t qid; // The QID set in the query
89
108 const IOAddress& address, uint16_t port, OutputBufferPtr& buff,
109 IOFetch::Callback* cb, int wait) :
110 io_service_(service), socket((proto == IOFetch::UDP) ?
111 static_cast<IOAsioSocket<IOFetch>*>(new UDPSocket<IOFetch>(io_service_)) :
112 static_cast<IOAsioSocket<IOFetch>*>(new TCPSocket<IOFetch>(io_service_))),
113 remote_snd((proto == IOFetch::UDP) ?
114 static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
115 static_cast<IOEndpoint*>(new TCPEndpoint(address, port))),
116 remote_rcv((proto == IOFetch::UDP) ?
117 static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
118 static_cast<IOEndpoint*>(new TCPEndpoint(address, port))),
119 msgbuf(new OutputBuffer(512)), received(buff), callback(cb),
120 timer(io_service_->getInternalIOService()), protocol(proto), cumulative(0),
121 expected(0), offset(0), stopped(false), timeout(wait), packet(false),
122 origin(ASIODNS_UNKNOWN_ORIGIN), staging(), qid(cryptolink::generateQid()) {
123 }
124
127 timer.cancel();
128 }
129
137 bool responseOK() {
138 return (*remote_snd == *remote_rcv && cumulative >= 2 &&
139 readUint16(received->getData(), received->getLength()) == qid);
140 }
141};
142
143IOFetch::IOFetch(Protocol protocol, const IOServicePtr& service,
144 const isc::dns::Question& question, const IOAddress& address,
145 uint16_t port, OutputBufferPtr& buff, Callback* cb, int wait, bool edns) {
147 initIOFetch(query, protocol, service, question, address, port, buff,
148 cb, wait, edns);
149}
150
151IOFetch::IOFetch(Protocol protocol, const IOServicePtr& service,
152 OutputBufferPtr& outpkt, const IOAddress& address, uint16_t port,
153 OutputBufferPtr& buff, Callback* cb, int wait) :
154 data_(new IOFetchData(protocol, service,
155 address, port, buff, cb, wait)) {
156 data_->msgbuf = outpkt;
157 data_->packet = true;
158}
159
160IOFetch::IOFetch(Protocol protocol, const IOServicePtr& service,
161 ConstMessagePtr query_message, const IOAddress& address,
162 uint16_t port, OutputBufferPtr& buff, Callback* cb, int wait) {
163 MessagePtr question(new Message(Message::RENDER));
164 question->setHeaderFlag(Message::HEADERFLAG_RD,
165 query_message->getHeaderFlag(Message::HEADERFLAG_RD));
166 question->setHeaderFlag(Message::HEADERFLAG_CD,
167 query_message->getHeaderFlag(Message::HEADERFLAG_CD));
168
169 initIOFetch(question, protocol, service, **(query_message->beginQuestion()), address,
170 port, buff, cb, wait);
171}
172
173void
174IOFetch::initIOFetch(MessagePtr& query, Protocol protocol, const IOServicePtr& service,
175 const isc::dns::Question& question, const IOAddress& address, uint16_t port,
176 OutputBufferPtr& buff, Callback* cb, int wait, bool edns) {
177 data_ = boost::shared_ptr<IOFetchData>(new IOFetchData(protocol, service, address, port, buff, cb, wait));
178 query->setQid(data_->qid);
179 query->setOpcode(Opcode::QUERY());
180 query->setRcode(Rcode::NOERROR());
181 query->setHeaderFlag(Message::HEADERFLAG_RD);
182 query->addQuestion(question);
183
184 if (edns) {
185 EDNSPtr edns_query(new EDNS());
186 edns_query->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
187 query->setEDNS(edns_query);
188 }
189
191 r.setBuffer(data_->msgbuf.get());
192 query->toWire(r);
193 r.setBuffer(NULL);
194}
195
198 return (data_->protocol);
199}
200
201void
202IOFetch::operator()(boost::system::error_code ec, size_t length) {
203 if (data_->stopped) {
204 return;
205
206 // On Debian it has been often observed that boost::asio async
207 // operations result in EINPROGRESS. This doesn't necessarily
208 // indicate an issue. Thus, we continue as if no error occurred.
209 } else if (ec && (ec.value() != boost::asio::error::in_progress)) {
210 // operation_aborted is expected when stop() cancels outstanding
211 // I/O; stop() (or an earlier completion) already invokes the
212 // user callback. Other I/O errors must complete the fetch so
213 // callers without a timeout do not hang indefinitely.
214 if (ec != boost::asio::error::operation_aborted) {
215 logIOFailure(ec);
216 stop(IO_ERROR);
217 }
218 return;
219 }
220
221 BOOST_ASIO_CORO_REENTER (this) {
222
226 {
227 if (data_->packet) {
228 // A packet was given, overwrite the QID (which is in the
229 // first two bytes of the packet).
230 data_->msgbuf->writeUint16At(data_->qid, 0);
231
232 }
233 }
234
235 // If we timeout, we stop, which cancels outstanding I/O operations and
236 // shuts down everything.
237 if (data_->timeout != -1) {
238 data_->timer.expires_after(std::chrono::milliseconds(
239 data_->timeout));
240 data_->timer.async_wait(std::bind(&IOFetch::stop, *this,
241 TIME_OUT));
242 }
243
244 // Open a connection to the target system. For speed, if the operation
245 // is synchronous (i.e. UDP operation) we bypass the yield.
246 data_->origin = ASIODNS_OPEN_SOCKET;
247 if (data_->socket->isOpenSynchronous()) {
248 data_->socket->open(data_->remote_snd.get(), *this);
249 } else {
250 BOOST_ASIO_CORO_YIELD data_->socket->open(data_->remote_snd.get(), *this);
251 }
252
253 do {
254 // Begin an asynchronous send, and then yield. When the send completes,
255 // we will resume immediately after this point.
256 data_->origin = ASIODNS_SEND_DATA;
257 BOOST_ASIO_CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(),
258 data_->msgbuf->getLength(), data_->remote_snd.get(), *this);
259
260 // Now receive the response. Since TCP may not receive the entire
261 // message in one operation, we need to loop until we have received
262 // it. (This can't be done within the asyncReceive() method because
263 // each I/O operation will be done asynchronously and between each one
264 // we need to yield ... and we *really* don't want to set up another
265 // coroutine within that method.) So after each receive (and yield),
266 // we check if the operation is complete and if not, loop to read again.
267 //
268 // Another concession to TCP is that the amount of is contained in the
269 // first two bytes. This leads to two problems:
270 //
271 // a) We don't want those bytes in the return buffer.
272 // b) They may not both arrive in the first I/O.
273 //
274 // So... we need to loop until we have at least two bytes, then store
275 // the expected amount of data. Then we need to loop until we have
276 // received all the data before copying it back to the user's buffer.
277 // And we want to minimize the amount of copying...
278
279 data_->origin = ASIODNS_READ_DATA;
280 data_->cumulative = 0; // No data yet received
281 data_->offset = 0; // First data into start of buffer
282 data_->received->clear(); // Clear the receive buffer
283 do {
284 BOOST_ASIO_CORO_YIELD data_->socket->asyncReceive(data_->staging,
285 static_cast<size_t>(STAGING_LENGTH),
286 data_->offset,
287 data_->remote_rcv.get(), *this);
288 } while (!data_->socket->processReceivedData(data_->staging, length,
289 data_->cumulative, data_->offset,
290 data_->expected, data_->received));
291 } while (!data_->responseOK());
292
293 // Finished with this socket, so close it. This will not generate an
294 // I/O error, but reset the origin to unknown in case we change this.
295 data_->origin = ASIODNS_UNKNOWN_ORIGIN;
296 data_->socket->close();
297
299 stop(SUCCESS);
300 }
301}
302
303void
305 if (!data_->stopped) {
306
307 // Mark the fetch as stopped to prevent other completion callbacks
308 // (invoked because of the calls to cancel()) from executing the
309 // cancel calls again.
310 //
311 // In a single threaded environment, the callbacks won't be invoked
312 // until this one completes. In a multi-threaded environment, they may
313 // well be, in which case the testing (and setting) of the stopped_
314 // variable should be done inside a mutex (and the stopped_ variable
315 // declared as "volatile").
316 //
317 // TODO: Update testing of stopped_ if threads are used.
318 data_->stopped = true;
319 switch (result) {
320 case SUCCESS:
322 arg(data_->remote_rcv->getAddress().toText()).
323 arg(data_->remote_rcv->getPort());
324 break;
325
326 case TIME_OUT:
328 arg(data_->remote_snd->getAddress().toText()).
329 arg(data_->remote_snd->getPort());
330 break;
331
332 case STOPPED:
333 // Fetch has been stopped for some other reason. This is
334 // allowed but as it is unusual it is logged, but with a lower
335 // debug level than a timeout (which is totally normal).
337 arg(data_->remote_snd->getAddress().toText()).
338 arg(data_->remote_snd->getPort());
339 break;
340
341 case IO_ERROR:
342 // The originating ASIO error was already reported by
343 // logIOFailure(); avoid treating this as an unknown result.
344 break;
345
346 default:
348 arg(data_->remote_snd->getAddress().toText()).
349 arg(data_->remote_snd->getPort());
350 }
351
352 // Stop requested, cancel and I/O's on the socket and shut it down,
353 // and cancel the timer.
354 data_->socket->cancel();
355 data_->socket->close();
356
357 data_->timer.cancel();
358
359 // Execute the I/O completion callback (if present).
360 if (data_->callback) {
361 (*(data_->callback))(result);
362 }
363 }
364}
365
366void IOFetch::logIOFailure(boost::system::error_code ec) {
367 // Should only get here with a known error code.
368 if ((data_->origin != ASIODNS_OPEN_SOCKET) &&
369 (data_->origin != ASIODNS_SEND_DATA) &&
370 (data_->origin != ASIODNS_READ_DATA) &&
371 (data_->origin != ASIODNS_UNKNOWN_ORIGIN)) {
372 isc_throw(isc::Unexpected, "impossible error code " << data_->origin);
373 }
374
375 LOG_ERROR(logger, data_->origin).arg(ec.value()).
376 arg((data_->remote_snd->getProtocol() == IPPROTO_TCP) ?
377 "TCP" : "UDP").
378 arg(data_->remote_snd->getAddress().toText()).
379 arg(data_->remote_snd->getPort());
380}
381
382} // namespace asiodns
383} // namespace isc
A generic exception that is thrown when an unexpected error condition occurs.
I/O Fetch Callback.
Definition io_fetch.h:106
Upstream Fetch Processing.
Definition io_fetch.h:36
Result
Result of Upstream Fetch.
Definition io_fetch.h:61
Protocol
Protocol to use on the fetch.
Definition io_fetch.h:39
IOFetch(Protocol protocol, const isc::asiolink::IOServicePtr &service, const isc::dns::Question &question, const isc::asiolink::IOAddress &address, uint16_t port, isc::util::OutputBufferPtr &buff, Callback *cb, int wait=-1, bool edns=true)
Constructor.
Definition io_fetch.cc:143
void operator()(boost::system::error_code ec=boost::system::error_code(), size_t length=0)
Coroutine entry point.
Definition io_fetch.cc:202
void stop(Result reason=STOPPED)
Terminate query.
Definition io_fetch.cc:304
Protocol getProtocol() const
Return Current Protocol.
Definition io_fetch.cc:197
void setBuffer(isc::util::OutputBuffer *buffer)
Set or reset a temporary output buffer.
The EDNS class represents the EDNS OPT RR defined in RFC2671.
Definition edns.h:121
The MessageRenderer is a concrete derived class of AbstractMessageRenderer as a general purpose imple...
The Message class encapsulates a standard DNS message.
Definition message.h:150
static const uint16_t DEFAULT_MAX_EDNS0_UDPSIZE
The default maximum size of UDP DNS messages we can handle.
Definition message.h:655
static const Opcode & QUERY()
A constant object for the QUERY Opcode.
Definition opcode.h:177
The Question class encapsulates the common search key of DNS lookup, consisting of owner name,...
Definition question.h:88
static const Rcode & NOERROR()
A constant object for the NOERROR Rcode (see Rcode::NOERROR_CODE).
Definition rcode.h:227
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition buffer.h:355
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
const int DBG_VERBOSE
Definition io_fetch.cc:50
isc::log::Logger logger("asiodns")
Use the ASIO logger.
const isc::log::MessageID ASIODNS_READ_TIMEOUT
const isc::log::MessageID ASIODNS_UNKNOWN_ORIGIN
const isc::log::MessageID ASIODNS_FETCH_STOPPED
const isc::log::MessageID ASIODNS_OPEN_SOCKET
const isc::log::MessageID ASIODNS_UNKNOWN_RESULT
const isc::log::MessageID ASIODNS_FETCH_COMPLETED
const int DBG_COMMON
Definition io_fetch.cc:49
const isc::log::MessageID ASIODNS_READ_DATA
const int DBG_IMPORTANT
Definition io_fetch.cc:48
const isc::log::MessageID ASIODNS_SEND_DATA
boost::shared_ptr< const Message > ConstMessagePtr
Definition message.h:669
uint16_t qid_t
Definition message.h:70
boost::shared_ptr< Message > MessagePtr
Pointer-like type pointing to a Message.
Definition message.h:668
boost::shared_ptr< EDNS > EDNSPtr
A pointer-like type pointing to an EDNS object.
Definition edns.h:32
const int DBGLVL_TRACE_BASIC
Trace basic operations.
const int DBGLVL_TRACE_TECHNICAL
Trace technical operations.
const char * MessageID
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
uint16_t readUint16(void const *const buffer, size_t const length)
uint16_t wrapper over readUint.
Definition io.h:76
boost::shared_ptr< OutputBuffer > OutputBufferPtr
Type of pointers to output buffers.
Definition buffer.h:583
Defines the logger used by the top-level component of kea-lfc.
IOFetch::Callback * callback
Definition io_fetch.cc:70
boost::scoped_ptr< IOEndpoint > remote_snd
Definition io_fetch.cc:66
isc::dns::qid_t qid
Definition io_fetch.cc:88
boost::asio::system_timer timer
Definition io_fetch.cc:71
IOServicePtr io_service_
Definition io_fetch.cc:60
OutputBufferPtr msgbuf
Definition io_fetch.cc:68
IOFetch::Protocol protocol
Definition io_fetch.cc:72
OutputBufferPtr received
Definition io_fetch.cc:69
uint8_t staging[IOFetch::STAGING_LENGTH]
Definition io_fetch.cc:86
boost::scoped_ptr< IOAsioSocket< IOFetch > > socket
Definition io_fetch.cc:65
boost::scoped_ptr< IOEndpoint > remote_rcv
Definition io_fetch.cc:67
~IOFetchData()
Destructor.
Definition io_fetch.cc:126
isc::log::MessageID origin
Definition io_fetch.cc:85
bool responseOK()
Checks if the response we received was ok.
Definition io_fetch.cc:137
IOFetchData(IOFetch::Protocol proto, const IOServicePtr &service, const IOAddress &address, uint16_t port, OutputBufferPtr &buff, IOFetch::Callback *cb, int wait)
Constructor.
Definition io_fetch.cc:107