io_fetch.cc 16.2 KB
Newer Older
1
// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 7 8

#include <config.h>

9
#include <unistd.h>             // for some IPC/network system calls
10
#include <netinet/in.h>
11 12
#include <stdint.h>
#include <sys/socket.h>
13 14

#include <boost/bind.hpp>
15
#include <boost/scoped_ptr.hpp>
16
#include <boost/date_time/posix_time/posix_time_types.hpp>
17

18 19
#include <boost/asio.hpp>
#include <boost/asio/deadline_timer.hpp>
20

21
#include <asiolink/io_address.h>
22
#include <asiolink/io_asio_socket.h>
23
#include <asiolink/io_endpoint.h>
24 25 26 27 28
#include <asiolink/io_service.h>
#include <asiolink/tcp_endpoint.h>
#include <asiolink/tcp_socket.h>
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
29

30 31 32 33 34
#include <dns/messagerenderer.h>
#include <dns/opcode.h>
#include <dns/rcode.h>

#include <asiodns/io_fetch.h>
35 36 37

#include <util/buffer.h>
#include <util/random/qid_gen.h>
38

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
39
#include <asiodns/logger.h>
40

41
using namespace boost::asio;
42
using namespace isc::asiolink;
43
using namespace isc::dns;
44
using namespace isc::util;
45
using namespace isc::util::random;
Stephen Morris's avatar
Stephen Morris committed
46 47
using namespace isc::log;
using namespace std;
48

49 50
namespace isc {
namespace asiodns {
51

52 53
// Log debug verbosity

54 55 56
const int DBG_IMPORTANT = DBGLVL_TRACE_BASIC;
const int DBG_COMMON = DBGLVL_TRACE_DETAIL;
const int DBG_ALL = DBGLVL_TRACE_DETAIL + 20;
57

58 59
/// \brief IOFetch Data
///
60 61 62 63 64
/// The data for IOFetch is held in a separate struct pointed to by a shared_ptr
/// object.  This is because the IOFetch object will be copied often (it is used
/// as a coroutine and passed as callback to many async_*() functions) and we
/// want keep the same data).  Organising the data in this way keeps copying to
/// a minimum.
65 66 67 68 69
struct IOFetchData {

    // The first two members are shared pointers to a base class because what is
    // actually instantiated depends on whether the fetch is over UDP or TCP,
    // which is not known until construction of the IOFetch.  Use of a shared
70
    // pointer here is merely to ensure deletion when the data object is deleted.
71
    boost::scoped_ptr<IOAsioSocket<IOFetch> > socket;
72 73 74
                                             ///< Socket to use for I/O
    boost::scoped_ptr<IOEndpoint> remote_snd;///< Where the fetch is sent
    boost::scoped_ptr<IOEndpoint> remote_rcv;///< Where the response came from
75 76
    OutputBufferPtr   msgbuf;      ///< Wire buffer for question
    OutputBufferPtr   received;    ///< Received data put here
77
    IOFetch::Callback*          callback;    ///< Called on I/O Completion
78
    boost::asio::deadline_timer timer;       ///< Timer to measure timeouts
79 80 81 82 83 84
    IOFetch::Protocol           protocol;    ///< Protocol being used
    size_t                      cumulative;  ///< Cumulative received amount
    size_t                      expected;    ///< Expected amount of data
    size_t                      offset;      ///< Offset to receive data
    bool                        stopped;     ///< Have we stopped running?
    int                         timeout;     ///< Timeout in ms
Stephen Morris's avatar
Stephen Morris committed
85
    bool                        packet;      ///< true if packet was supplied
86 87 88 89 90 91 92

    // In case we need to log an error, the origin of the last asynchronous
    // I/O is recorded.  To save time and simplify the code, this is recorded
    // as the ID of the error message that would be generated if the I/O failed.
    // This means that we must make sure that all possible "origins" take the
    // same arguments in their message in the same order.
    isc::log::MessageID         origin;     ///< Origin of last asynchronous I/O
93 94
    uint8_t                     staging[IOFetch::STAGING_LENGTH];
                                            ///< Temporary array for received data
95
    isc::dns::qid_t             qid;         ///< The QID set in the query
96 97 98 99 100

    /// \brief Constructor
    ///
    /// Just fills in the data members of the IOFetchData structure
    ///
101
    /// \param proto Either IOFetch::TCP or IOFetch::UDP.
102
    /// \param service I/O Service object to handle the asynchronous
103
    ///        operations.
104 105 106
    /// \param address IP address of upstream server
    /// \param port Port to use for the query
    /// \param buff Output buffer into which the response (in wire format)
107
    ///        is written (if a response is received).
108
    /// \param cb Callback object containing the callback to be called
109 110
    ///        when we terminate.  The caller is responsible for managing this
    ///        object and deleting it if necessary.
111 112 113
    /// \param wait Timeout for the fetch (in ms).
    ///
    /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554)
114
    IOFetchData(IOFetch::Protocol proto, IOService& service,
115 116
        const IOAddress& address, uint16_t port, OutputBufferPtr& buff,
        IOFetch::Callback* cb, int wait)
117
        :
118
        socket((proto == IOFetch::UDP) ?
119 120 121 122 123
            static_cast<IOAsioSocket<IOFetch>*>(
                new UDPSocket<IOFetch>(service)) :
            static_cast<IOAsioSocket<IOFetch>*>(
                new TCPSocket<IOFetch>(service))
            ),
124 125 126 127 128
        remote_snd((proto == IOFetch::UDP) ?
            static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
            static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
            ),
        remote_rcv((proto == IOFetch::UDP) ?
129 130 131
            static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
            static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
            ),
132
        msgbuf(new OutputBuffer(512)),
133
        received(buff),
134
        callback(cb),
135 136
        timer(service.get_io_service()),
        protocol(proto),
137
        cumulative(0),
138 139
        expected(0),
        offset(0),
140 141
        stopped(false),
        timeout(wait),
Stephen Morris's avatar
Stephen Morris committed
142
        packet(false),
143
        origin(ASIODNS_UNKNOWN_ORIGIN),
144 145
        staging(),
        qid(QidGenerator::getInstance().generateQid())
146
    {}
147 148 149 150 151 152 153 154 155 156

    // Checks if the response we received was ok;
    // - data contains the buffer we read, as well as the address
    // we sent to and the address we received from.
    // length is provided by the operator() in IOFetch.
    // Addresses must match, number of octets read must be at least
    // 2, and the first two octets must match the qid of the message
    // we sent.
    bool responseOK() {
        return (*remote_snd == *remote_rcv && cumulative >= 2 &&
157
                readUint16(received->getData(), received->getLength()) == qid);
158
    }
159 160
};

161
/// IOFetch Constructor - just initialize the private data
Stephen Morris's avatar
Stephen Morris committed
162

163
IOFetch::IOFetch(Protocol protocol, IOService& service,
Dima Volodin's avatar
Dima Volodin committed
164 165
    const isc::dns::Question& question, const IOAddress& address,
    uint16_t port, OutputBufferPtr& buff, Callback* cb, int wait, bool edns)
166
{
167 168
    MessagePtr query_msg(new Message(Message::RENDER));
    initIOFetch(query_msg, protocol, service, question, address, port, buff,
Dima Volodin's avatar
Dima Volodin committed
169
                cb, wait, edns);
170 171
}

Stephen Morris's avatar
Stephen Morris committed
172 173 174 175 176 177 178 179 180 181 182
IOFetch::IOFetch(Protocol protocol, IOService& service,
    OutputBufferPtr& outpkt, const IOAddress& address, uint16_t port,
    OutputBufferPtr& buff, Callback* cb, int wait)
    :
    data_(new IOFetchData(protocol, service,
          address, port, buff, cb, wait))
{
    data_->msgbuf = outpkt;
    data_->packet = true;
}

183 184 185 186 187
IOFetch::IOFetch(Protocol protocol, IOService& service,
    ConstMessagePtr query_message, const IOAddress& address, uint16_t port,
    OutputBufferPtr& buff, Callback* cb, int wait)
{
    MessagePtr msg(new Message(Message::RENDER));
188 189 190 191 192

    msg->setHeaderFlag(Message::HEADERFLAG_RD,
                       query_message->getHeaderFlag(Message::HEADERFLAG_RD));
    msg->setHeaderFlag(Message::HEADERFLAG_CD,
                       query_message->getHeaderFlag(Message::HEADERFLAG_CD));
193 194 195 196 197 198 199

    initIOFetch(msg, protocol, service,
                **(query_message->beginQuestion()),
                address, port, buff, cb, wait);
}

void
200 201
IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol,
                     IOService& service,
202 203
                     const isc::dns::Question& question,
                     const IOAddress& address, uint16_t port,
Dima Volodin's avatar
Dima Volodin committed
204
                     OutputBufferPtr& buff, Callback* cb, int wait, bool edns)
205 206 207 208 209 210 211 212 213
{
    data_ = boost::shared_ptr<IOFetchData>(new IOFetchData(
        protocol, service, address, port, buff, cb, wait));

    query_msg->setQid(data_->qid);
    query_msg->setOpcode(Opcode::QUERY());
    query_msg->setRcode(Rcode::NOERROR());
    query_msg->setHeaderFlag(Message::HEADERFLAG_RD);
    query_msg->addQuestion(question);
Dima Volodin's avatar
Dima Volodin committed
214 215 216 217 218 219 220

    if (edns) {
        EDNSPtr edns_query(new EDNS());
        edns_query->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
        query_msg->setEDNS(edns_query);
    }

221 222
    MessageRenderer renderer;
    renderer.setBuffer(data_->msgbuf.get());
223
    query_msg->toWire(renderer);
224
    renderer.setBuffer(NULL);
225 226
}

227 228 229 230 231 232 233
// Return protocol in use.

IOFetch::Protocol
IOFetch::getProtocol() const {
    return (data_->protocol);
}

234 235
/// The function operator is implemented with the "stackless coroutine"
/// pattern; see internal/coroutine.h for details.
Stephen Morris's avatar
Stephen Morris committed
236

237
void
238
IOFetch::operator()(boost::system::error_code ec, size_t length) {
239

240 241 242 243
    if (data_->stopped) {
        return;
    } else if (ec) {
        logIOFailure(ec);
244 245 246 247
        return;
    }

    CORO_REENTER (this) {
Stephen Morris's avatar
Stephen Morris committed
248

249 250 251 252
        /// Generate the upstream query and render it to wire format
        /// This is done in a different scope to allow inline variable
        /// declarations.
        {
Stephen Morris's avatar
Stephen Morris committed
253 254 255 256 257
            if (data_->packet) {
                // A packet was given, overwrite the QID (which is in the
                // first two bytes of the packet).
                data_->msgbuf->writeUint16At(data_->qid, 0);

258
            } 
259 260
        }

261 262
        // If we timeout, we stop, which will can cancel outstanding I/Os and
        // shutdown everything.
263 264 265 266 267 268 269 270
        if (data_->timeout != -1) {
            data_->timer.expires_from_now(boost::posix_time::milliseconds(
                data_->timeout));
            data_->timer.async_wait(boost::bind(&IOFetch::stop, *this,
                TIME_OUT));
        }

        // Open a connection to the target system.  For speed, if the operation
271
        // is synchronous (i.e. UDP operation) we bypass the yield.
272
        data_->origin = ASIODNS_OPEN_SOCKET;
273
        if (data_->socket->isOpenSynchronous()) {
274
            data_->socket->open(data_->remote_snd.get(), *this);
275
        } else {
276
            CORO_YIELD data_->socket->open(data_->remote_snd.get(), *this);
277 278
        }

Stephen Morris's avatar
Stephen Morris committed
279
        do {
280 281
            // Begin an asynchronous send, and then yield.  When the send completes,
            // we will resume immediately after this point.
282
            data_->origin = ASIODNS_SEND_DATA;
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
            CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(),
                data_->msgbuf->getLength(), data_->remote_snd.get(), *this);
    
            // Now receive the response.  Since TCP may not receive the entire
            // message in one operation, we need to loop until we have received
            // it. (This can't be done within the asyncReceive() method because
            // each I/O operation will be done asynchronously and between each one
            // we need to yield ... and we *really* don't want to set up another
            // coroutine within that method.)  So after each receive (and yield),
            // we check if the operation is complete and if not, loop to read again.
            //
            // Another concession to TCP is that the amount of is contained in the
            // first two bytes.  This leads to two problems:
            //
            // a) We don't want those bytes in the return buffer.
            // b) They may not both arrive in the first I/O.
            //
            // So... we need to loop until we have at least two bytes, then store
            // the expected amount of data.  Then we need to loop until we have
            // received all the data before copying it back to the user's buffer.
            // And we want to minimise the amount of copying...
    
305
            data_->origin = ASIODNS_READ_DATA;
306 307
            data_->cumulative = 0;          // No data yet received
            data_->offset = 0;              // First data into start of buffer
308
            data_->received->clear();       // Clear the receive buffer
309 310 311 312 313 314 315 316 317
            do {
                CORO_YIELD data_->socket->asyncReceive(data_->staging,
                                                       static_cast<size_t>(STAGING_LENGTH),
                                                       data_->offset,
                                                       data_->remote_rcv.get(), *this);
            } while (!data_->socket->processReceivedData(data_->staging, length,
                                                         data_->cumulative, data_->offset,
                                                         data_->expected, data_->received));
        } while (!data_->responseOK());
318

319 320
        // Finished with this socket, so close it.  This will not generate an
        // I/O error, but reset the origin to unknown in case we change this.
321
        data_->origin = ASIODNS_UNKNOWN_ORIGIN;
322 323
        data_->socket->close();

324 325 326 327 328 329 330 331 332
        /// We are done
        stop(SUCCESS);
    }
}

// Function that stops the coroutine sequence.  It is called either when the
// query finishes or when the timer times out.  Either way, it sets the
// "stopped_" flag and cancels anything that is in progress.
//
333 334
// As the function may be entered multiple times as things wind down, it checks
// if the stopped_ flag is already set.  If it is, the call is a no-op.
Stephen Morris's avatar
Stephen Morris committed
335

336 337
void
IOFetch::stop(Result result) {
338

339
    if (!data_->stopped) {
340 341 342 343 344 345 346 347 348 349 350 351 352

        // Mark the fetch as stopped to prevent other completion callbacks
        // (invoked because of the calls to cancel()) from executing the
        // cancel calls again.
        //
        // In a single threaded environment, the callbacks won't be invoked
        // until this one completes. In a multi-threaded environment, they may
        // well be, in which case the testing (and setting) of the stopped_
        // variable should be done inside a mutex (and the stopped_ variable
        // declared as "volatile").
        //
        // TODO: Update testing of stopped_ if threads are used.
        data_->stopped = true;
353 354
        switch (result) {
            case TIME_OUT:
355
                LOG_DEBUG(logger, DBG_COMMON, ASIODNS_READ_TIMEOUT).
356 357
                    arg(data_->remote_snd->getAddress().toText()).
                    arg(data_->remote_snd->getPort());
358 359
                break;

360
            case SUCCESS:
361
                LOG_DEBUG(logger, DBG_ALL, ASIODNS_FETCH_COMPLETED).
362 363
                    arg(data_->remote_rcv->getAddress().toText()).
                    arg(data_->remote_rcv->getPort());
364 365
                break;

366 367 368 369
            case STOPPED:
                // Fetch has been stopped for some other reason.  This is
                // allowed but as it is unusual it is logged, but with a lower
                // debug level than a timeout (which is totally normal).
370
                LOG_DEBUG(logger, DBG_IMPORTANT, ASIODNS_FETCH_STOPPED).
371 372
                    arg(data_->remote_snd->getAddress().toText()).
                    arg(data_->remote_snd->getPort());
373 374
                break;

375
            default:
376
                LOG_ERROR(logger, ASIODNS_UNKNOWN_RESULT).
377 378
                    arg(data_->remote_snd->getAddress().toText()).
                    arg(data_->remote_snd->getPort());
379 380
        }

Stephen Morris's avatar
Stephen Morris committed
381 382 383 384 385 386 387 388
        // Stop requested, cancel and I/O's on the socket and shut it down,
        // and cancel the timer.
        data_->socket->cancel();
        data_->socket->close();

        data_->timer.cancel();

        // Execute the I/O completion callback (if present).
389
        if (data_->callback) {
Stephen Morris's avatar
Stephen Morris committed
390
            (*(data_->callback))(result);
391 392 393 394
        }
    }
}

395 396
// Log an error - called on I/O failure

397
void IOFetch::logIOFailure(boost::system::error_code ec) {
398

399
    // Should only get here with a known error code.
400 401 402
    assert((data_->origin == ASIODNS_OPEN_SOCKET) ||
           (data_->origin == ASIODNS_SEND_DATA) ||
           (data_->origin == ASIODNS_READ_DATA) ||
403
           (data_->origin == ASIODNS_UNKNOWN_ORIGIN));
404

405 406
    LOG_ERROR(logger, data_->origin).arg(ec.value()).
        arg((data_->remote_snd->getProtocol() == IPPROTO_TCP) ?
407
                     "TCP" : "UDP").
408 409
        arg(data_->remote_snd->getAddress().toText()).
        arg(data_->remote_snd->getPort());
410 411
}

412
} // namespace asiodns
413
} // namespace isc {