Branch data Line data Source code
1 : : // Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
2 : : //
3 : : // Permission to use, copy, modify, and/or distribute this software for any
4 : : // purpose with or without fee is hereby granted, provided that the above
5 : : // copyright notice and this permission notice appear in all copies.
6 : : //
7 : : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
8 : : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9 : : // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
10 : : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 : : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
12 : : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 : : // PERFORMANCE OF THIS SOFTWARE.
14 : :
15 : : #include <config.h>
16 : :
17 : : #include <unistd.h> // for some IPC/network system calls
18 : : #include <netinet/in.h>
19 : : #include <sys/socket.h>
20 : : #include <errno.h>
21 : :
22 : : #include <boost/shared_array.hpp>
23 : :
24 : : #include <log/dummylog.h>
25 : :
26 : : #include <util/buffer.h>
27 : :
28 : : #include <asio.hpp>
29 : : #include <asiolink/dummy_io_cb.h>
30 : : #include <asiolink/tcp_endpoint.h>
31 : : #include <asiolink/tcp_socket.h>
32 : : #include <asiodns/tcp_server.h>
33 : : #include <asiodns/logger.h>
34 : :
35 : : using namespace asio;
36 : : using asio::ip::udp;
37 : : using asio::ip::tcp;
38 : :
39 : : using namespace std;
40 : : using namespace isc::dns;
41 : : using namespace isc::util;
42 : : using namespace isc::asiolink;
43 : :
44 : : namespace isc {
45 : : namespace asiodns {
46 : :
47 : : /// The following functions implement the \c TCPServer class.
48 : : ///
49 : : /// The constructor
50 : 85 : TCPServer::TCPServer(io_service& io_service, int fd, int af,
51 : : const SimpleCallback* checkin,
52 : : const DNSLookup* lookup,
53 : : const DNSAnswer* answer) :
54 : : io_(io_service), done_(false),
55 : : checkin_callback_(checkin), lookup_callback_(lookup),
56 [ + - ][ # # ]: 85 : answer_callback_(answer)
57 : : {
58 [ + + ][ # # ]: 85 : if (af != AF_INET && af != AF_INET6) {
59 [ + - ][ + - ]: 4 : isc_throw(InvalidParameter, "Address family must be either AF_INET "
[ + - ][ # # ]
[ # # ][ # # ]
60 : : "or AF_INET6, not " << af);
61 : : }
62 [ + - ][ + - ]: 83 : LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_TCP).arg(fd);
[ + - ][ + - ]
[ + - ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
63 : :
64 : : try {
65 [ + - ][ + - ]: 83 : acceptor_.reset(new tcp::acceptor(io_service));
[ + - ][ # # ]
[ # # ][ # # ]
66 [ + + ][ + + ]: 136 : acceptor_->assign(af == AF_INET6 ? tcp::v6() : tcp::v4(), fd);
[ # # ][ # # ]
67 [ + - ][ # # ]: 81 : acceptor_->listen();
68 [ - + ][ # # ]: 4 : } catch (const std::exception& exception) {
69 : : // Whatever the thing throws, it is something from ASIO and we convert
70 : : // it
71 [ - + ][ - + ]: 4 : isc_throw(IOError, exception.what());
[ # # ][ # # ]
72 : : }
73 : 81 : }
74 : :
75 : : void
76 : 163 : TCPServer::operator()(asio::error_code ec, size_t length) {
77 : : /// Because the coroutine reentry block is implemented as
78 : : /// a switch statement, inline variable declarations are not
79 : : /// permitted. Certain variables used below can be declared here.
80 : :
81 : : boost::array<const_buffer,2> bufs;
82 : 163 : OutputBuffer lenbuf(TCP_MESSAGE_LENGTHSIZE);
83 : :
84 [ - + + + : 163 : CORO_REENTER (this) {
+ - + - -
+ - - +
- ][ # # ]
85 [ + + ]: 32 : do {
86 : : /// Create a socket to listen for connections
87 [ + - ][ + - ]: 166 : socket_.reset(new tcp::socket(acceptor_->get_io_service()));
88 : :
89 : : /// Wait for new connections. In the event of non-fatal error,
90 : : /// try again
91 [ - + ]: 16 : do {
92 [ + - ]: 166 : CORO_YIELD acceptor_->async_accept(*socket_, *this);
[ - - - + ]
[ # # ][ - + ]
[ + - ]
93 : :
94 : : // Abort on fatal errors
95 : : // TODO: Log error?
96 [ + + ]: 28 : if (ec) {
97 : : using namespace asio::error;
98 [ + - ][ + - ]: 12 : if (ec.value() != would_block && ec.value() != try_again &&
[ + - ][ - + ]
[ - + ]
99 : : ec.value() != connection_aborted &&
100 : : ec.value() != interrupted) {
101 : : return;
102 : : }
103 : : }
104 : : } while (ec);
105 : :
106 : : /// Fork the coroutine by creating a copy of this one and
107 : : /// scheduling it on the ASIO service queue. The parent
108 : : /// will continue listening for DNS connections while the
109 : : /// handles the one that has just arrived.
110 [ + + ][ + - ]: 32 : CORO_FORK io_.post(TCPServer(*this));
[ + - ]
111 : 32 : } while (is_parent());
112 : :
113 : : /// Instantiate the data buffer that will be used by the
114 : : /// asynchronous read call.
115 [ + - ]: 16 : data_.reset(new char[MAX_LENGTH]);
116 : :
117 : : /// Read the message, in two parts. First, the message length:
118 [ + - ]: 48 : CORO_YIELD async_read(*socket_, asio::buffer(data_.get(),
[ - - - + ]
[ # # ][ - + ]
119 [ + - ][ + - ]: 32 : TCP_MESSAGE_LENGTHSIZE), *this);
120 [ - + ]: 16 : if (ec) {
121 [ # # ]: 0 : socket_->close();
122 [ # # ]: 0 : CORO_YIELD return;
[ # # # # ]
[ # # ][ # # ]
123 : : }
124 : :
125 : : /// Now read the message itself. (This is done in a different scope
126 : : /// to allow inline variable declarations.)
127 [ + - ]: 32 : CORO_YIELD {
[ - - - + ]
[ # # ][ - + ]
128 : 16 : InputBuffer dnsbuffer(data_.get(), length);
129 : 16 : uint16_t msglen = dnsbuffer.readUint16();
130 [ + - ][ + - ]: 16 : async_read(*socket_, asio::buffer(data_.get(), msglen), *this);
131 : : }
132 : :
133 [ - + ]: 16 : if (ec) {
134 [ # # ]: 0 : socket_->close();
135 [ # # ]: 0 : CORO_YIELD return;
[ # # # # ]
[ # # ][ # # ]
136 : : }
137 : :
138 : :
139 : : // Create an \c IOMessage object to store the query.
140 : : //
141 : : // (XXX: It would be good to write a factory function
142 : : // that would quickly generate an IOMessage object without
143 : : // all these calls to "new".)
144 [ + - ][ + - ]: 32 : peer_.reset(new TCPEndpoint(socket_->remote_endpoint()));
[ + - ]
145 : :
146 : : // The TCP socket class has been extended with asynchronous functions
147 : : // and takes as a template parameter a completion callback class. As
148 : : // TCPServer does not use these extended functions (only those defined
149 : : // in the IOSocket base class) - but needs a TCPSocket to get hold of
150 : : // the underlying Boost TCP socket - DummyIOCallback is used. This
151 : : // provides the appropriate operator() but is otherwise functionless.
152 [ + - ][ + - ]: 16 : iosock_.reset(new TCPSocket<DummyIOCallback>(*socket_));
153 [ + - ][ + - ]: 16 : io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
154 : 16 : bytes_ = length;
155 : :
156 : : // Perform any necessary operations prior to processing the incoming
157 : : // packet (e.g., checking for queued configuration messages).
158 : : //
159 : : // (XXX: it may be a performance issue to have this called for
160 : : // every single incoming packet; we may wish to throttle it somehow
161 : : // in the future.)
162 [ + - ]: 16 : if (checkin_callback_ != NULL) {
163 [ + - ]: 16 : (*checkin_callback_)(*io_message_);
164 : : }
165 : :
166 : : // If we don't have a DNS Lookup provider, there's no point in
167 : : // continuing; we exit the coroutine permanently.
168 [ + + ]: 16 : if (lookup_callback_ == NULL) {
169 [ + - ]: 6 : socket_->close();
170 [ + - ]: 6 : CORO_YIELD return;
[ - - - + ]
[ # # ][ # # ]
171 : : }
172 : :
173 : : // Reset or instantiate objects that will be needed by the
174 : : // DNS lookup and the write call.
175 [ + - ][ + - ]: 10 : respbuf_.reset(new OutputBuffer(0));
[ + - ]
176 [ + - ][ + - ]: 10 : query_message_.reset(new Message(Message::PARSE));
[ + - ]
177 [ + - ][ + - ]: 10 : answer_message_.reset(new Message(Message::RENDER));
[ + - ]
178 : :
179 : : // Schedule a DNS lookup, and yield. When the lookup is
180 : : // finished, the coroutine will resume immediately after
181 : : // this point.
182 [ + - ]: 20 : CORO_YIELD io_.post(AsyncLookup<TCPServer>(*this));
[ - - - + ]
[ # # ][ - + ]
183 : :
184 : : // The 'done_' flag indicates whether we have an answer
185 : : // to send back. If not, exit the coroutine permanently.
186 [ - + ]: 10 : if (!done_) {
187 : : // TODO: should we keep the connection open for a short time
188 : : // to see if new requests come in?
189 [ # # ]: 0 : socket_->close();
190 [ # # ]: 0 : CORO_YIELD return;
[ # # # # ]
[ # # ][ # # ]
191 : : }
192 : :
193 [ - + ]: 10 : if (ec) {
194 [ # # ]: 0 : CORO_YIELD return;
[ # # # # ]
[ # # ][ # # ]
195 : : }
196 : : // Call the DNS answer provider to render the answer into
197 : : // wire format
198 : 10 : (*answer_callback_)(*io_message_, query_message_,
199 [ + - ]: 10 : answer_message_, respbuf_);
200 : :
201 : : // Set up the response, beginning with two length bytes.
202 [ + - ]: 10 : lenbuf.writeUint16(respbuf_->getLength());
203 : 10 : bufs[0] = buffer(lenbuf.getData(), lenbuf.getLength());
204 : 10 : bufs[1] = buffer(respbuf_->getData(), respbuf_->getLength());
205 : :
206 : : // Begin an asynchronous send, and then yield. When the
207 : : // send completes, we will resume immediately after this point
208 : : // (though we have nothing further to do, so the coroutine
209 : : // will simply exit at that time).
210 [ + - ]: 20 : CORO_YIELD async_write(*socket_, bufs, *this);
[ - - - + ]
[ # # ][ - + ]
[ + - ]
211 : :
212 : : // TODO: should we keep the connection open for a short time
213 : : // to see if new requests come in?
214 [ + - ]: 10 : socket_->close();
215 : : }
216 : : }
217 : :
218 : : /// Call the DNS lookup provider. (Expected to be called by the
219 : : /// AsyncLookup<TCPServer> handler.)
220 : : void
221 : 10 : TCPServer::asyncLookup() {
222 : 10 : (*lookup_callback_)(*io_message_, query_message_,
223 [ + - ]: 10 : answer_message_, respbuf_, this);
224 : 10 : }
225 : :
226 : 44 : void TCPServer::stop() {
227 : : /// we use close instead of cancel, with the same reason
228 : : /// with udp server stop, refer to the udp server code
229 : :
230 : 44 : acceptor_->close();
231 : : // User may stop the server even when it hasn't started to
232 : : // run, in that that socket_ is empty
233 [ + + ]: 44 : if (socket_) {
234 : 28 : socket_->close();
235 : : }
236 : 44 : }
237 : : /// Post this coroutine on the ASIO service queue so that it will
238 : : /// resume processing where it left off. The 'done' parameter indicates
239 : : /// whether there is an answer to return to the client.
240 : : void
241 : 10 : TCPServer::resume(const bool done) {
242 : 10 : done_ = done;
243 [ + - ]: 10 : io_.post(*this);
244 : 10 : }
245 : :
246 : : } // namespace asiodns
247 [ + - ][ + - ]: 25 : } // namespace isc
|