// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. #ifndef __UDP_SOCKET_H #define __UDP_SOCKET_H 1 #ifndef ASIO_HPP #error "asio.hpp must be included before including this, see asiolink.h as to why" #endif #include #include #include #include // for some IPC/network system calls #include #include #include #include #include #include namespace asiolink { /// \brief The \c UDPSocket class is a concrete derived class of \c IOAsioSocket /// that represents a UDP socket. /// /// \param C Callback type template class UDPSocket : public IOAsioSocket { private: /// \brief Class is non-copyable UDPSocket(const UDPSocket&); UDPSocket& operator=(const UDPSocket&); public: enum { MIN_SIZE = 4096 // Minimum send and receive size }; /// \brief Constructor from an ASIO UDP socket. /// /// \param socket The ASIO representation of the UDP socket. It is assumed /// that the caller will open and close the socket, so these /// operations are a no-op for that socket. UDPSocket(asio::ip::udp::socket& socket); /// \brief Constructor /// /// Used when the UDPSocket is being asked to manage its own internal /// socket. In this case, the open() and close() methods are used. /// /// \param service I/O Service object used to manage the socket. UDPSocket(IOService& service); /// \brief Destructor virtual ~UDPSocket(); /// \brief Return file descriptor of underlying socket virtual int getNative() const { return (socket_.native()); } /// \brief Return protocol of socket virtual int getProtocol() const { return (IPPROTO_UDP); } /// \brief Is "open()" synchronous? /// /// Indicates that the opening of a UDP socket is synchronous. virtual bool isOpenSynchronous() const { return true; } /// \brief Open Socket /// /// Opens the UDP socket. This is a synchronous operation. /// /// \param endpoint Endpoint to which the socket will send data. This is /// used to determine the address family trhat should be used for the /// underlying socket. /// \param callback Unused as the operation is synchronous. virtual void open(const IOEndpoint* endpoint, C& callback); /// \brief Send Asynchronously /// /// Calls the underlying socket's async_send_to() method to send a packet of /// data asynchronously to the remote endpoint. The callback will be called /// on completion. /// /// \param data Data to send /// \param length Length of data to send /// \param endpoint Target of the send /// \param callback Callback object. virtual void asyncSend(const void* data, size_t length, const IOEndpoint* endpoint, C& callback); /// \brief Receive Asynchronously /// /// Calls the underlying socket's async_receive_from() method to read a /// packet of data from a remote endpoint. Arrival of the data is signalled /// via a call to the callback function. /// /// \param data Buffer to receive incoming message /// \param length Length of the data buffer /// \param offset Offset into buffer where data is to be put /// \param endpoint Source of the communication /// \param callback Callback object virtual void asyncReceive(void* data, size_t length, size_t offset, IOEndpoint* endpoint, C& callback); /// \brief Checks if the data received is complete. /// /// For a UDP socket all the data is received in one I/O, so this is /// effectively a no-op (although it does update the amount of data /// received). /// /// \param data Data buffer containing data to date (ignored) /// \param length Amount of data in the buffer. /// /// \return Always true virtual bool receiveComplete(const void*, size_t) { return (true); } /// \brief Append Normalized Data /// /// When a UDP buffer is received, the entire buffer contains the data. /// When a TCP buffer is received, the first two bytes of the buffer hold /// a length count. This method removes those bytes from the buffer. /// /// \param inbuf Input buffer. This contains the data received over the /// network connection. /// \param length Amount of data in the input buffer. If TCP, this includes /// the two-byte count field. /// \param outbuf Pointer to output buffer to which the data will be /// appended virtual void appendNormalizedData(const void* inbuf, size_t length, isc::dns::OutputBufferPtr outbuf) { outbuf->writeData(inbuf, length); } /// \brief Cancel I/O On Socket virtual void cancel(); /// \brief Close socket virtual void close(); private: // Two variables to hold the socket - a socket and a pointer to it. This // handles the case where a socket is passed to the UDPSocket on // construction, or where it is asked to manage its own socket. asio::ip::udp::socket* socket_ptr_; ///< Pointer to own socket asio::ip::udp::socket& socket_; ///< Socket bool isopen_; ///< true when socket is open }; // Constructor - caller manages socket template UDPSocket::UDPSocket(asio::ip::udp::socket& socket) : socket_ptr_(NULL), socket_(socket), isopen_(true) { } // Constructor - create socket on the fly template UDPSocket::UDPSocket(IOService& service) : socket_ptr_(new asio::ip::udp::socket(service.get_io_service())), socket_(*socket_ptr_), isopen_(false) { } // Destructor. Only delete the socket if we are managing it. template UDPSocket::~UDPSocket() { delete socket_ptr_; } // Open the socket. template void UDPSocket::open(const IOEndpoint* endpoint, C&) { // Ignore opens on already-open socket. (Don't throw a failure because // of uncertainties as to what precedes whan when using asynchronous I/O.) // It also allows us a treat a passed-in socket in exactly the same way as // a self-managed socket (in that we can call the open() and close() methods // of this class). if (!isopen_) { if (endpoint->getFamily() == AF_INET) { socket_.open(asio::ip::udp::v4()); } else { socket_.open(asio::ip::udp::v6()); } isopen_ = true; // Ensure it can send and receive at least 4K buffers. asio::ip::udp::socket::send_buffer_size snd_size; socket_.get_option(snd_size); if (snd_size.value() < MIN_SIZE) { snd_size = MIN_SIZE; socket_.set_option(snd_size); } asio::ip::udp::socket::receive_buffer_size rcv_size; socket_.get_option(rcv_size); if (rcv_size.value() < MIN_SIZE) { rcv_size = MIN_SIZE; socket_.set_option(rcv_size); } } } // Send a message. Should never do this if the socket is not open, so throw // an exception if this is the case. template void UDPSocket::asyncSend(const void* data, size_t length, const IOEndpoint* endpoint, C& callback) { if (isopen_) { // Upconvert to a UDPEndpoint. We need to do this because although // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it // does not contain a method for getting at the underlying endpoint // type - that is in the derived class and the two classes differ on // return type. assert(endpoint->getProtocol() == IPPROTO_UDP); const UDPEndpoint* udp_endpoint = static_cast(endpoint); // ... and send the message. socket_.async_send_to(asio::buffer(data, length), udp_endpoint->getASIOEndpoint(), callback); } else { isc_throw(SocketNotOpen, "attempt to send on a UDP socket that is not open"); } } // Receive a message. Should never do this if the socket is not open, so throw // an exception if this is the case. template void UDPSocket::asyncReceive(void* data, size_t length, size_t offset, IOEndpoint* endpoint, C& callback) { if (isopen_) { // Upconvert the endpoint again. assert(endpoint->getProtocol() == IPPROTO_UDP); UDPEndpoint* udp_endpoint = static_cast(endpoint); // Ensure we can write into the buffer if (offset >= length) { isc_throw(BufferOverflow, "attempt to read into area beyond end of " "UDP receive buffer"); } void* buffer_start = static_cast(static_cast(data) + offset); // Issue the read socket_.async_receive_from(asio::buffer(buffer_start, length - offset), udp_endpoint->getASIOEndpoint(), callback); } else { isc_throw(SocketNotOpen, "attempt to receive from a UDP socket that is not open"); } } // Cancel I/O on the socket. No-op if the socket is not open. template void UDPSocket::cancel() { if (isopen_) { socket_.cancel(); } } // Close the socket down. Can only do this if the socket is open and we are // managing it ourself. template void UDPSocket::close() { if (isopen_ && socket_ptr_) { socket_.close(); isopen_ = false; } } } // namespace asiolink #endif // __UDP_SOCKET_H