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/lg2.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      */
Channel(std::shared_ptr<boost::asio::ip::udp::socket> socket)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      */
isIpv4InIpv6(const struct in6_addr & v6Addr) const54     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      */
getRemoteAddress(uint32_t & remoteIpv4Addr) const70     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 = inet_ntop(AF_INET, &(sa->sin_addr), ipv4addr,
80                                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         lg2::error("Error in inet_ntop: {ERROR}", "ERROR", strerror(errno));
103         return std::string();
104     }
105 
106     /**
107      * @brief Fetch the port number of the remote peer
108      *
109      * Returns the port number of the remote peer
110      *
111      * @return Port number
112      *
113      */
getPort() const114     uint16_t getPort() const
115     {
116         if (sockAddrSize == sizeof(sockaddr_in))
117         {
118             return ntohs(reinterpret_cast<const sockaddr_in*>(&remoteSockAddr)
119                              ->sin_port);
120         }
121         if (sockAddrSize == sizeof(sockaddr_in6))
122         {
123             return ntohs(reinterpret_cast<const sockaddr_in6*>(&remoteSockAddr)
124                              ->sin6_port);
125         }
126         return 0;
127     }
128 
129     /**
130      * @brief Read the incoming packet
131      *
132      * Reads the data available on the socket
133      *
134      * @return A tuple with return code and vector with the buffer
135      *         In case of success, the vector is populated with the data
136      *         available on the socket and return code is 0.
137      *         In case of error, the return code is < 0 and vector is set
138      *         to size 0.
139      */
read()140     std::tuple<int, std::vector<uint8_t>> read()
141     {
142         // cannot use the standard asio reading mechanism because it does not
143         // provide a mechanism to reach down into the depths and use a msghdr
144         std::vector<uint8_t> packet(socket->available());
145         iovec iov = {packet.data(), packet.size()};
146         char msgCtrl[1024];
147         msghdr msg = {&remoteSockAddr, sizeof(remoteSockAddr), &iov, 1,
148                       msgCtrl,         sizeof(msgCtrl),        0};
149 
150         ssize_t bytesReceived = recvmsg(socket->native_handle(), &msg, 0);
151         // Read of the packet failed
152         if (bytesReceived < 0)
153         {
154             // something bad happened; bail
155             lg2::error("Error in recvmsg: {ERROR}", "ERROR",
156                        strerror(-bytesReceived));
157             return std::make_tuple(-errno, std::vector<uint8_t>());
158         }
159         // save the size of either ipv4 or i4v6 sockaddr
160         sockAddrSize = msg.msg_namelen;
161 
162         // extract the destination address from the message
163         cmsghdr* cmsg;
164         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0;
165              cmsg = CMSG_NXTHDR(&msg, cmsg))
166         {
167             if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
168             {
169                 // save local address from the pktinfo4
170                 pktinfo4 = *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg));
171             }
172             if (cmsg->cmsg_level == IPPROTO_IPV6 &&
173                 cmsg->cmsg_type == IPV6_PKTINFO)
174             {
175                 // save local address from the pktinfo6
176                 pktinfo6 = *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg));
177             }
178         }
179         return std::make_tuple(0, packet);
180     }
181 
182     /**
183      *  @brief Write the outgoing packet
184      *
185      *  Writes the data in the vector to the socket
186      *
187      *  @param [in] inBuffer
188      *      The vector would be the buffer of data to write to the socket.
189      *
190      *  @return In case of success the return code is the number of bytes
191      *          written and return code is < 0 in case of failure.
192      */
write(const std::vector<uint8_t> & inBuffer)193     int write(const std::vector<uint8_t>& inBuffer)
194     {
195         // in order to make sure packets go back out from the same
196         // IP address they came in on, sendmsg must be used instead
197         // of the boost::asio::ip::send or sendto
198         iovec iov = {const_cast<uint8_t*>(inBuffer.data()), inBuffer.size()};
199         char msgCtrl[1024];
200         msghdr msg = {&remoteSockAddr, sockAddrSize,    &iov, 1,
201                       msgCtrl,         sizeof(msgCtrl), 0};
202         int cmsg_space = 0;
203         cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
204         if (pktinfo6)
205         {
206             cmsg->cmsg_level = IPPROTO_IPV6;
207             cmsg->cmsg_type = IPV6_PKTINFO;
208             cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
209             *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo6;
210             cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo));
211         }
212         else if (pktinfo4)
213         {
214             cmsg->cmsg_level = IPPROTO_IP;
215             cmsg->cmsg_type = IP_PKTINFO;
216             cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
217             *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo4;
218             cmsg_space += CMSG_SPACE(sizeof(in_pktinfo));
219         }
220         msg.msg_controllen = cmsg_space;
221         int ret = sendmsg(socket->native_handle(), &msg, 0);
222         if (ret < 0)
223         {
224             lg2::error("Error in sendmsg: {ERROR}", "ERROR", strerror(-ret));
225         }
226         return ret;
227     }
228 
229     /**
230      * @brief Returns file descriptor for the socket
231      */
getHandle(void) const232     auto getHandle(void) const
233     {
234         return socket->native_handle();
235     }
236 
237   private:
238     std::shared_ptr<boost::asio::ip::udp::socket> socket;
239     sockaddr_storage remoteSockAddr;
240     socklen_t sockAddrSize;
241     std::optional<in_pktinfo> pktinfo4;
242     std::optional<in6_pktinfo> pktinfo6;
243 };
244 
245 } // namespace udpsocket
246