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