LCOV - code coverage report
Current view: top level - eventdispatcher - udp_server.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 100 1.0 %
Date: 2021-09-19 09:06:58 Functions: 2 8 25.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2012-2021  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/eventdispatcher
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software; you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation; either version 2 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License
      17             : // along with this program; if not, write to the Free Software
      18             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      19             : 
      20             : /** \file
      21             :  * \brief Event dispatch class.
      22             :  *
      23             :  * Class used to handle events.
      24             :  */
      25             : 
      26             : // to get the POLLRDHUP definition
      27             : #ifndef _GNU_SOURCE
      28             : #define _GNU_SOURCE
      29             : #endif
      30             : 
      31             : 
      32             : // self
      33             : //
      34             : #include    "eventdispatcher/udp_server.h"
      35             : 
      36             : #include    "eventdispatcher/exception.h"
      37             : 
      38             : 
      39             : // snapwebsites lib
      40             : //
      41             : #include    <snaplogger/message.h>
      42             : 
      43             : 
      44             : // C lib
      45             : //
      46             : #include    <arpa/inet.h>
      47             : #include    <poll.h>
      48             : #include    <string.h>
      49             : 
      50             : 
      51             : // last include
      52             : //
      53             : #include    <snapdev/poison.h>
      54             : 
      55             : 
      56             : 
      57             : 
      58             : namespace ed
      59             : {
      60             : 
      61             : 
      62             : 
      63             : /** \brief Initialize a UDP server object.
      64             :  *
      65             :  * This function initializes a UDP server object making it ready to
      66             :  * receive messages.
      67             :  *
      68             :  * The server address and port are specified in the constructor so
      69             :  * if you need to receive messages from several different addresses
      70             :  * and/or port, you'll have to create a server for each.
      71             :  *
      72             :  * The address is a string and it can represent an IPv4 or IPv6
      73             :  * address.
      74             :  *
      75             :  * Note that this function calls bind() to listen to the socket
      76             :  * at the specified address. To accept data on different UDP addresses
      77             :  * and ports, multiple UDP servers must be created.
      78             :  *
      79             :  * \note
      80             :  * The socket is open in this process. If you fork() or exec() then the
      81             :  * socket will be closed by the operating system.
      82             :  *
      83             :  * \warning
      84             :  * We only make use of the first address found by getaddrinfo(). All
      85             :  * the other addresses are ignored.
      86             :  *
      87             :  * \warning
      88             :  * Remember that the multicast feature under Linux is shared by all
      89             :  * processes running on that server. Any one process can listen for
      90             :  * any and all multicast messages from any other process. Our
      91             :  * implementation limits the multicast from a specific IP. However.
      92             :  * other processes can also receive your packets and there is nothing
      93             :  * you can do to prevent that.
      94             :  *
      95             :  * \exception udp_client_server_runtime_error
      96             :  * The udp_client_server_runtime_error exception is raised when the address
      97             :  * and port combination cannot be resolved or if the socket cannot be
      98             :  * opened.
      99             :  *
     100             :  * \param[in] addr  The address we receive on.
     101             :  * \param[in] port  The port we receive from.
     102             :  * \param[in] family  The family used to search for 'addr'.
     103             :  * \param[in] multicast_addr  A multicast address.
     104             :  */
     105           0 : udp_server::udp_server(std::string const & addr, int port, int family, std::string const * multicast_addr)
     106           0 :     : udp_base(addr, port, family)
     107             : {
     108           0 :     int r(0);
     109           0 :     raii_addrinfo_t multicast_addrinfo;
     110             : 
     111           0 :     std::stringstream decimal_port;
     112           0 :     decimal_port << f_port;
     113           0 :     std::string port_str(decimal_port.str());
     114             : 
     115           0 :     if(multicast_addr != nullptr)
     116             :     {
     117             :         // in multicast we have to bind to the multicast IP (or IN_ANYADDR
     118             :         // which right now we do not support)
     119             :         //
     120           0 :         addrinfo hints = addrinfo();
     121           0 :         hints.ai_family = AF_UNSPEC;
     122           0 :         hints.ai_socktype = SOCK_DGRAM;
     123           0 :         hints.ai_protocol = IPPROTO_UDP;
     124             : 
     125             :         // we use the multicast address, but the same port as for
     126             :         // the other address
     127             :         //
     128           0 :         addrinfo * a(nullptr);
     129           0 :         r = getaddrinfo(multicast_addr->c_str(), port_str.c_str(), &hints, &a);
     130           0 :         if(r != 0 || a == nullptr)
     131             :         {
     132             :             throw event_dispatcher_runtime_error(
     133             :                       "invalid address or port for UDP multicast socket: \""
     134           0 :                     + *multicast_addr
     135           0 :                     + "\"");
     136             :         }
     137           0 :         multicast_addrinfo = raii_addrinfo_t(a);
     138             : 
     139           0 :         if(multicast_addrinfo->ai_family != AF_INET
     140           0 :         || f_addrinfo->ai_family != AF_INET)
     141             :         {
     142             :             throw event_dispatcher_runtime_error(
     143             :                       "the UDP multicast implementation only supports IPv4 at the moment: \""
     144           0 :                     + *multicast_addr
     145           0 :                     + "\"");
     146             :         }
     147             : 
     148           0 :         r = bind(f_socket.get(), multicast_addrinfo->ai_addr, multicast_addrinfo->ai_addrlen);
     149             :     }
     150             :     else
     151             :     {
     152             :         // bind to the very first address
     153             :         //
     154           0 :         r = bind(f_socket.get(), f_addrinfo->ai_addr, f_addrinfo->ai_addrlen);
     155             :     }
     156             : 
     157           0 :     if(r != 0)
     158             :     {
     159           0 :         int const e(errno);
     160             : 
     161             :         // reverse the address from the f_addrinfo so we know exactly
     162             :         // which one was picked
     163             :         //
     164           0 :         char addr_buf[256];
     165           0 :         switch(f_addrinfo->ai_family)
     166             :         {
     167           0 :         case AF_INET:
     168           0 :             inet_ntop(AF_INET
     169           0 :                     , &reinterpret_cast<struct sockaddr_in *>(f_addrinfo->ai_addr)->sin_addr
     170             :                     , addr_buf
     171             :                     , sizeof(addr_buf));
     172           0 :             break;
     173             : 
     174           0 :         case AF_INET6:
     175           0 :             inet_ntop(AF_INET6
     176           0 :                     , &reinterpret_cast<struct sockaddr_in6 *>(f_addrinfo->ai_addr)->sin6_addr
     177             :                     , addr_buf
     178             :                     , sizeof(addr_buf));
     179           0 :             break;
     180             : 
     181           0 :         default:
     182           0 :             strncpy(addr_buf, "Unknown Address Family", sizeof(addr_buf));
     183           0 :             break;
     184             : 
     185             :         }
     186             : 
     187           0 :         SNAP_LOG_ERROR
     188           0 :                 << "the bind() function failed with errno: "
     189             :                 << e
     190             :                 << " ("
     191           0 :                 << strerror(e)
     192           0 :                 << "); address length "
     193           0 :                 << f_addrinfo->ai_addrlen
     194             :                 << " and address is \""
     195             :                 << addr_buf
     196             :                 << "\""
     197             :                 << SNAP_LOG_SEND;
     198           0 :         throw event_dispatcher_runtime_error("could not bind UDP socket to \"" + f_addr + ":" + std::to_string(port) + "\"");
     199             :     }
     200             : 
     201             :     // are we creating a server to listen to multicast packets?
     202             :     //
     203           0 :     if(multicast_addrinfo != nullptr)
     204             :     {
     205           0 :         ip_mreqn mreq = {};
     206             : 
     207             :         // both addresses must have the right size
     208             :         //
     209           0 :         if(multicast_addrinfo->ai_addrlen <= sizeof(mreq.imr_multiaddr)
     210           0 :         || f_addrinfo->ai_addrlen <= sizeof(mreq.imr_address))
     211             :         {
     212             :             throw event_dispatcher_runtime_error(
     213             :                       "invalid address type for UDP multicast: \""
     214           0 :                     + addr + ":" + port_str
     215           0 :                     + "\" or \""
     216           0 :                     + *multicast_addr + ":" + port_str + "\" (sizes: "
     217           0 :                     + std::to_string(multicast_addrinfo->ai_addrlen) + ", "
     218           0 :                     + std::to_string(f_addrinfo->ai_addrlen) + ", "
     219           0 :                     + std::to_string(sizeof(mreq.imr_address)) + ")");
     220             :         }
     221             : 
     222           0 :         memcpy(&mreq.imr_multiaddr, &reinterpret_cast<sockaddr_in *>(multicast_addrinfo->ai_addr)->sin_addr, sizeof(mreq.imr_multiaddr));
     223           0 :         memcpy(&mreq.imr_address, &reinterpret_cast<sockaddr_in *>(f_addrinfo->ai_addr)->sin_addr, sizeof(mreq.imr_address));
     224           0 :         mreq.imr_ifindex = 0;   // no specific interface
     225             : 
     226           0 :         r = setsockopt(f_socket.get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
     227           0 :         if(r < 0)
     228             :         {
     229           0 :             int const e(errno);
     230             :             throw event_dispatcher_runtime_error(
     231             :                       "IP_ADD_MEMBERSHIP failed for: \""
     232           0 :                     + addr + ":" + port_str
     233           0 :                     + "\" or \"" + *multicast_addr + ":" + port_str
     234           0 :                     + "\", errno: "
     235           0 :                     + std::to_string(e) + ", " + strerror(e));
     236             :         }
     237             : 
     238             :         // setup the multicast to 0 so we don't receive other's
     239             :         // messages; apparently the default would be 1
     240             :         //
     241           0 :         int multicast_all(0);
     242           0 :         r = setsockopt(f_socket.get(), IPPROTO_IP, IP_MULTICAST_ALL, &multicast_all, sizeof(multicast_all));
     243           0 :         if(r < 0)
     244             :         {
     245             :             // things should still work if the IP_MULTICAST_ALL is not
     246             :             // set as we want it
     247             :             //
     248           0 :             int const e(errno);
     249           0 :             SNAP_LOG_WARNING
     250           0 :                     << "could not set IP_MULTICAST_ALL to zero, e = "
     251             :                     << e
     252             :                     << " -- "
     253           0 :                     << strerror(e)
     254             :                     << SNAP_LOG_SEND;
     255             :         }
     256             :     }
     257           0 : }
     258             : 
     259             : 
     260             : /** \brief Clean up the UDP server.
     261             :  *
     262             :  * This function frees the address info structures and close the socket.
     263             :  */
     264           0 : udp_server::~udp_server()
     265             : {
     266           0 : }
     267             : 
     268             : 
     269             : /** \brief Wait on a message.
     270             :  *
     271             :  * This function waits until a message is received on this UDP server.
     272             :  * There are no means to return from this function except by receiving
     273             :  * a message. Remember that UDP does not have a connect state so whether
     274             :  * another process quits does not change the status of this UDP server
     275             :  * and thus it continues to wait forever.
     276             :  *
     277             :  * Note that you may change the type of socket by making it non-blocking
     278             :  * (use the get_socket() to retrieve the socket identifier) in which
     279             :  * case this function will not block if no message is available. Instead
     280             :  * it returns immediately.
     281             :  *
     282             :  * \param[in] msg  The buffer where the message is saved.
     283             :  * \param[in] max_size  The maximum size the message (i.e. size of the \p msg buffer.)
     284             :  *
     285             :  * \return The number of bytes read or -1 if an error occurs.
     286             :  */
     287           0 : int udp_server::recv(char * msg, size_t max_size)
     288             : {
     289           0 :     return static_cast<int>(::recv(f_socket.get(), msg, max_size, 0));
     290             : }
     291             : 
     292             : 
     293             : /** \brief Wait for data to come in.
     294             :  *
     295             :  * This function waits for a given amount of time for data to come in. If
     296             :  * no data comes in after max_wait_ms, the function returns with -1 and
     297             :  * errno set to EAGAIN.
     298             :  *
     299             :  * The socket is expected to be a blocking socket (the default,) although
     300             :  * it is possible to setup the socket as non-blocking if necessary for
     301             :  * some other reason.
     302             :  *
     303             :  * This function blocks for a maximum amount of time as defined by
     304             :  * max_wait_ms. It may return sooner with an error or a message.
     305             :  *
     306             :  * \param[in] msg  The buffer where the message will be saved.
     307             :  * \param[in] max_size  The size of the \p msg buffer in bytes.
     308             :  * \param[in] max_wait_ms  The maximum number of milliseconds to wait for a message.
     309             :  *
     310             :  * \return -1 if an error occurs or the function timed out, the number of bytes received otherwise.
     311             :  */
     312           0 : int udp_server::timed_recv(char * msg, size_t const max_size, int const max_wait_ms)
     313             : {
     314           0 :     pollfd fd;
     315           0 :     fd.events = POLLIN | POLLPRI | POLLRDHUP;
     316           0 :     fd.fd = f_socket.get();
     317           0 :     int const retval(poll(&fd, 1, max_wait_ms));
     318             : 
     319             : //    fd_set s;
     320             : //    FD_ZERO(&s);
     321             : //#pragma GCC diagnostic push
     322             : //#pragma GCC diagnostic ignored "-Wold-style-cast"
     323             : //    FD_SET(f_socket.get(), &s);
     324             : //#pragma GCC diagnostic pop
     325             : //    struct timeval timeout;
     326             : //    timeout.tv_sec = max_wait_ms / 1000;
     327             : //    timeout.tv_usec = (max_wait_ms % 1000) * 1000;
     328             : //    int const retval(select(f_socket.get() + 1, &s, nullptr, &s, &timeout));
     329           0 :     if(retval == -1)
     330             :     {
     331             :         // poll() sets errno accordingly
     332           0 :         return -1;
     333             :     }
     334           0 :     if(retval > 0)
     335             :     {
     336             :         // our socket has data
     337           0 :         return static_cast<int>(::recv(f_socket.get(), msg, max_size, 0));
     338             :     }
     339             : 
     340             :     // our socket has no data
     341           0 :     errno = EAGAIN;
     342           0 :     return -1;
     343             : }
     344             : 
     345             : 
     346             : /** \brief Wait for data to come in, but return a std::string.
     347             :  *
     348             :  * This function waits for a given amount of time for data to come in. If
     349             :  * no data comes in after max_wait_ms, the function returns with -1 and
     350             :  * errno set to EAGAIN.
     351             :  *
     352             :  * The socket is expected to be a blocking socket (the default,) although
     353             :  * it is possible to setup the socket as non-blocking if necessary for
     354             :  * some other reason.
     355             :  *
     356             :  * This function blocks for a maximum amount of time as defined by
     357             :  * max_wait_ms. It may return sooner with an error or a message.
     358             :  *
     359             :  * \param[in] bufsize  The maximum size of the returned string in bytes.
     360             :  * \param[in] max_wait_ms  The maximum number of milliseconds to wait for a message.
     361             :  *
     362             :  * \return The received string or an empty string if not data received or error.
     363             :  *
     364             :  * \sa timed_recv()
     365             :  */
     366           0 : std::string udp_server::timed_recv(int const bufsize, int const max_wait_ms)
     367             : {
     368           0 :     std::vector<char> buf;
     369           0 :     buf.resize(bufsize + 1, '\0'); // +1 for ending \0
     370           0 :     int const r(timed_recv(&buf[0], bufsize, max_wait_ms));
     371           0 :     if(r <= -1)
     372             :     {
     373             :         // Timed out, so return empty string.
     374             :         // TBD: could std::string() smash errno?
     375             :         //
     376           0 :         return std::string();
     377             :     }
     378             : 
     379             :     // Resize the buffer, then convert to std string
     380             :     //
     381           0 :     buf.resize(r + 1, '\0');
     382             : 
     383           0 :     std::string word;
     384           0 :     word.resize(r);
     385           0 :     std::copy(buf.begin(), buf.end(), word.begin());
     386             : 
     387           0 :     return word;
     388             : }
     389             : 
     390             : 
     391             : 
     392             : 
     393           6 : } // namespace ed
     394             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13