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 : : #ifndef __TCP_SOCKET_H
16 : : #define __TCP_SOCKET_H 1
17 : :
18 : : #ifndef ASIO_HPP
19 : : #error "asio.hpp must be included before including this, see asiolink.h as to why"
20 : : #endif
21 : :
22 : : #include <log/dummylog.h>
23 : : #include <netinet/in.h>
24 : : #include <sys/socket.h>
25 : : #include <unistd.h> // for some IPC/network system calls
26 : :
27 : : #include <algorithm>
28 : : #include <cassert>
29 : : #include <cstddef>
30 : :
31 : : #include <boost/bind.hpp>
32 : : #include <boost/numeric/conversion/cast.hpp>
33 : :
34 : : #include <config.h>
35 : :
36 : : #include <util/buffer.h>
37 : : #include <util/io_utilities.h>
38 : :
39 : : #include <asiolink/io_asio_socket.h>
40 : : #include <asiolink/io_endpoint.h>
41 : : #include <asiolink/io_service.h>
42 : : #include <asiolink/tcp_endpoint.h>
43 : :
44 : : namespace isc {
45 : : namespace asiolink {
46 : :
47 : : /// \brief Buffer Too Large
48 : : ///
49 : : /// Thrown on an attempt to send a buffer > 64k
50 : 0 : class BufferTooLarge : public IOError {
51 : : public:
52 : : BufferTooLarge(const char* file, size_t line, const char* what) :
53 [ # # ]: 0 : IOError(file, line, what) {}
54 : : };
55 : :
56 : : /// \brief The \c TCPSocket class is a concrete derived class of \c IOAsioSocket
57 : : /// that represents a TCP socket.
58 : : ///
59 : : /// \param C Callback type
60 : : template <typename C>
61 : : class TCPSocket : public IOAsioSocket<C> {
62 : : private:
63 : : /// \brief Class is non-copyable
64 : : TCPSocket(const TCPSocket&);
65 : : TCPSocket& operator=(const TCPSocket&);
66 : :
67 : : public:
68 : :
69 : : /// \brief Constructor from an ASIO TCP socket.
70 : : ///
71 : : /// \param socket The ASIO representation of the TCP socket. It is assumed
72 : : /// that the caller will open and close the socket, so these
73 : : /// operations are a no-op for that socket.
74 : : TCPSocket(asio::ip::tcp::socket& socket);
75 : :
76 : : /// \brief Constructor
77 : : ///
78 : : /// Used when the TCPSocket is being asked to manage its own internal
79 : : /// socket. In this case, the open() and close() methods are used.
80 : : ///
81 : : /// \param service I/O Service object used to manage the socket.
82 : : TCPSocket(IOService& service);
83 : :
84 : : /// \brief Destructor
85 : : virtual ~TCPSocket();
86 : :
87 : : /// \brief Return file descriptor of underlying socket
88 : 6 : virtual int getNative() const {
89 : 6 : return (socket_.native());
90 : : }
91 : :
92 : : /// \brief Return protocol of socket
93 : 6 : virtual int getProtocol() const {
94 : 6 : return (IPPROTO_TCP);
95 : : }
96 : :
97 : : /// \brief Is "open()" synchronous?
98 : : ///
99 : : /// Indicates that the opening of a TCP socket is asynchronous.
100 : 20 : virtual bool isOpenSynchronous() const {
101 : 20 : return (false);
102 : : }
103 : :
104 : : /// \brief Open Socket
105 : : ///
106 : : /// Opens the TCP socket. This is an asynchronous operation, completion of
107 : : /// which will be signalled via a call to the callback function.
108 : : ///
109 : : /// \param endpoint Endpoint to which the socket will connect.
110 : : /// \param callback Callback object.
111 : : virtual void open(const IOEndpoint* endpoint, C& callback);
112 : :
113 : : /// \brief Send Asynchronously
114 : : ///
115 : : /// Calls the underlying socket's async_send() method to send a packet of
116 : : /// data asynchronously to the remote endpoint. The callback will be called
117 : : /// on completion.
118 : : ///
119 : : /// \param data Data to send
120 : : /// \param length Length of data to send
121 : : /// \param endpoint Target of the send. (Unused for a TCP socket because
122 : : /// that was determined when the connection was opened.)
123 : : /// \param callback Callback object.
124 : : virtual void asyncSend(const void* data, size_t length,
125 : : const IOEndpoint* endpoint, C& callback);
126 : :
127 : : /// \brief Receive Asynchronously
128 : : ///
129 : : /// Calls the underlying socket's async_receive() method to read a packet
130 : : /// of data from a remote endpoint. Arrival of the data is signalled via a
131 : : /// call to the callback function.
132 : : ///
133 : : /// \param data Buffer to receive incoming message
134 : : /// \param length Length of the data buffer
135 : : /// \param offset Offset into buffer where data is to be put
136 : : /// \param endpoint Source of the communication
137 : : /// \param callback Callback object
138 : : virtual void asyncReceive(void* data, size_t length, size_t offset,
139 : : IOEndpoint* endpoint, C& callback);
140 : :
141 : : /// \brief Process received data packet
142 : : ///
143 : : /// See the description of IOAsioSocket::receiveComplete for a complete
144 : : /// description of this method.
145 : : ///
146 : : /// \param staging Pointer to the start of the staging buffer.
147 : : /// \param length Amount of data in the staging buffer.
148 : : /// \param cumulative Amount of data received before the staging buffer is
149 : : /// processed.
150 : : /// \param offset Unused.
151 : : /// \param expected unused.
152 : : /// \param outbuff Output buffer. Data in the staging buffer is be copied
153 : : /// to this output buffer in the call.
154 : : ///
155 : : /// \return Always true
156 : : virtual bool processReceivedData(const void* staging, size_t length,
157 : : size_t& cumulative, size_t& offset,
158 : : size_t& expected,
159 : : isc::util::OutputBufferPtr& outbuff);
160 : :
161 : : /// \brief Cancel I/O On Socket
162 : : virtual void cancel();
163 : :
164 : : /// \brief Close socket
165 : : virtual void close();
166 : :
167 : :
168 : : private:
169 : : // Two variables to hold the socket - a socket and a pointer to it. This
170 : : // handles the case where a socket is passed to the TCPSocket on
171 : : // construction, or where it is asked to manage its own socket.
172 : : asio::ip::tcp::socket* socket_ptr_; ///< Pointer to own socket
173 : : asio::ip::tcp::socket& socket_; ///< Socket
174 : : bool isopen_; ///< true when socket is open
175 : :
176 : : // TODO: Remove temporary buffer
177 : : // The current implementation copies the buffer passed to asyncSend() into
178 : : // a temporary buffer and precedes it with a two-byte count field. As
179 : : // ASIO should really be just about sending and receiving data, the TCP
180 : : // code should not do this. If the protocol using this requires a two-byte
181 : : // count, it should add it before calling this code. (This may be best
182 : : // achieved by altering isc::dns::buffer to have pairs of methods:
183 : : // getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
184 : : // methods taking into account a two-byte count field.)
185 : : //
186 : : // The option of sending the data in two operations, the count followed by
187 : : // the data was discounted as that would lead to two callbacks which would
188 : : // cause problems with the stackless coroutine code.
189 : : isc::util::OutputBufferPtr send_buffer_; ///< Send buffer
190 : : };
191 : :
192 : : // Constructor - caller manages socket
193 : :
194 : : template <typename C>
195 : : TCPSocket<C>::TCPSocket(asio::ip::tcp::socket& socket) :
196 : 32 : socket_ptr_(NULL), socket_(socket), isopen_(true), send_buffer_()
197 : : {
198 : : }
199 : :
200 : : // Constructor - create socket on the fly
201 : :
202 : : template <typename C>
203 : 30 : TCPSocket<C>::TCPSocket(IOService& service) :
204 : : socket_ptr_(new asio::ip::tcp::socket(service.get_io_service())),
205 [ + - ][ + - ]: 30 : socket_(*socket_ptr_), isopen_(false)
[ + - ]
206 : : {
207 : 30 : }
208 : :
209 : : // Destructor. Only delete the socket if we are managing it.
210 : :
211 : : template <typename C>
212 : 90 : TCPSocket<C>::~TCPSocket()
213 : : {
214 [ + + ][ + - ]: 74 : delete socket_ptr_;
215 : 90 : }
216 : :
217 : : // Open the socket.
218 : :
219 : : template <typename C> void
220 : 21 : TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
221 : :
222 : : // Ignore opens on already-open socket. Don't throw a failure because
223 : : // of uncertainties as to what precedes whan when using asynchronous I/O.
224 : : // At also allows us a treat a passed-in socket as a self-managed socket.
225 [ + - ]: 21 : if (!isopen_) {
226 [ + - ]: 21 : if (endpoint->getFamily() == AF_INET) {
227 : 21 : socket_.open(asio::ip::tcp::v4());
228 : : }
229 : : else {
230 : 0 : socket_.open(asio::ip::tcp::v6());
231 : : }
232 : 21 : isopen_ = true;
233 : :
234 : : // Set options on the socket:
235 : :
236 : : // Reuse address - allow the socket to bind to a port even if the port
237 : : // is in the TIMED_WAIT state.
238 : 21 : socket_.set_option(asio::socket_base::reuse_address(true));
239 : : }
240 : :
241 : : // Upconvert to a TCPEndpoint. We need to do this because although
242 : : // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
243 : : // contain a method for getting at the underlying endpoint type - that is in
244 : : /// the derived class and the two classes differ on return type.
245 [ - + ]: 21 : assert(endpoint->getProtocol() == IPPROTO_TCP);
246 : : const TCPEndpoint* tcp_endpoint =
247 : 21 : static_cast<const TCPEndpoint*>(endpoint);
248 : :
249 : : // Connect to the remote endpoint. On success, the handler will be
250 : : // called (with one argument - the length argument will default to
251 : : // zero).
252 [ + - ]: 21 : socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
253 : 21 : }
254 : :
255 : : // Send a message. Should never do this if the socket is not open, so throw
256 : : // an exception if this is the case.
257 : :
258 : : template <typename C> void
259 : 19 : TCPSocket<C>::asyncSend(const void* data, size_t length,
260 : : const IOEndpoint*, C& callback)
261 : : {
262 [ + - ]: 19 : if (isopen_) {
263 : :
264 : : // Need to copy the data into a temporary buffer and precede it with
265 : : // a two-byte count field.
266 : : // TODO: arrange for the buffer passed to be preceded by the count
267 : : try {
268 : : // Ensure it fits into 16 bits
269 : 19 : uint16_t count = boost::numeric_cast<uint16_t>(length);
270 : :
271 : : // Copy data into a buffer preceded by the count field.
272 [ + - ][ + - ]: 19 : send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
[ # # ]
273 [ # # ]: 19 : send_buffer_->writeUint16(count);
274 [ + - ]: 19 : send_buffer_->writeData(data, length);
275 : :
276 : : // ... and send it
277 [ + - ][ + - ]: 19 : socket_.async_send(asio::buffer(send_buffer_->getData(),
278 : : send_buffer_->getLength()), callback);
279 [ # # ]: 0 : } catch (boost::numeric::bad_numeric_cast&) {
280 [ # # ][ # # ]: 0 : isc_throw(BufferTooLarge,
281 : : "attempt to send buffer larger than 64kB");
282 : : }
283 : :
284 : : } else {
285 [ # # ]: 0 : isc_throw(SocketNotOpen,
286 : : "attempt to send on a TCP socket that is not open");
287 : : }
288 : 19 : }
289 : :
290 : : // Receive a message. Note that the "offset" argument is used as an index
291 : : // into the buffer in order to decide where to put the data. It is up to the
292 : : // caller to initialize the data to zero
293 : : template <typename C> void
294 : 58 : TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
295 : : IOEndpoint* endpoint, C& callback)
296 : : {
297 [ + - ]: 58 : if (isopen_) {
298 : : // Upconvert to a TCPEndpoint. We need to do this because although
299 : : // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
300 : : // does not contain a method for getting at the underlying endpoint
301 : : // type - that is in the derived class and the two classes differ on
302 : : // return type.
303 [ - + ]: 58 : assert(endpoint->getProtocol() == IPPROTO_TCP);
304 : 58 : TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
305 : :
306 : : // Write the endpoint details from the communications link. Ideally
307 : : // we should make IOEndpoint assignable, but this runs in to all sorts
308 : : // of problems concerning the management of the underlying Boost
309 : : // endpoint (e.g. if it is not self-managed, is the copied one
310 : : // self-managed?) The most pragmatic solution is to let Boost take care
311 : : // of everything and copy details of the underlying endpoint.
312 : 116 : tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
313 : :
314 : : // Ensure we can write into the buffer and if so, set the pointer to
315 : : // where the data will be written.
316 [ - + ]: 58 : if (offset >= length) {
317 [ # # ]: 0 : isc_throw(BufferOverflow, "attempt to read into area beyond end of "
318 : : "TCP receive buffer");
319 : : }
320 : 58 : void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
321 : :
322 : : // ... and kick off the read.
323 [ + - ]: 58 : socket_.async_receive(asio::buffer(buffer_start, length - offset), callback);
324 : :
325 : : } else {
326 [ # # ]: 0 : isc_throw(SocketNotOpen,
327 : : "attempt to receive from a TCP socket that is not open");
328 : : }
329 : 58 : }
330 : :
331 : : // Is the receive complete?
332 : :
333 : : template <typename C> bool
334 : 58 : TCPSocket<C>::processReceivedData(const void* staging, size_t length,
335 : : size_t& cumulative, size_t& offset,
336 : : size_t& expected,
337 : : isc::util::OutputBufferPtr& outbuff)
338 : : {
339 : : // Point to the data in the staging buffer and note how much there is.
340 : 58 : const uint8_t* data = static_cast<const uint8_t*>(staging);
341 : 58 : size_t data_length = length;
342 : :
343 : : // Is the number is "expected" valid? It won't be unless we have received
344 : : // at least two bytes of data in total for this set of receives.
345 [ + + ]: 58 : if (cumulative < 2) {
346 : :
347 : : // "expected" is not valid. Did this read give us enough data to
348 : : // work it out?
349 : 20 : cumulative += length;
350 [ + + ]: 20 : if (cumulative < 2) {
351 : :
352 : : // Nope, still not valid. This must have been the first packet and
353 : : // was only one byte long. Tell the fetch code to read the next
354 : : // packet into the staging buffer beyond the data that is already
355 : : // there so that the next time we are called we have a complete
356 : : // TCP count.
357 : 1 : offset = cumulative;
358 : 1 : return (false);
359 : : }
360 : :
361 : : // Have enough data to interpret the packet count, so do so now.
362 : 19 : expected = isc::util::readUint16(data);
363 : :
364 : : // We have two bytes less of data to process. Point to the start of the
365 : : // data and adjust the packet size. Note that at this point,
366 : : // "cumulative" is the true amount of data in the staging buffer, not
367 : : // "length".
368 : 19 : data += 2;
369 : 19 : data_length = cumulative - 2;
370 : : } else {
371 : :
372 : : // Update total amount of data received.
373 : 38 : cumulative += length;
374 : : }
375 : :
376 : : // Regardless of anything else, the next read goes into the start of the
377 : : // staging buffer.
378 : 57 : offset = 0;
379 : :
380 : : // Work out how much data we still have to put in the output buffer. (This
381 : : // could be zero if we have just interpreted the TCP count and that was
382 : : // set to zero.)
383 [ + - ]: 57 : if (expected >= outbuff->getLength()) {
384 : :
385 : : // Still need data in the output packet. Copy what we can from the
386 : : // staging buffer to the output buffer.
387 : 57 : size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
388 : 57 : outbuff->writeData(data, copy_amount);
389 : : }
390 : :
391 : : // We can now say if we have all the data.
392 : 58 : return (expected == outbuff->getLength());
393 : : }
394 : :
395 : : // Cancel I/O on the socket. No-op if the socket is not open.
396 : :
397 : : template <typename C> void
398 : 21 : TCPSocket<C>::cancel() {
399 [ + + ]: 21 : if (isopen_) {
400 : 6 : socket_.cancel();
401 : : }
402 : 21 : }
403 : :
404 : : // Close the socket down. Can only do this if the socket is open and we are
405 : : // managing it ourself.
406 : :
407 : : template <typename C> void
408 : 36 : TCPSocket<C>::close() {
409 [ + + ][ + - ]: 36 : if (isopen_ && socket_ptr_) {
410 : 21 : socket_.close();
411 : 21 : isopen_ = false;
412 : : }
413 : 36 : }
414 : :
415 : : } // namespace asiolink
416 : : } // namespace isc
417 : :
418 : : #endif // __TCP_SOCKET_H
|