LCOV - code coverage report
Current view: top level - eventdispatcher - tcp_client.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 98 1.0 %
Date: 2019-08-08 02:52:36 Functions: 2 12 16.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Event Dispatcher
       2             : // Copyright (c) 2012-2019  Made to Order Software Corp.  All Rights Reserved
       3             : //
       4             : // This program is free software; you can redistribute it and/or modify
       5             : // it under the terms of the GNU General Public License as published by
       6             : // the Free Software Foundation; either version 2 of the License, or
       7             : // (at your option) any later version.
       8             : //
       9             : // This program is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             : // GNU General Public License for more details.
      13             : //
      14             : // You should have received a copy of the GNU General Public License
      15             : // along with this program; if not, write to the Free Software
      16             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      17             : 
      18             : 
      19             : // make sure we use OpenSSL with multi-thread support
      20             : // (TODO: move to .cpp once we have the impl!)
      21             : #define OPENSSL_THREAD_DEFINES
      22             : 
      23             : 
      24             : // self
      25             : //
      26             : #include    "eventdispatcher/tcp_client.h"
      27             : 
      28             : #include    "eventdispatcher/exception.h"
      29             : #include    "eventdispatcher/utils.h"
      30             : 
      31             : 
      32             : // addr lib
      33             : //
      34             : //#include "libaddr/addr.h"
      35             : 
      36             : 
      37             : // snaplogger lib
      38             : //
      39             : #include "snaplogger/message.h"
      40             : 
      41             : 
      42             : //// snapdev lib
      43             : ////
      44             : //#include "snapdev/not_reached.h"
      45             : //#include "snapdev/not_used.h"
      46             : //#include "snapdev/raii_generic_deleter.h"
      47             : //
      48             : //
      49             : //// C++
      50             : ////
      51             : //#include <sstream>
      52             : //#include <iomanip>
      53             : //
      54             : //
      55             : //// C lib
      56             : ////
      57             : #include <netdb.h>
      58             : #include <arpa/inet.h>
      59             : //#include <poll.h>
      60             : #include <string.h>
      61             : //#include <sys/ioctl.h>
      62             : //#include <sys/socket.h>
      63             : //#include <sys/types.h>
      64             : //#include <unistd.h>
      65             : 
      66             : 
      67             : // last include
      68             : //
      69             : #include "snapdev/poison.h"
      70             : 
      71             : 
      72             : 
      73             : 
      74             : //#ifndef OPENSSL_THREADS
      75             : //#error "OPENSSL_THREADS is not defined. Snap! requires support for multiple threads in OpenSSL."
      76             : //#endif
      77             : 
      78             : namespace ed
      79             : {
      80             : 
      81             : 
      82             : 
      83             : /** \class tcp_client
      84             :  * \brief Create a client socket and connect to a server.
      85             :  *
      86             :  * This class is a client socket implementation used to connect to a server.
      87             :  * The server is expected to be running at the time the client is created
      88             :  * otherwise it fails connecting.
      89             :  *
      90             :  * This class is not appropriate to connect to a server that may come and go
      91             :  * over time.
      92             :  */
      93             : 
      94             : /** \brief Contruct a tcp_client object.
      95             :  *
      96             :  * The tcp_client constructor initializes a TCP client object by connecting
      97             :  * to the specified server. The server is defined with the \p addr and
      98             :  * \p port specified as parameters.
      99             :  *
     100             :  * \exception tcp_client_server_parameter_error
     101             :  * This exception is raised if the \p port parameter is out of range or the
     102             :  * IP address is an empty string or otherwise an invalid address.
     103             :  *
     104             :  * \exception tcp_client_server_runtime_error
     105             :  * This exception is raised if the client cannot create the socket or it
     106             :  * cannot connect to the server.
     107             :  *
     108             :  * \param[in] addr  The address of the server to connect to. It must be valid.
     109             :  * \param[in] port  The port the server is listening on.
     110             :  */
     111           0 : tcp_client::tcp_client(std::string const & addr, int port)
     112             :     : f_socket(-1)
     113             :     , f_port(port)
     114           0 :     , f_addr(addr)
     115             : {
     116           0 :     if(f_port < 0 || f_port >= 65536)
     117             :     {
     118           0 :         throw event_dispatcher_invalid_parameter("invalid port for a client socket");
     119             :     }
     120           0 :     if(f_addr.empty())
     121             :     {
     122           0 :         throw event_dispatcher_invalid_parameter("an empty address is not valid for a client socket");
     123             :     }
     124             : 
     125             :     addrinfo hints;
     126           0 :     memset(&hints, 0, sizeof(hints));
     127           0 :     hints.ai_family = AF_UNSPEC;
     128           0 :     hints.ai_socktype = SOCK_STREAM;
     129           0 :     hints.ai_protocol = IPPROTO_TCP;
     130           0 :     std::string const port_str(std::to_string(f_port));
     131           0 :     addrinfo * addrinfo(nullptr);
     132           0 :     int const r(getaddrinfo(addr.c_str(), port_str.c_str(), &hints, &addrinfo));
     133           0 :     raii_addrinfo_t addr_info(addrinfo);
     134           0 :     if(r != 0
     135           0 :     || addrinfo == nullptr)
     136             :     {
     137           0 :         int const e(errno);
     138           0 :         std::string err("getaddrinfo() failed to parse the address or port \"");
     139           0 :         err += addr;
     140           0 :         err += ":";
     141           0 :         err += port_str;
     142           0 :         err += "\" strings (errno: ";
     143           0 :         err += std::to_string(e);
     144           0 :         err += " -- ";
     145           0 :         err += strerror(e);
     146           0 :         err += ")";
     147           0 :         SNAP_LOG_FATAL << err;
     148           0 :         throw event_dispatcher_runtime_error(err);
     149             :     }
     150             : 
     151           0 :     f_socket = socket(addr_info.get()->ai_family, SOCK_STREAM, IPPROTO_TCP);
     152           0 :     if(f_socket < 0)
     153             :     {
     154           0 :         int const e(errno);
     155             :         SNAP_LOG_FATAL
     156           0 :             << "socket() failed to create a socket descriptor (errno: "
     157           0 :             << e
     158           0 :             << " -- "
     159           0 :             << strerror(e)
     160           0 :             << ")";
     161           0 :         throw event_dispatcher_runtime_error("could not create socket for client");
     162             :     }
     163             : 
     164           0 :     if(connect(f_socket, addr_info.get()->ai_addr, addr_info.get()->ai_addrlen) < 0)
     165             :     {
     166           0 :         int const e(errno);
     167             :         SNAP_LOG_FATAL
     168           0 :             << "connect() failed to connect a socket (errno: "
     169           0 :             << e
     170           0 :             << " -- "
     171           0 :             << strerror(e)
     172           0 :             << ")";
     173           0 :         close(f_socket);
     174           0 :         throw event_dispatcher_runtime_error("could not connect client socket to \"" + f_addr + "\"");
     175             :     }
     176           0 : }
     177             : 
     178             : /** \brief Clean up the TCP client object.
     179             :  *
     180             :  * This function cleans up the TCP client object by closing the attached socket.
     181             :  *
     182             :  * \note
     183             :  * DO NOT use the shutdown() call since we may end up forking and using
     184             :  * that connection in the child.
     185             :  */
     186           0 : tcp_client::~tcp_client()
     187             : {
     188           0 :     close(f_socket);
     189           0 : }
     190             : 
     191             : /** \brief Get the socket descriptor.
     192             :  *
     193             :  * This function returns the TCP client socket descriptor. This can be
     194             :  * used to change the descriptor behavior (i.e. make it non-blocking for
     195             :  * example.)
     196             :  *
     197             :  * \return The socket descriptor.
     198             :  */
     199           0 : int tcp_client::get_socket() const
     200             : {
     201           0 :     return f_socket;
     202             : }
     203             : 
     204             : /** \brief Get the TCP client port.
     205             :  *
     206             :  * This function returns the port used when creating the TCP client.
     207             :  * Note that this is the port the server is listening to and not the port
     208             :  * the TCP client is currently connected to.
     209             :  *
     210             :  * \return The TCP client port.
     211             :  */
     212           0 : int tcp_client::get_port() const
     213             : {
     214           0 :     return f_port;
     215             : }
     216             : 
     217             : /** \brief Get the TCP server address.
     218             :  *
     219             :  * This function returns the address used when creating the TCP address as is.
     220             :  * Note that this is the address of the server where the client is connected
     221             :  * and not the address where the client is running (although it may be the
     222             :  * same.)
     223             :  *
     224             :  * Use the get_client_addr() function to retrieve the client's TCP address.
     225             :  *
     226             :  * \return The TCP client address.
     227             :  */
     228           0 : std::string tcp_client::get_addr() const
     229             : {
     230           0 :     return f_addr;
     231             : }
     232             : 
     233             : /** \brief Get the TCP client port.
     234             :  *
     235             :  * This function retrieve the port of the client (used on your computer).
     236             :  * This is retrieved from the socket using the getsockname() function.
     237             :  *
     238             :  * \return The port or -1 if it cannot be determined.
     239             :  */
     240           0 : int tcp_client::get_client_port() const
     241             : {
     242             :     struct sockaddr addr;
     243           0 :     socklen_t len(sizeof(addr));
     244           0 :     int r(getsockname(f_socket, &addr, &len));
     245           0 :     if(r != 0)
     246             :     {
     247           0 :         return -1;
     248             :     }
     249             :     // Note: I know the port is at the exact same location in both
     250             :     //       structures in Linux but it could change on other Unices
     251           0 :     if(addr.sa_family == AF_INET)
     252             :     {
     253             :         // IPv4
     254           0 :         return reinterpret_cast<sockaddr_in *>(&addr)->sin_port;
     255             :     }
     256           0 :     if(addr.sa_family == AF_INET6)
     257             :     {
     258             :         // IPv6
     259           0 :         return reinterpret_cast<sockaddr_in6 *>(&addr)->sin6_port;
     260             :     }
     261           0 :     return -1;
     262             : }
     263             : 
     264             : /** \brief Get the TCP client address.
     265             :  *
     266             :  * This function retrieve the IP address of the client (your computer).
     267             :  * This is retrieved from the socket using the getsockname() function.
     268             :  *
     269             :  * \return The IP address as a string.
     270             :  */
     271           0 : std::string tcp_client::get_client_addr() const
     272             : {
     273             :     struct sockaddr addr;
     274           0 :     socklen_t len(sizeof(addr));
     275           0 :     int const r(getsockname(f_socket, &addr, &len));
     276           0 :     if(r != 0)
     277             :     {
     278           0 :         throw event_dispatcher_runtime_error("address not available");
     279             :     }
     280             :     char buf[BUFSIZ];
     281           0 :     switch(addr.sa_family)
     282             :     {
     283             :     case AF_INET:
     284           0 :         if(len < sizeof(struct sockaddr_in))
     285             :         {
     286           0 :             throw event_dispatcher_runtime_error("address size incompatible (AF_INET)");
     287             :         }
     288           0 :         inet_ntop(AF_INET, &reinterpret_cast<struct sockaddr_in *>(&addr)->sin_addr, buf, sizeof(buf));
     289           0 :         break;
     290             : 
     291             :     case AF_INET6:
     292           0 :         if(len < sizeof(struct sockaddr_in6))
     293             :         {
     294           0 :             throw event_dispatcher_runtime_error("address size incompatible (AF_INET6)");
     295             :         }
     296           0 :         inet_ntop(AF_INET6, &reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_addr, buf, sizeof(buf));
     297           0 :         break;
     298             : 
     299             :     default:
     300           0 :         throw event_dispatcher_runtime_error("unknown address family");
     301             : 
     302             :     }
     303           0 :     return buf;
     304             : }
     305             : 
     306             : /** \brief Read data from the socket.
     307             :  *
     308             :  * A TCP socket is a stream type of socket and one can read data from it
     309             :  * as if it were a regular file. This function reads \p size bytes and
     310             :  * returns. The function returns early if the server closes the connection.
     311             :  *
     312             :  * If your socket is blocking, \p size should be exactly what you are
     313             :  * expecting or this function will block forever or until the server
     314             :  * closes the connection.
     315             :  *
     316             :  * The function returns -1 if an error occurs. The error is available in
     317             :  * errno as expected in the POSIX interface.
     318             :  *
     319             :  * \param[in,out] buf  The buffer where the data is read.
     320             :  * \param[in] size  The size of the buffer.
     321             :  *
     322             :  * \return The number of bytes read from the socket, or -1 on errors.
     323             :  */
     324           0 : int tcp_client::read(char *buf, size_t size)
     325             : {
     326           0 :     return static_cast<int>(::read(f_socket, buf, size));
     327             : }
     328             : 
     329             : 
     330             : /** \brief Read one line.
     331             :  *
     332             :  * This function reads one line from the current location up to the next
     333             :  * '\\n' character. We do not have any special handling of the '\\r'
     334             :  * character.
     335             :  *
     336             :  * The function may return 0 in which case the server closed the connection.
     337             :  *
     338             :  * \param[out] line  The resulting line read from the server.
     339             :  *
     340             :  * \return The number of bytes read from the socket, or -1 on errors.
     341             :  *         If the function returns 0 or more, then the \p line parameter
     342             :  *         represents the characters read on the network.
     343             :  */
     344           0 : int tcp_client::read_line(std::string& line)
     345             : {
     346           0 :     line.clear();
     347           0 :     int len(0);
     348           0 :     for(;;)
     349             :     {
     350             :         char c;
     351           0 :         int r(read(&c, sizeof(c)));
     352           0 :         if(r <= 0)
     353             :         {
     354           0 :             return len == 0 && r < 0 ? -1 : len;
     355             :         }
     356           0 :         if(c == '\n')
     357             :         {
     358           0 :             return len;
     359             :         }
     360           0 :         ++len;
     361           0 :         line += c;
     362             :     }
     363             : }
     364             : 
     365             : 
     366             : /** \brief Write data to the socket.
     367             :  *
     368             :  * A TCP socket is a stream type of socket and one can write data to it
     369             :  * as if it were a regular file. This function writes \p size bytes to
     370             :  * the socket and then returns. This function returns early if the server
     371             :  * closes the connection.
     372             :  *
     373             :  * If your socket is not blocking, less than \p size bytes may be written
     374             :  * to the socket. In that case you are responsible for calling the function
     375             :  * again to write the remainder of the buffer until the function returns
     376             :  * a number of bytes written equal to \p size.
     377             :  *
     378             :  * The function returns -1 if an error occurs. The error is available in
     379             :  * errno as expected in the POSIX interface.
     380             :  *
     381             :  * \param[in] buf  The buffer with the data to send over the socket.
     382             :  * \param[in] size  The number of bytes in buffer to send over the socket.
     383             :  *
     384             :  * \return The number of bytes that were actually accepted by the socket
     385             :  * or -1 if an error occurs.
     386             :  */
     387           0 : int tcp_client::write(const char *buf, size_t size)
     388             : {
     389           0 :     return static_cast<int>(::write(f_socket, buf, size));
     390             : }
     391             : 
     392             : 
     393             : 
     394           6 : } // namespace tcp_client_server
     395             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.12