1 #pragma once
2 #include <netinet/in.h>
3 #include <sys/socket.h>
4 #include <sys/types.h>
5 
6 #include <boost/asio/ip/udp.hpp>
7 #include <memory>
8 #include <optional>
9 #include <phosphor-logging/log.hpp>
10 #include <string>
11 #include <tuple>
12 #include <variant>
13 #include <vector>
14 
15 namespace udpsocket
16 {
17 
18 /** @class Channel
19  *
20  *  @brief Provides encapsulation for UDP socket operations like Read, Peek,
21  *         Write, Remote peer's IP Address and Port.
22  */
23 class Channel
24 {
25   public:
26     Channel() = delete;
27     ~Channel() = default;
28     Channel(const Channel& right) = delete;
29     Channel& operator=(const Channel& right) = delete;
30     Channel(Channel&&) = delete;
31     Channel& operator=(Channel&&) = delete;
32 
33     /**
34      * @brief Constructor
35      *
36      * Initialize the IPMI socket object with the socket descriptor
37      *
38      * @param [in] pointer to a boost::asio udp socket object
39      *
40      * @return None
41      */
42     explicit Channel(std::shared_ptr<boost::asio::ip::udp::socket> socket) :
43         socket(socket)
44     {
45     }
46 
47     /**
48      * @brief Fetch the IP address of the remote peer
49      *
50      * Returns the IP address of the remote peer which is connected to this
51      * socket
52      *
53      * @return IP address of the remote peer
54      */
55     std::string getRemoteAddress() const
56     {
57         const char* retval = nullptr;
58         if (sockAddrSize == sizeof(sockaddr_in))
59         {
60             char ipv4addr[INET_ADDRSTRLEN];
61             retval =
62                 inet_ntop(AF_INET, &remoteSockAddr, ipv4addr, sizeof(ipv4addr));
63         }
64         else if (sockAddrSize == sizeof(sockaddr_in6))
65         {
66             char ipv6addr[INET6_ADDRSTRLEN];
67             retval = inet_ntop(AF_INET6, &remoteSockAddr, ipv6addr,
68                                sizeof(ipv6addr));
69         }
70         if (retval)
71         {
72             return retval;
73         }
74         phosphor::logging::log<phosphor::logging::level::ERR>(
75             "Error in inet_ntop",
76             phosphor::logging::entry("ERROR=%s", strerror(errno)));
77         return std::string();
78     }
79 
80     /**
81      * @brief Fetch the port number of the remote peer
82      *
83      * Returns the port number of the remote peer
84      *
85      * @return Port number
86      *
87      */
88     uint16_t getPort() const
89     {
90         if (sockAddrSize == sizeof(sockaddr_in))
91         {
92             return ntohs(reinterpret_cast<const sockaddr_in*>(&remoteSockAddr)
93                              ->sin_port);
94         }
95         if (sockAddrSize == sizeof(sockaddr_in6))
96         {
97             return ntohs(reinterpret_cast<const sockaddr_in6*>(&remoteSockAddr)
98                              ->sin6_port);
99         }
100         return 0;
101     }
102 
103     /**
104      * @brief Read the incoming packet
105      *
106      * Reads the data available on the socket
107      *
108      * @return A tuple with return code and vector with the buffer
109      *         In case of success, the vector is populated with the data
110      *         available on the socket and return code is 0.
111      *         In case of error, the return code is < 0 and vector is set
112      *         to size 0.
113      */
114     std::tuple<int, std::vector<uint8_t>> read()
115     {
116         // cannot use the standard asio reading mechanism because it does not
117         // provide a mechanism to reach down into the depths and use a msghdr
118         std::vector<uint8_t> packet(socket->available());
119         iovec iov = {packet.data(), packet.size()};
120         char msgCtrl[1024];
121         msghdr msg = {&remoteSockAddr, sizeof(remoteSockAddr), &iov, 1,
122                       msgCtrl,         sizeof(msgCtrl),        0};
123 
124         ssize_t bytesReceived = recvmsg(socket->native_handle(), &msg, 0);
125         // Read of the packet failed
126         if (bytesReceived < 0)
127         {
128             // something bad happened; bail
129             phosphor::logging::log<phosphor::logging::level::ERR>(
130                 "Error in recvmsg",
131                 phosphor::logging::entry("ERROR=%s", strerror(errno)));
132             return std::make_tuple(-errno, std::vector<uint8_t>());
133         }
134         // save the size of either ipv4 or i4v6 sockaddr
135         sockAddrSize = msg.msg_namelen;
136 
137         // extract the destination address from the message
138         cmsghdr* cmsg;
139         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0;
140              cmsg = CMSG_NXTHDR(&msg, cmsg))
141         {
142             if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
143             {
144                 // save local address from the pktinfo4
145                 pktinfo4 = *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg));
146             }
147             if (cmsg->cmsg_level == IPPROTO_IPV6 &&
148                 cmsg->cmsg_type == IPV6_PKTINFO)
149             {
150                 // save local address from the pktinfo6
151                 pktinfo6 = *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg));
152             }
153         }
154         return std::make_tuple(0, packet);
155     }
156 
157     /**
158      *  @brief Write the outgoing packet
159      *
160      *  Writes the data in the vector to the socket
161      *
162      *  @param [in] inBuffer
163      *      The vector would be the buffer of data to write to the socket.
164      *
165      *  @return In case of success the return code is the number of bytes
166      *          written and return code is < 0 in case of failure.
167      */
168     int write(const std::vector<uint8_t>& inBuffer)
169     {
170         // in order to make sure packets go back out from the same
171         // IP address they came in on, sendmsg must be used instead
172         // of the boost::asio::ip::send or sendto
173         iovec iov = {const_cast<uint8_t*>(inBuffer.data()), inBuffer.size()};
174         char msgCtrl[1024];
175         msghdr msg = {&remoteSockAddr, sockAddrSize,    &iov, 1,
176                       msgCtrl,         sizeof(msgCtrl), 0};
177         int cmsg_space = 0;
178         cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
179         if (pktinfo6)
180         {
181             cmsg->cmsg_level = IPPROTO_IPV6;
182             cmsg->cmsg_type = IPV6_PKTINFO;
183             cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
184             *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo6;
185             cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo));
186         }
187         else if (pktinfo4)
188         {
189             cmsg->cmsg_level = IPPROTO_IP;
190             cmsg->cmsg_type = IP_PKTINFO;
191             cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
192             *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo4;
193             cmsg_space += CMSG_SPACE(sizeof(in_pktinfo));
194         }
195         msg.msg_controllen = cmsg_space;
196         int ret = sendmsg(socket->native_handle(), &msg, 0);
197         if (ret < 0)
198         {
199             phosphor::logging::log<phosphor::logging::level::ERR>(
200                 "Error in sendmsg",
201                 phosphor::logging::entry("ERROR=%s", strerror(errno)));
202         }
203         return ret;
204     }
205 
206     /**
207      * @brief Returns file descriptor for the socket
208      */
209     auto getHandle(void) const
210     {
211         return socket->native_handle();
212     }
213 
214   private:
215     std::shared_ptr<boost::asio::ip::udp::socket> socket;
216     sockaddr_storage remoteSockAddr;
217     socklen_t sockAddrSize;
218     std::optional<in_pktinfo> pktinfo4;
219     std::optional<in6_pktinfo> pktinfo6;
220 };
221 
222 } // namespace udpsocket
223