netipmid: move raw sockets to boost::asio sockets

Replacing the raw socket code with boost::asio sockets once again
provides a simple API with fewer lines of code.

Change-Id: Ibdd4b5ecbead947128200f17025c351d9b3ec859
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index 297aa9c..fd6a20e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -11,7 +11,6 @@
 netipmid_SOURCES = \
 	endian.hpp \
 	socket_channel.hpp \
-	socket_channel.cpp \
 	message.hpp \
 	auth_algo.hpp \
 	auth_algo.cpp \
diff --git a/sd_event_loop.cpp b/sd_event_loop.cpp
index 3e42d6c..990426f 100644
--- a/sd_event_loop.cpp
+++ b/sd_event_loop.cpp
@@ -16,45 +16,49 @@
 {
 using namespace phosphor::logging;
 
-static int udp623Handler(sd_event_source* es, int fd, uint32_t revents,
-                         void* userdata)
+void EventLoop::handleRmcpPacket()
 {
-    std::shared_ptr<udpsocket::Channel> channelPtr;
-    struct timeval timeout;
-    timeout.tv_sec = SELECT_CALL_TIMEOUT;
-    timeout.tv_usec = 0;
-
     try
     {
-        channelPtr.reset(new udpsocket::Channel(fd, timeout));
+        auto channelPtr = std::make_shared<udpsocket::Channel>(udpSocket);
 
         // Initialize the Message Handler with the socket channel
-        message::Handler msgHandler(channelPtr);
+        auto msgHandler = std::make_shared<message::Handler>(channelPtr);
 
         // Read the incoming IPMI packet
-        std::shared_ptr<message::Message> inMessage(msgHandler.receive());
+        std::shared_ptr<message::Message> inMessage(msgHandler->receive());
         if (inMessage == nullptr)
         {
-            return 0;
+            return;
         }
 
         // Execute the Command
-        auto outMessage = msgHandler.executeCommand(inMessage);
+        std::shared_ptr<message::Message> outMessage =
+            msgHandler->executeCommand(inMessage);
         if (outMessage == nullptr)
         {
-            return 0;
+            return;
         }
-
         // Send the response IPMI Message
-        msgHandler.send(outMessage);
+        msgHandler->send(outMessage);
     }
-    catch (std::exception& e)
+    catch (const std::exception& e)
     {
-        log<level::ERR>("Executing the IPMI message failed");
-        log<level::ERR>(e.what());
+        log<level::ERR>("Executing the IPMI message failed",
+                        entry("EXCEPTION=%s", e.what()));
     }
+}
 
-    return 0;
+void EventLoop::startRmcpReceive()
+{
+    udpSocket->async_wait(boost::asio::socket_base::wait_read,
+                          [this](const boost::system::error_code& ec) {
+                              if (!ec)
+                              {
+                                  io->post([this]() { startRmcpReceive(); });
+                                  handleRmcpPacket();
+                              }
+                          });
 }
 
 static int consoleInputHandler(sd_event_source* es, int fd, uint32_t revents,
@@ -174,62 +178,45 @@
 
 int EventLoop::startEventLoop()
 {
-    int fd = -1;
-    int r = 0;
-    int listenFd;
-    sd_event_source* source = nullptr;
-
     sdbusplus::asio::sd_event_wrapper sdEvents(*io);
     event = sdEvents.get();
 
     // set up boost::asio signal handling
     boost::asio::signal_set signals(*io, SIGINT, SIGTERM);
-    signals.async_wait([this](const boost::system::error_code& error,
-                              int signalNumber) { io->stop(); });
+    signals.async_wait(
+        [this](const boost::system::error_code& error, int signalNumber) {
+            udpSocket->cancel();
+            udpSocket->close();
+            io->stop();
+        });
 
     // Create our own socket if SysD did not supply one.
-    listenFd = sd_listen_fds(0);
-    if (listenFd == 1)
+    int listensFdCount = sd_listen_fds(0);
+    if (listensFdCount == 1)
     {
-        fd = SD_LISTEN_FDS_START;
+        if (sd_is_socket(SD_LISTEN_FDS_START, AF_UNSPEC, SOCK_DGRAM, -1))
+        {
+            udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
+                *io, boost::asio::ip::udp::v6(), SD_LISTEN_FDS_START);
+        }
     }
-    else if (listenFd > 1)
+    else if (listensFdCount > 1)
     {
         log<level::ERR>("Too many file descriptors received");
-        return 1;
-    }
-    else
-    {
-        struct sockaddr_in address;
-        if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0)
-        {
-            r = -errno;
-            log<level::ERR>("Unable to manually open socket");
-            return EXIT_FAILURE;
-        }
-
-        address.sin_family = AF_INET;
-        address.sin_addr.s_addr = INADDR_ANY;
-        address.sin_port = htons(IPMI_STD_PORT);
-
-        if (bind(fd, (struct sockaddr*)&address, sizeof(address)) < 0)
-        {
-            r = -errno;
-            log<level::ERR>("Unable to bind socket");
-            close(fd);
-            return EXIT_FAILURE;
-        }
-    }
-
-    r = sd_event_add_io(event, &source, fd, EPOLLIN, udp623Handler, nullptr);
-    if (r < 0)
-    {
-        close(fd);
         return EXIT_FAILURE;
     }
-
-    udpIPMI.reset(source);
-    source = nullptr;
+    if (!udpSocket)
+    {
+        udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
+            *io, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v6(),
+                                                IPMI_STD_PORT));
+        if (!udpSocket)
+        {
+            log<level::ERR>("Failed to start listening on RMCP socket");
+            return EXIT_FAILURE;
+        }
+    }
+    startRmcpReceive();
 
     io->run();
 
diff --git a/sd_event_loop.hpp b/sd_event_loop.hpp
index 5218b27..c81218a 100644
--- a/sd_event_loop.hpp
+++ b/sd_event_loop.hpp
@@ -139,6 +139,12 @@
     sd_event* event = nullptr;
 
   private:
+    /** @brief async handler for incoming udp packets */
+    void handleRmcpPacket();
+
+    /** @brief register the async handler for incoming udp packets */
+    void startRmcpReceive();
+
     /** @brief Event source object for host console. */
     EventSource hostConsole = nullptr;
 
@@ -146,10 +152,9 @@
      */
     std::shared_ptr<boost::asio::io_context> io;
 
-    /** @brief Event source for the UDP socket listening on IPMI standard
-     *         port.
+    /** @brief boost::asio udp socket
      */
-    EventSource udpIPMI = nullptr;
+    std::shared_ptr<boost::asio::ip::udp::socket> udpSocket = nullptr;
 
     /** @brief Map to keep information regarding IPMI payload instance and
      *         timers for character accumulate interval and retry interval.
diff --git a/socket_channel.cpp b/socket_channel.cpp
deleted file mode 100644
index a3e1301..0000000
--- a/socket_channel.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-#include "socket_channel.hpp"
-
-#include <errno.h>
-#include <netinet/in.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <phosphor-logging/log.hpp>
-#include <string>
-
-using namespace phosphor::logging;
-
-namespace udpsocket
-{
-
-std::string Channel::getRemoteAddress() const
-{
-    char tmp[INET_ADDRSTRLEN] = {0};
-    inet_ntop(AF_INET6, &address.inAddr.sin6_addr, tmp, sizeof(tmp));
-    return std::string(tmp);
-}
-
-std::tuple<int, std::vector<uint8_t>> Channel::read()
-{
-    int rc = 0;
-    int readSize = 0;
-    ssize_t readDataLen = 0;
-    std::vector<uint8_t> outBuffer(0);
-
-    if (ioctl(sockfd, FIONREAD, &readSize) < 0)
-    {
-        log<level::ERR>("Channel::Read: ioctl failed",
-                        entry("ERRNO=%d", errno));
-        rc = -errno;
-        return std::make_tuple(rc, std::move(outBuffer));
-    }
-
-    outBuffer.resize(readSize);
-    auto bufferSize = outBuffer.size();
-    auto outputPtr = outBuffer.data();
-
-    address.addrSize = static_cast<socklen_t>(sizeof(address.inAddr));
-
-    do
-    {
-        readDataLen = recvfrom(sockfd,             // File Descriptor
-                               outputPtr,          // Buffer
-                               bufferSize,         // Bytes requested
-                               0,                  // Flags
-                               &address.sockAddr,  // Address
-                               &address.addrSize); // Address Length
-
-        if (readDataLen == 0) // Peer has performed an orderly shutdown
-        {
-            log<level::ERR>("Channel::Read: Connection Closed");
-            outBuffer.resize(0);
-            rc = -1;
-        }
-        else if (readDataLen < 0) // Error
-        {
-            rc = -errno;
-            log<level::ERR>("Channel::Read: Receive Error",
-                            entry("ERRNO=%d", rc));
-            outBuffer.resize(0);
-        }
-    } while ((readDataLen < 0) && (-(rc) == EINTR));
-
-    // Resize the vector to the actual data read from the socket
-    outBuffer.resize(readDataLen);
-    return std::make_tuple(rc, std::move(outBuffer));
-}
-
-int Channel::write(const std::vector<uint8_t>& inBuffer)
-{
-    int rc = 0;
-    auto outputPtr = inBuffer.data();
-    auto bufferSize = inBuffer.size();
-    auto spuriousWakeup = false;
-    ssize_t writeDataLen = 0;
-    timeval varTimeout = timeout;
-
-    fd_set writeSet;
-    FD_ZERO(&writeSet);
-    FD_SET(sockfd, &writeSet);
-
-    do
-    {
-        spuriousWakeup = false;
-
-        rc = select((sockfd + 1), nullptr, &writeSet, NULL, &varTimeout);
-
-        if (rc > 0)
-        {
-            if (FD_ISSET(sockfd, &writeSet))
-            {
-                address.addrSize =
-                    static_cast<socklen_t>(sizeof(address.inAddr));
-                do
-                {
-                    writeDataLen =
-                        sendto(sockfd,            // File Descriptor
-                               outputPtr,         // Message
-                               bufferSize,        // Length
-                               MSG_NOSIGNAL,      // Flags
-                               &address.sockAddr, // Destination Address
-                               address.addrSize); // Address Length
-
-                    if (writeDataLen < 0)
-                    {
-                        rc = -errno;
-                        log<level::ERR>("Channel::Write: Write failed",
-                                        entry("ERRNO=%d", rc));
-                    }
-                    else if (static_cast<size_t>(writeDataLen) < bufferSize)
-                    {
-                        rc = -1;
-                        log<level::ERR>(
-                            "Channel::Write: Complete data not written"
-                            " to the socket");
-                    }
-                } while ((writeDataLen < 0) && (-(rc) == EINTR));
-            }
-            else
-            {
-                // Spurious wake up
-                log<level::ERR>("Spurious wake up on select (writeset)");
-                spuriousWakeup = true;
-            }
-        }
-        else
-        {
-            if (rc == 0)
-            {
-                // Timed out
-                rc = -1;
-                log<level::ERR>("We timed out on select call (writeset)");
-            }
-            else
-            {
-                // Error
-                rc = -errno;
-                log<level::ERR>("select call (writeset) error",
-                                entry("ERRNO=%d", rc));
-            }
-        }
-    } while (spuriousWakeup);
-
-    return rc;
-}
-
-} // namespace udpsocket
diff --git a/socket_channel.hpp b/socket_channel.hpp
index 4b10b9a..ebe0c8f 100644
--- a/socket_channel.hpp
+++ b/socket_channel.hpp
@@ -1,8 +1,7 @@
 #pragma once
 
-#include <arpa/inet.h>
-#include <unistd.h>
-
+#include <boost/asio/ip/udp.hpp>
+#include <memory>
 #include <string>
 #include <tuple>
 #include <vector>
@@ -18,28 +17,24 @@
 class Channel
 {
   public:
-    struct SockAddr_t
-    {
-        union
-        {
-            sockaddr sockAddr;
-            sockaddr_in6 inAddr;
-        };
-        socklen_t addrSize;
-    };
+    Channel() = delete;
+    ~Channel() = default;
+    Channel(const Channel& right) = delete;
+    Channel& operator=(const Channel& right) = delete;
+    Channel(Channel&&) = delete;
+    Channel& operator=(Channel&&) = delete;
 
     /**
      * @brief Constructor
      *
      * Initialize the IPMI socket object with the socket descriptor
      *
-     * @param [in] File Descriptor for the socket
-     * @param [in] Timeout parameter for the select call
+     * @param [in] pointer to a boost::asio udp socket object
      *
      * @return None
      */
-    Channel(int insockfd, timeval& inTimeout) :
-        sockfd(insockfd), timeout(inTimeout)
+    explicit Channel(std::shared_ptr<boost::asio::ip::udp::socket> socket) :
+        socket(socket)
     {
     }
 
@@ -51,7 +46,10 @@
      *
      * @return IP address of the remote peer
      */
-    std::string getRemoteAddress() const;
+    std::string getRemoteAddress() const
+    {
+        return endpoint.address().to_string();
+    }
 
     /**
      * @brief Fetch the port number of the remote peer
@@ -63,7 +61,7 @@
      */
     auto getPort() const
     {
-        return address.inAddr.sin6_port;
+        return endpoint.port();
     }
 
     /**
@@ -77,7 +75,19 @@
      *         In case of error, the return code is < 0 and vector is set
      *         to size 0.
      */
-    std::tuple<int, std::vector<uint8_t>> read();
+    std::tuple<int, std::vector<uint8_t>> read()
+    {
+        std::vector<uint8_t> packet(socket->available());
+        try
+        {
+            socket->receive_from(boost::asio::buffer(packet), endpoint);
+        }
+        catch (const boost::system::system_error& e)
+        {
+            return std::make_tuple(e.code().value(), std::vector<uint8_t>());
+        }
+        return std::make_tuple(0, packet);
+    }
 
     /**
      *  @brief Write the outgoing packet
@@ -90,33 +100,30 @@
      *  @return In case of success the return code is 0 and return code is
      *          < 0 in case of failure.
      */
-    int write(const std::vector<uint8_t>& inBuffer);
+    int write(const std::vector<uint8_t>& inBuffer)
+    {
+        try
+        {
+            socket->send_to(boost::asio::buffer(inBuffer), endpoint);
+        }
+        catch (const boost::system::system_error& e)
+        {
+            return e.code().value();
+        }
+        return 0;
+    }
 
     /**
      * @brief Returns file descriptor for the socket
      */
     auto getHandle(void) const
     {
-        return sockfd;
+        return socket->native_handle();
     }
 
-    ~Channel() = default;
-    Channel(const Channel& right) = delete;
-    Channel& operator=(const Channel& right) = delete;
-    Channel(Channel&&) = default;
-    Channel& operator=(Channel&&) = default;
-
   private:
-    /*
-     * The socket descriptor is the UDP server socket for the IPMI port.
-     * The same socket descriptor is used for multiple ipmi clients and the
-     * life of the descriptor is lifetime of the net-ipmid server. So we
-     * do not need to close the socket descriptor in the cleanup of the
-     * udpsocket class.
-     */
-    int sockfd;
-    SockAddr_t address;
-    timeval timeout;
+    std::shared_ptr<boost::asio::ip::udp::socket> socket;
+    boost::asio::ip::udp::endpoint endpoint{};
 };
 
 } // namespace udpsocket