LCOV - code coverage report
Current view: top level - eventdispatcher - local_stream_server_connection.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 43 121 35.5 %
Date: 2024-03-16 21:58:34 Functions: 5 10 50.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2012-2024  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 along
      17             : // with this program; if not, write to the Free Software Foundation, Inc.,
      18             : // 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      19             : 
      20             : /** \file
      21             :  * \brief Handle an AF_UNIX socket as a server.
      22             :  *
      23             :  * This class is used to listen on a stream oriented connection using an
      24             :  * AF_UNIX type of socket.
      25             :  */
      26             : 
      27             : // self
      28             : //
      29             : #include    "eventdispatcher/local_stream_server_connection.h"
      30             : 
      31             : #include    "eventdispatcher/exception.h"
      32             : #include    "eventdispatcher/local_stream_client_connection.h"
      33             : 
      34             : 
      35             : // snapdev
      36             : //
      37             : #include    <snapdev/chownnm.h>
      38             : 
      39             : 
      40             : // snaplogger
      41             : //
      42             : #include    <snaplogger/message.h>
      43             : 
      44             : 
      45             : // C
      46             : //
      47             : #include    <fcntl.h>
      48             : #include    <sys/stat.h>
      49             : 
      50             : 
      51             : // last include
      52             : //
      53             : #include    <snapdev/poison.h>
      54             : 
      55             : 
      56             : 
      57             : namespace ed
      58             : {
      59             : 
      60             : 
      61             : 
      62             : /** \brief Initialize the local stream server.
      63             :  *
      64             :  * The server constructor creates a socket, binds it, and then listen on it.
      65             :  *
      66             :  * \todo
      67             :  * Fix docs.
      68             :  *
      69             :  * By default the server accepts a maximum of \p max_connections (set to
      70             :  * 0 or less to get the default tcp_server::MAX_CONNECTIONS) in its waiting queue.
      71             :  * If you use the server and expect a low connection rate, you may want to
      72             :  * reduce the count to 5. Although some very busy servers use larger numbers.
      73             :  * This value gets clamped to a minimum of 5 and a maximum of 1,000.
      74             :  *
      75             :  * Note that the maximum number of connections is actually limited to
      76             :  * /proc/sys/net/core/somaxconn connections. This number is generally 128 
      77             :  * in 2016. So the  super high limit of 1,000 is anyway going to be ignored
      78             :  * by the OS.
      79             :  *
      80             :  * The Unix socket is made non-reusable by default. It is possible to mark
      81             :  * the server address as reusable by setting the \p force_reuse_addr to true.
      82             :  * If not true, then the fact that the file exists prevents a new server from
      83             :  * being created. When true, the function first checks whether it can connect
      84             :  * to a server. It a connection succeeds, then the creation of the server
      85             :  * fails (i.e. the Unix socket is already in use). If the connection fails,
      86             :  * the function makes sure to delete the file if it exists.
      87             :  *
      88             :  * By default the server is marked as "keepalive". You can turn it off
      89             :  * using the keepalive() function with false.
      90             :  *
      91             :  * \note
      92             :  * The code here handles the file based sockets by attempting to delete
      93             :  * the file if it already exists. If you create services that could end
      94             :  * up using the exact same socket name, then it could be an issue. If
      95             :  * not then the code will do exactly what you expects. That being said.
      96             :  * If you want to increase your chance of proper clean up of a Unix
      97             :  * socket file, you would have to create a parent process which deletes
      98             :  * the file whenever the child dies. Then it can either restart the child
      99             :  * (on a crash) or just quit.
     100             :  *
     101             :  * \exception runtime_error
     102             :  * This exception is raised if the socket cannot be created, bound to
     103             :  * the specified Unix address, or listen() fails on the socket.
     104             :  *
     105             :  * \param[in] address  The Unix address to listen on.
     106             :  * \param[in] max_connections  The number of connections to keep in the listen queue.
     107             :  * \param[in] force_reuse_addr  Whether to allow the deletion of a file
     108             :  * before the bind() call.
     109             :  * \param[in] close_on_exec  Whether to close the server & client sockets
     110             :  * on an execve().
     111             :  */
     112           1 : local_stream_server_connection::local_stream_server_connection(
     113             :               addr::addr_unix const & address
     114             :             , int max_connections
     115             :             , bool force_reuse_addr
     116           1 :             , bool close_on_exec)
     117           1 :     : f_address(address)
     118           1 :     , f_max_connections(max_connections)
     119           1 :     , f_close_on_exec(close_on_exec)
     120             : {
     121           1 :     if(f_max_connections < 5)
     122             :     {
     123           0 :         f_max_connections = 5;
     124             :     }
     125           1 :     else if(f_max_connections > 1000)
     126             :     {
     127           0 :         f_max_connections = 1000;
     128             :     }
     129             : 
     130           1 :     sockaddr_un un;
     131           1 :     f_address.get_un(un);
     132           2 :     f_socket.reset(socket(
     133           1 :                   un.sun_family
     134             :                 , SOCK_STREAM | SOCK_NONBLOCK | (close_on_exec ? SOCK_CLOEXEC : 0)
     135             :                 , 0));
     136           1 :     if(f_socket == nullptr)
     137             :     {
     138           0 :         int const e(errno);
     139           0 :         SNAP_LOG_ERROR
     140             :             << "socket() failed creating a socket descriptor (errno: "
     141           0 :             << std::to_string(e)
     142             :             << " -- "
     143           0 :             << strerror(e)
     144             :             << "); cannot listen on address \""
     145           0 :             << f_address.to_uri()
     146             :             << "\"."
     147             :             << SNAP_LOG_SEND;
     148           0 :         throw runtime_error("could not create socket for AF_UNIX server");
     149             :     }
     150             : 
     151           1 :     if(f_address.is_unnamed())
     152             :     {
     153             :         // for an unnamed socket, we do not bind at all the user is
     154             :         // responsible for knowing where to read and where to write
     155             :         //
     156           0 :         return;
     157             :     }
     158             : 
     159           1 :     int r(-1);
     160           1 :     if(f_address.is_file())
     161             :     {
     162             :         // a Unix file socket must create a new socket file to prove unicity
     163             :         // if the file already exists, even if it isn't used, the bind() call
     164             :         // will fail; if the file exists and the force_reuse_addr is true this
     165             :         // this function attempts to delete the file if it is a socket and we
     166             :         // can't connect to it (i.e. "lost file")
     167             :         //
     168           1 :         struct stat st = {};
     169           1 :         if(stat(un.sun_path, &st) == 0)
     170             :         {
     171           0 :             if(!S_ISSOCK(st.st_mode))
     172             :             {
     173           0 :                 SNAP_LOG_ERROR
     174             :                     << "file \""
     175             :                     << un.sun_path
     176             :                     << "\" is not a socket; cannot listen on address \""
     177           0 :                     << f_address.to_uri()
     178             :                     << "\"."
     179             :                     << SNAP_LOG_SEND;
     180           0 :                 throw runtime_error("file already exists and it is not a socket, can't create an AF_UNIX server");
     181             :             }
     182             : 
     183           0 :             bool available(false);
     184           0 :             if(force_reuse_addr)
     185             :             {
     186           0 :                 SNAP_LOG_WARNING
     187             :                     << "attempting a contection to "
     188           0 :                     << f_address.to_uri()
     189             :                     << " as a client to see that the address is available for this server;"
     190             :                     << " on success this generates an expected fatal error which we catch here."
     191             :                     << SNAP_LOG_SEND;
     192             :                 try
     193             :                 {
     194             :                     // TODO: this generates a fatal error in the log which can
     195             :                     //       be disturbing... we could look at adding a tag on
     196             :                     //       that error and here mark the messages associated
     197             :                     //       with that tag as hidden so that way we do not get
     198             :                     //       the error output.
     199             :                     //
     200           0 :                     local_stream_client_connection test_connection(f_address);
     201           0 :                 }
     202           0 :                 catch(runtime_error const & e)
     203             :                 {
     204             :                     // note: in Linux we can distinguish between a full
     205             :                     //       backlog (EAGAIN) and a disconnected socket
     206             :                     //       (ECONNREFUSED); we should not set available
     207             :                     //       to true on EAGAIN...
     208             :                     //
     209           0 :                     available = true;
     210           0 :                 }
     211             :             }
     212           0 :             if(!available)
     213             :             {
     214           0 :                 SNAP_LOG_ERROR
     215             :                     << "file socket \""
     216             :                     << un.sun_path
     217             :                     << "\" already in use (errno: "
     218           0 :                     << std::to_string(EADDRINUSE)
     219             :                     << " -- "
     220           0 :                     << strerror(EADDRINUSE)
     221             :                     << "); cannot listen on address \""
     222           0 :                     << f_address.to_uri()
     223             :                     << "\"."
     224             :                     << SNAP_LOG_SEND;
     225           0 :                 throw runtime_error("socket already exists, can't create an AF_UNIX server");
     226             :             }
     227             : 
     228           0 :             r = f_address.unlink();
     229           0 :             if(r != 0
     230           0 :             && errno != ENOENT)
     231             :             {
     232           0 :                 SNAP_LOG_ERROR
     233             :                     << "not able to delete file socket \""
     234             :                     << un.sun_path
     235             :                     << "\"; socket already in use (errno: "
     236           0 :                     << std::to_string(EADDRINUSE)
     237             :                     << " -- "
     238           0 :                     << strerror(EADDRINUSE)
     239             :                     << "); cannot listen on address \""
     240           0 :                     << f_address.to_uri()
     241             :                     << "\"."
     242             :                     << SNAP_LOG_SEND;
     243           0 :                 throw runtime_error("could not unlink socket to reuse it as an AF_UNIX server");
     244             :             }
     245             :         }
     246             : 
     247             : #ifdef __linux__
     248             :         // before the bind() restrict the permissions as much as possible
     249             :         // for security reasons (only available under Linux apparently)
     250             :         //
     251           1 :         if(fchmod(f_socket.get(), S_IRUSR | S_IWUSR) != 0)
     252             :         {
     253           0 :             int const e(errno);
     254           0 :             SNAP_LOG_ERROR
     255           0 :                 << "fchmod() failed changing permissions before bind() (errno: "
     256             :                 << e
     257             :                 << " -- "
     258           0 :                 << strerror(e)
     259             :                 << ") on socket with address \""
     260           0 :                 << f_address.to_uri()
     261             :                 << "\"."
     262             :                 << SNAP_LOG_SEND;
     263           0 :             throw runtime_error("could not change socket permissions.");
     264             :         }
     265             : #endif
     266             : 
     267           2 :         r = bind(
     268           2 :                   f_socket.get()
     269             :                 , reinterpret_cast<sockaddr const *>(&un)
     270             :                 , sizeof(struct sockaddr_un));
     271             : 
     272           1 :         std::string const group(f_address.get_group());
     273           1 :         if(!group.empty())
     274             :         {
     275           0 :             if(snapdev::chownnm(un.sun_path, std::string(), group) != 0)
     276             :             {
     277           0 :                 int const e(errno);
     278           0 :                 SNAP_LOG_ERROR
     279             :                     << "not able to change group ownership of socket file \""
     280             :                     << un.sun_path
     281           0 :                     << "\" (errno: "
     282             :                     << e
     283             :                     << " -- "
     284           0 :                     << strerror(e)
     285             :                     << "); cannot listen on address \""
     286           0 :                     << f_address.to_uri()
     287             :                     << "\"."
     288             :                     << SNAP_LOG_SEND;
     289           0 :                 throw runtime_error("could not change group ownership on socket file.");
     290             :             }
     291             :         }
     292             : 
     293             :         // after the bind() we can then set the full permissions the way the
     294             :         // user wants them to be (also bind() applies the umask() so doing
     295             :         // that with the fchmod() above is likely to fail in many cases).
     296             :         //
     297             :         // note: we know that the path in un.snn_path is null terminated.
     298             :         //
     299           1 :         if(r == 0
     300           1 :         && chmod(un.sun_path, f_address.get_mode()) != 0)
     301             :         {
     302           0 :             int const e(errno);
     303           0 :             SNAP_LOG_ERROR
     304           0 :                 << "chmod() failed changing permissions after bind() (errno: "
     305             :                 << e
     306             :                 << " -- "
     307           0 :                 << strerror(e)
     308             :                 << ") on socket with address \""
     309           0 :                 << f_address.to_uri()
     310             :                 << "\"."
     311             :                 << SNAP_LOG_SEND;
     312           0 :             throw runtime_error("could not change socket permissions.");
     313             :         }
     314           1 :     }
     315             :     else
     316             :     {
     317             :         // we want to limit the size because otherwise it would include
     318             :         // the '\0's after the specified name
     319             :         //
     320           0 :         std::size_t const size(sizeof(un.sun_family)
     321             :                                         + 1 // for the '\0' in sun_path[0]
     322           0 :                                         + strlen(un.sun_path + 1));
     323           0 :         r = bind(
     324           0 :                   f_socket.get()
     325             :                 , reinterpret_cast<sockaddr const *>(&un)
     326             :                 , size);
     327             :     }
     328             : 
     329           1 :     if(r < 0)
     330             :     {
     331           0 :         throw runtime_error(
     332             :                   "could not bind the socket to \""
     333           0 :                 + f_address.to_uri()
     334           0 :                 + "\"");
     335             :     }
     336             : 
     337             :     // start listening, we expect the caller to then call accept() to
     338             :     // acquire connections
     339             :     //
     340           1 :     if(listen(f_socket.get(), f_max_connections) < 0)
     341             :     {
     342           0 :         throw runtime_error(
     343             :                   "could not listen to the socket bound to \""
     344           0 :                 + f_address.to_uri()
     345           0 :                 + "\"");
     346             :     }
     347           0 : }
     348             : 
     349             : 
     350             : /** \brief Clean up the server socket.
     351             :  *
     352             :  * This function deletes the socket file if this service used such a socket.
     353             :  *
     354             :  * \note
     355             :  * If the server crashes, that delete may not happen. In order to allow
     356             :  * for a restart, calling the constructor and setting the force_reuse_addr
     357             :  * to true is what will generally work best.
     358             :  */
     359           1 : local_stream_server_connection::~local_stream_server_connection()
     360             : {
     361           1 :     f_address.unlink();
     362           1 : }
     363             : 
     364             : 
     365             : /** \brief Check whether this connection is a listener.
     366             :  *
     367             :  * This function already returns true since a server is a listener.
     368             :  * This allows us to have our process_accept() function called instead
     369             :  * of the process_read().
     370             :  *
     371             :  * \return Always true since this is a listening server.
     372             :  */
     373          10 : bool local_stream_server_connection::is_listener() const
     374             : {
     375          10 :     return true;
     376             : }
     377             : 
     378             : 
     379             : /** \brief Retrieve the socket descriptor.
     380             :  *
     381             :  * This function returns the socket descriptor. It can be used to
     382             :  * tweak things on the socket such as making it non-blocking or
     383             :  * directly accessing the data.
     384             :  *
     385             :  * \return The socket descriptor.
     386             :  */
     387          19 : int local_stream_server_connection::get_socket() const
     388             : {
     389          19 :     return f_socket.get();
     390             : }
     391             : 
     392             : 
     393             : /** \brief Retrieve the maximum number of connections.
     394             :  *
     395             :  * This function returns the maximum number of connections that can
     396             :  * be accepted by the socket. This was set by the constructor and
     397             :  * it cannot be changed later.
     398             :  *
     399             :  * \return The maximum number of incoming connections.
     400             :  */
     401           0 : int local_stream_server_connection::get_max_connections() const
     402             : {
     403           0 :     return f_max_connections;
     404             : }
     405             : 
     406             : 
     407             : /** \brief Retrieve one new connection.
     408             :  *
     409             :  * This function will wait until a new connection arrives and returns a
     410             :  * new bio_client object for each new connection.
     411             :  *
     412             :  * If the socket is made non-blocking then the function may return without
     413             :  * a bio_client object (i.e. a null pointer instead.)
     414             :  *
     415             :  * \return A file descriptor representing the new connection socket.
     416             :  */
     417           1 : snapdev::raii_fd_t local_stream_server_connection::accept()
     418             : {
     419           1 :     struct sockaddr_un un;
     420           1 :     socklen_t len(sizeof(un));
     421             :     snapdev::raii_fd_t r(::accept(
     422           2 :               f_socket.get()
     423             :             , reinterpret_cast<sockaddr *>(&un)
     424           2 :             , &len));
     425           1 :     if(r == nullptr)
     426             :     {
     427           0 :         throw runtime_error("failed accepting a new AF_UNIX client");
     428             :     }
     429             : 
     430             :     // force a close on execve() to avoid sharing the socket in child
     431             :     // processes
     432             :     //
     433           1 :     if(f_close_on_exec)
     434             :     {
     435             :         // if this call fails, we ignore the error, but still log the event
     436             :         //
     437           1 :         if(fcntl(r.get(), F_SETFD, FD_CLOEXEC) != 0)
     438             :         {
     439           0 :             SNAP_LOG_WARNING
     440             :                 << "::accept(): an error occurred trying"
     441             :                    " to mark accepted AF_UNIX socket with FD_CLOEXEC."
     442             :                 << SNAP_LOG_SEND;
     443             :         }
     444             :     }
     445             : 
     446           2 :     return r;
     447           0 : }
     448             : 
     449             : 
     450             : /** \brief Return the current state of the close-on-exec flag.
     451             :  *
     452             :  * This function returns the current state of the close-on-exec flag. This
     453             :  * is the flag as defined in the contructor or by the set_close_on_exec()
     454             :  * function. It does not represent the status of the server socket nor
     455             :  * of the clients that were accept()'ed by this class.
     456             :  *
     457             :  * It will, however, be used whenever the accept() is called in the future.
     458             :  *
     459             :  * \return The current status of the close-on-exec flag.
     460             :  */
     461           0 : bool local_stream_server_connection::get_close_on_exec() const
     462             : {
     463           0 :     return f_close_on_exec;
     464             : }
     465             : 
     466             : 
     467             : /** \brief Change the close-on-exec flag for future accept() calls.
     468             :  *
     469             :  * This function allows you to change the close-on-exec flag after
     470             :  * you created a Unix server. This means you may say that the server
     471             :  * needs to be closed, but not the connections or vice versa.
     472             :  *
     473             :  * \param[in] yes  Whether the close-on-exec will be set on sockets
     474             :  * returned by the accept() function.
     475             :  */
     476           0 : void local_stream_server_connection::set_close_on_exec(bool yes)
     477             : {
     478           0 :     f_close_on_exec = yes;
     479           0 : }
     480             : 
     481             : 
     482             : /** \brief Retrieve the server IP address.
     483             :  *
     484             :  * This function returns the IP address used to bind the socket. This
     485             :  * is the address clients have to use to connect to the server unless
     486             :  * the address was set to all zeroes (0.0.0.0) in which case any user
     487             :  * can connect.
     488             :  *
     489             :  * The IP address cannot be changed.
     490             :  *
     491             :  * \return The server IP address.
     492             :  */
     493           0 : addr::addr_unix local_stream_server_connection::get_addr() const
     494             : {
     495           0 :     return f_address;
     496             : }
     497             : 
     498             : 
     499             : 
     500             : 
     501             : 
     502             : } // namespace ed
     503             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.14

Snap C++ | List of projects | List of versions