Kea 1.9.11
io_fetch.cc
Go to the documentation of this file.
1// Copyright (C) 2011-2021 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>
21#include <dns/qid_gen.h>
22#include <dns/rcode.h>
23#include <util/buffer.h>
24
25#include <boost/scoped_ptr.hpp>
26#include <boost/date_time/posix_time/posix_time_types.hpp>
27
28#include <functional>
29#include <unistd.h> // for some IPC/network system calls
30#include <netinet/in.h>
31#include <stdint.h>
32#include <sys/socket.h>
33
34using namespace boost::asio;
35using namespace isc::asiolink;
36using namespace isc::dns;
37using namespace isc::util;
38using namespace isc::log;
39using namespace std;
40
41namespace isc {
42namespace asiodns {
43
44// Log debug verbosity
45
49
58
59 // The first two members are shared pointers to a base class because what is
60 // actually instantiated depends on whether the fetch is over UDP or TCP,
61 // which is not known until construction of the IOFetch. Use of a shared
62 // pointer here is merely to ensure deletion when the data object is deleted.
63 boost::scoped_ptr<IOAsioSocket<IOFetch> > socket;
65 boost::scoped_ptr<IOEndpoint> remote_snd;
66 boost::scoped_ptr<IOEndpoint> remote_rcv;
70 boost::asio::deadline_timer timer;
72 size_t cumulative;
73 size_t expected;
74 size_t offset;
75 bool stopped;
76 int timeout;
77 bool packet;
78
79 // In case we need to log an error, the origin of the last asynchronous
80 // I/O is recorded. To save time and simplify the code, this is recorded
81 // as the ID of the error message that would be generated if the I/O failed.
82 // This means that we must make sure that all possible "origins" take the
83 // same arguments in their message in the same order.
88
107 const IOAddress& address, uint16_t port, OutputBufferPtr& buff,
108 IOFetch::Callback* cb, int wait)
109 :
110 socket((proto == IOFetch::UDP) ?
111 static_cast<IOAsioSocket<IOFetch>*>(
112 new UDPSocket<IOFetch>(service)) :
113 static_cast<IOAsioSocket<IOFetch>*>(
114 new TCPSocket<IOFetch>(service))
115 ),
116 remote_snd((proto == IOFetch::UDP) ?
117 static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
118 static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
119 ),
120 remote_rcv((proto == IOFetch::UDP) ?
121 static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
122 static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
123 ),
124 msgbuf(new OutputBuffer(512)),
125 received(buff),
126 callback(cb),
127 timer(service.get_io_service()),
128 protocol(proto),
129 cumulative(0),
130 expected(0),
131 offset(0),
132 stopped(false),
133 timeout(wait),
134 packet(false),
136 staging(),
137 qid(QidGenerator::getInstance().generateQid())
138 {}
139
140 // Checks if the response we received was ok;
141 // - data contains the buffer we read, as well as the address
142 // we sent to and the address we received from.
143 // length is provided by the operator() in IOFetch.
144 // Addresses must match, number of octets read must be at least
145 // 2, and the first two octets must match the qid of the message
146 // we sent.
147 bool responseOK() {
148 return (*remote_snd == *remote_rcv && cumulative >= 2 &&
149 readUint16(received->getData(), received->getLength()) == qid);
150 }
151};
152
154
156 const isc::dns::Question& question, const IOAddress& address,
157 uint16_t port, OutputBufferPtr& buff, Callback* cb, int wait, bool edns)
158{
159 MessagePtr query_msg(new Message(Message::RENDER));
160 initIOFetch(query_msg, protocol, service, question, address, port, buff,
161 cb, wait, edns);
162}
163
165 OutputBufferPtr& outpkt, const IOAddress& address, uint16_t port,
166 OutputBufferPtr& buff, Callback* cb, int wait)
167 :
168 data_(new IOFetchData(protocol, service,
169 address, port, buff, cb, wait))
170{
171 data_->msgbuf = outpkt;
172 data_->packet = true;
173}
174
176 ConstMessagePtr query_message, const IOAddress& address, uint16_t port,
177 OutputBufferPtr& buff, Callback* cb, int wait)
178{
179 MessagePtr msg(new Message(Message::RENDER));
180
181 msg->setHeaderFlag(Message::HEADERFLAG_RD,
182 query_message->getHeaderFlag(Message::HEADERFLAG_RD));
183 msg->setHeaderFlag(Message::HEADERFLAG_CD,
184 query_message->getHeaderFlag(Message::HEADERFLAG_CD));
185
186 initIOFetch(msg, protocol, service,
187 **(query_message->beginQuestion()),
188 address, port, buff, cb, wait);
189}
190
191void
192IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol,
193 IOService& service,
194 const isc::dns::Question& question,
195 const IOAddress& address, uint16_t port,
196 OutputBufferPtr& buff, Callback* cb, int wait, bool edns)
197{
198 data_ = boost::shared_ptr<IOFetchData>(new IOFetchData(
199 protocol, service, address, port, buff, cb, wait));
200
201 query_msg->setQid(data_->qid);
202 query_msg->setOpcode(Opcode::QUERY());
203 query_msg->setRcode(Rcode::NOERROR());
204 query_msg->setHeaderFlag(Message::HEADERFLAG_RD);
205 query_msg->addQuestion(question);
206
207 if (edns) {
208 EDNSPtr edns_query(new EDNS());
209 edns_query->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
210 query_msg->setEDNS(edns_query);
211 }
212
213 MessageRenderer renderer;
214 renderer.setBuffer(data_->msgbuf.get());
215 query_msg->toWire(renderer);
216 renderer.setBuffer(NULL);
217}
218
219// Return protocol in use.
220
223 return (data_->protocol);
224}
225
228
229void
230IOFetch::operator()(boost::system::error_code ec, size_t length) {
231
232 if (data_->stopped) {
233 return;
234
235 // On Debian it has been often observed that boost::asio async
236 // operations result in EINPROGRESS. This doesn't necessarily
237 // indicate an issue. Thus, we continue as if no error occurred.
238 } else if (ec && (ec.value() != boost::asio::error::in_progress)) {
239 logIOFailure(ec);
240 return;
241 }
242
243 BOOST_ASIO_CORO_REENTER (this) {
244
248 {
249 if (data_->packet) {
250 // A packet was given, overwrite the QID (which is in the
251 // first two bytes of the packet).
252 data_->msgbuf->writeUint16At(data_->qid, 0);
253
254 }
255 }
256
257 // If we timeout, we stop, which will can cancel outstanding I/Os and
258 // shutdown everything.
259 if (data_->timeout != -1) {
260 data_->timer.expires_from_now(boost::posix_time::milliseconds(
261 data_->timeout));
262 data_->timer.async_wait(std::bind(&IOFetch::stop, *this,
263 TIME_OUT));
264 }
265
266 // Open a connection to the target system. For speed, if the operation
267 // is synchronous (i.e. UDP operation) we bypass the yield.
268 data_->origin = ASIODNS_OPEN_SOCKET;
269 if (data_->socket->isOpenSynchronous()) {
270 data_->socket->open(data_->remote_snd.get(), *this);
271 } else {
272 BOOST_ASIO_CORO_YIELD data_->socket->open(data_->remote_snd.get(), *this);
273 }
274
275 do {
276 // Begin an asynchronous send, and then yield. When the send completes,
277 // we will resume immediately after this point.
278 data_->origin = ASIODNS_SEND_DATA;
279 BOOST_ASIO_CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(),
280 data_->msgbuf->getLength(), data_->remote_snd.get(), *this);
281
282 // Now receive the response. Since TCP may not receive the entire
283 // message in one operation, we need to loop until we have received
284 // it. (This can't be done within the asyncReceive() method because
285 // each I/O operation will be done asynchronously and between each one
286 // we need to yield ... and we *really* don't want to set up another
287 // coroutine within that method.) So after each receive (and yield),
288 // we check if the operation is complete and if not, loop to read again.
289 //
290 // Another concession to TCP is that the amount of is contained in the
291 // first two bytes. This leads to two problems:
292 //
293 // a) We don't want those bytes in the return buffer.
294 // b) They may not both arrive in the first I/O.
295 //
296 // So... we need to loop until we have at least two bytes, then store
297 // the expected amount of data. Then we need to loop until we have
298 // received all the data before copying it back to the user's buffer.
299 // And we want to minimize the amount of copying...
300
301 data_->origin = ASIODNS_READ_DATA;
302 data_->cumulative = 0; // No data yet received
303 data_->offset = 0; // First data into start of buffer
304 data_->received->clear(); // Clear the receive buffer
305 do {
306 BOOST_ASIO_CORO_YIELD data_->socket->asyncReceive(data_->staging,
307 static_cast<size_t>(STAGING_LENGTH),
308 data_->offset,
309 data_->remote_rcv.get(), *this);
310 } while (!data_->socket->processReceivedData(data_->staging, length,
311 data_->cumulative, data_->offset,
312 data_->expected, data_->received));
313 } while (!data_->responseOK());
314
315 // Finished with this socket, so close it. This will not generate an
316 // I/O error, but reset the origin to unknown in case we change this.
317 data_->origin = ASIODNS_UNKNOWN_ORIGIN;
318 data_->socket->close();
319
321 stop(SUCCESS);
322 }
323}
324
325// Function that stops the coroutine sequence. It is called either when the
326// query finishes or when the timer times out. Either way, it sets the
327// "stopped_" flag and cancels anything that is in progress.
328//
329// As the function may be entered multiple times as things wind down, it checks
330// if the stopped_ flag is already set. If it is, the call is a no-op.
331
332void
334
335 if (!data_->stopped) {
336
337 // Mark the fetch as stopped to prevent other completion callbacks
338 // (invoked because of the calls to cancel()) from executing the
339 // cancel calls again.
340 //
341 // In a single threaded environment, the callbacks won't be invoked
342 // until this one completes. In a multi-threaded environment, they may
343 // well be, in which case the testing (and setting) of the stopped_
344 // variable should be done inside a mutex (and the stopped_ variable
345 // declared as "volatile").
346 //
347 // TODO: Update testing of stopped_ if threads are used.
348 data_->stopped = true;
349 switch (result) {
350 case TIME_OUT:
352 arg(data_->remote_snd->getAddress().toText()).
353 arg(data_->remote_snd->getPort());
354 break;
355
356 case SUCCESS:
358 arg(data_->remote_rcv->getAddress().toText()).
359 arg(data_->remote_rcv->getPort());
360 break;
361
362 case STOPPED:
363 // Fetch has been stopped for some other reason. This is
364 // allowed but as it is unusual it is logged, but with a lower
365 // debug level than a timeout (which is totally normal).
367 arg(data_->remote_snd->getAddress().toText()).
368 arg(data_->remote_snd->getPort());
369 break;
370
371 default:
373 arg(data_->remote_snd->getAddress().toText()).
374 arg(data_->remote_snd->getPort());
375 }
376
377 // Stop requested, cancel and I/O's on the socket and shut it down,
378 // and cancel the timer.
379 data_->socket->cancel();
380 data_->socket->close();
381
382 data_->timer.cancel();
383
384 // Execute the I/O completion callback (if present).
385 if (data_->callback) {
386 (*(data_->callback))(result);
387 }
388 }
389}
390
391// Log an error - called on I/O failure
392
393void IOFetch::logIOFailure(boost::system::error_code ec) {
394
395 // Should only get here with a known error code.
396 if ((data_->origin != ASIODNS_OPEN_SOCKET) &&
397 (data_->origin != ASIODNS_SEND_DATA) &&
398 (data_->origin != ASIODNS_READ_DATA) &&
399 (data_->origin != ASIODNS_UNKNOWN_ORIGIN)) {
400 isc_throw(isc::Unexpected, "impossible error code " << data_->origin);
401 }
402
403 LOG_ERROR(logger, data_->origin).arg(ec.value()).
404 arg((data_->remote_snd->getProtocol() == IPPROTO_TCP) ?
405 "TCP" : "UDP").
406 arg(data_->remote_snd->getAddress().toText()).
407 arg(data_->remote_snd->getPort());
408}
409
410} // namespace asiodns
411} // namespace isc {
A generic exception that is thrown when an unexpected error condition occurs.
I/O Fetch Callback.
Definition: io_fetch.h:98
Upstream Fetch Processing.
Definition: io_fetch.h:45
void operator()(boost::system::error_code ec=boost::system::error_code(), size_t length=0)
Coroutine entry point.
Definition: io_fetch.cc:230
@ STAGING_LENGTH
Size of staging buffer.
Definition: io_fetch.h:82
Result
Result of Upstream Fetch.
Definition: io_fetch.h:70
@ STOPPED
Control code, fetch has been stopped.
Definition: io_fetch.h:73
@ TIME_OUT
Failure, fetch timed out.
Definition: io_fetch.h:72
@ SUCCESS
Success, fetch completed.
Definition: io_fetch.h:71
IOFetch(Protocol protocol, isc::asiolink::IOService &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:155
Protocol
Protocol to use on the fetch.
Definition: io_fetch.h:48
void stop(Result reason=STOPPED)
Terminate query.
Definition: io_fetch.cc:333
Protocol getProtocol() const
Return Current Protocol.
Definition: io_fetch.cc:222
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:123
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:143
This class generates Qids for outgoing queries.
Definition: qid_gen.h:29
The Question class encapsulates the common search key of DNS lookup, consisting of owner name,...
Definition: question.h:95
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
#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_ALL
Definition: io_fetch.cc:48
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:47
const isc::log::MessageID ASIODNS_READ_DATA
const int DBG_IMPORTANT
Definition: io_fetch.cc:46
const isc::log::MessageID ASIODNS_SEND_DATA
boost::shared_ptr< const Message > ConstMessagePtr
Definition: message.h:663
uint16_t qid_t
Definition: message.h:75
boost::shared_ptr< Message > MessagePtr
Pointer-like type pointing to a Message.
Definition: message.h:662
boost::shared_ptr< EDNS > EDNSPtr
A pointer-like type pointing to an EDNS object.
Definition: edns.h:31
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
const char * MessageID
Definition: message_types.h:15
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:75
Definition: edns.h:19
boost::shared_ptr< OutputBuffer > OutputBufferPtr
Definition: buffer.h:599
uint16_t readUint16(const void *buffer, size_t length)
Read Unsigned 16-Bit Integer from Buffer.
Definition: io_utilities.h:28
Defines the logger used by the top-level component of kea-lfc.
IOFetch::Callback * callback
Called on I/O Completion.
Definition: io_fetch.cc:69
boost::scoped_ptr< IOEndpoint > remote_snd
Where the fetch is sent.
Definition: io_fetch.cc:65
isc::dns::qid_t qid
The QID set in the query.
Definition: io_fetch.cc:87
int timeout
Timeout in ms.
Definition: io_fetch.cc:76
bool stopped
Have we stopped running?
Definition: io_fetch.cc:75
OutputBufferPtr msgbuf
Wire buffer for question.
Definition: io_fetch.cc:67
IOFetch::Protocol protocol
Protocol being used.
Definition: io_fetch.cc:71
OutputBufferPtr received
Received data put here.
Definition: io_fetch.cc:68
uint8_t staging[IOFetch::STAGING_LENGTH]
Temporary array for received data.
Definition: io_fetch.cc:85
size_t expected
Expected amount of data.
Definition: io_fetch.cc:73
boost::scoped_ptr< IOAsioSocket< IOFetch > > socket
Socket to use for I/O.
Definition: io_fetch.cc:63
size_t cumulative
Cumulative received amount.
Definition: io_fetch.cc:72
IOFetchData(IOFetch::Protocol proto, IOService &service, const IOAddress &address, uint16_t port, OutputBufferPtr &buff, IOFetch::Callback *cb, int wait)
Constructor.
Definition: io_fetch.cc:106
boost::asio::deadline_timer timer
Timer to measure timeouts.
Definition: io_fetch.cc:70
boost::scoped_ptr< IOEndpoint > remote_rcv
Where the response came from.
Definition: io_fetch.cc:66
isc::log::MessageID origin
Origin of last asynchronous I/O.
Definition: io_fetch.cc:84
size_t offset
Offset to receive data.
Definition: io_fetch.cc:74
bool packet
true if packet was supplied
Definition: io_fetch.cc:77