1aaeb29e3STom Joseph #pragma once
2d92bc324SVernon Mauery #include <netinet/in.h>
3d92bc324SVernon Mauery #include <sys/socket.h>
4d92bc324SVernon Mauery #include <sys/types.h>
5aaeb29e3STom Joseph 
67a0142c5SVernon Mauery #include <boost/asio/ip/udp.hpp>
77b7f25f7SGeorge Liu #include <phosphor-logging/lg2.hpp>
8bc8958feSGeorge Liu 
97a0142c5SVernon Mauery #include <memory>
10d92bc324SVernon Mauery #include <optional>
11aaeb29e3STom Joseph #include <string>
12aaeb29e3STom Joseph #include <tuple>
13d92bc324SVernon Mauery #include <variant>
14aaeb29e3STom Joseph #include <vector>
15aaeb29e3STom Joseph 
16aaeb29e3STom Joseph namespace udpsocket
17aaeb29e3STom Joseph {
189979e997SRajashekar Gade Reddy static constexpr uint8_t v4v6Index = 12;
19aaeb29e3STom Joseph 
20aaeb29e3STom Joseph /** @class Channel
21aaeb29e3STom Joseph  *
22aaeb29e3STom Joseph  *  @brief Provides encapsulation for UDP socket operations like Read, Peek,
23aaeb29e3STom Joseph  *         Write, Remote peer's IP Address and Port.
24aaeb29e3STom Joseph  */
25aaeb29e3STom Joseph class Channel
26aaeb29e3STom Joseph {
27aaeb29e3STom Joseph   public:
287a0142c5SVernon Mauery     Channel() = delete;
297a0142c5SVernon Mauery     ~Channel() = default;
307a0142c5SVernon Mauery     Channel(const Channel& right) = delete;
317a0142c5SVernon Mauery     Channel& operator=(const Channel& right) = delete;
327a0142c5SVernon Mauery     Channel(Channel&&) = delete;
337a0142c5SVernon Mauery     Channel& operator=(Channel&&) = delete;
34aaeb29e3STom Joseph 
35aaeb29e3STom Joseph     /**
36aaeb29e3STom Joseph      * @brief Constructor
37aaeb29e3STom Joseph      *
38aaeb29e3STom Joseph      * Initialize the IPMI socket object with the socket descriptor
39aaeb29e3STom Joseph      *
407a0142c5SVernon Mauery      * @param [in] pointer to a boost::asio udp socket object
41aaeb29e3STom Joseph      *
42aaeb29e3STom Joseph      * @return None
43aaeb29e3STom Joseph      */
Channel(std::shared_ptr<boost::asio::ip::udp::socket> socket)447a0142c5SVernon Mauery     explicit Channel(std::shared_ptr<boost::asio::ip::udp::socket> socket) :
457a0142c5SVernon Mauery         socket(socket)
46bc8958feSGeorge Liu     {}
479979e997SRajashekar Gade Reddy     /**
489979e997SRajashekar Gade Reddy      * @brief Check if ip address is ipv4 mapped ipv6
499979e997SRajashekar Gade Reddy      *
509979e997SRajashekar Gade Reddy      *  @param v6Addr : in6_addr obj
519979e997SRajashekar Gade Reddy      *
529979e997SRajashekar Gade Reddy      * @return true if ipv4 mapped ipv6 else return false
539979e997SRajashekar Gade Reddy      */
isIpv4InIpv6(const struct in6_addr & v6Addr) const549979e997SRajashekar Gade Reddy     bool isIpv4InIpv6(const struct in6_addr& v6Addr) const
559979e997SRajashekar Gade Reddy     {
569979e997SRajashekar Gade Reddy         constexpr uint8_t prefix[v4v6Index] = {0, 0, 0, 0, 0,    0,
579979e997SRajashekar Gade Reddy                                                0, 0, 0, 0, 0xff, 0xff};
589979e997SRajashekar Gade Reddy         return 0 == std::memcmp(&v6Addr.s6_addr[0], &prefix[0], sizeof(prefix));
599979e997SRajashekar Gade Reddy     }
60aaeb29e3STom Joseph     /**
61aaeb29e3STom Joseph      * @brief Fetch the IP address of the remote peer
62aaeb29e3STom Joseph      *
639979e997SRajashekar Gade Reddy      *  @param remoteIpv4Addr : ipv4 address is assigned to it.
649979e997SRajashekar Gade Reddy      *
65aaeb29e3STom Joseph      * Returns the IP address of the remote peer which is connected to this
66aaeb29e3STom Joseph      * socket
67aaeb29e3STom Joseph      *
68aaeb29e3STom Joseph      * @return IP address of the remote peer
69aaeb29e3STom Joseph      */
getRemoteAddress(uint32_t & remoteIpv4Addr) const709979e997SRajashekar Gade Reddy     std::string getRemoteAddress(uint32_t& remoteIpv4Addr) const
717a0142c5SVernon Mauery     {
72d92bc324SVernon Mauery         const char* retval = nullptr;
73d92bc324SVernon Mauery         if (sockAddrSize == sizeof(sockaddr_in))
74d92bc324SVernon Mauery         {
75d92bc324SVernon Mauery             char ipv4addr[INET_ADDRSTRLEN];
769979e997SRajashekar Gade Reddy             const sockaddr_in* sa =
779979e997SRajashekar Gade Reddy                 reinterpret_cast<const sockaddr_in*>(&remoteSockAddr);
789979e997SRajashekar Gade Reddy             remoteIpv4Addr = sa->sin_addr.s_addr;
79*8425624aSPatrick Williams             retval =
80*8425624aSPatrick Williams                 inet_ntop(AF_INET, &(sa->sin_addr), ipv4addr, sizeof(ipv4addr));
81d92bc324SVernon Mauery         }
82d92bc324SVernon Mauery         else if (sockAddrSize == sizeof(sockaddr_in6))
83d92bc324SVernon Mauery         {
84d92bc324SVernon Mauery             char ipv6addr[INET6_ADDRSTRLEN];
859979e997SRajashekar Gade Reddy             const sockaddr_in6* sa =
869979e997SRajashekar Gade Reddy                 reinterpret_cast<const sockaddr_in6*>(&remoteSockAddr);
879979e997SRajashekar Gade Reddy 
889979e997SRajashekar Gade Reddy             if (isIpv4InIpv6(sa->sin6_addr))
899979e997SRajashekar Gade Reddy             {
909979e997SRajashekar Gade Reddy                 std::copy_n(&sa->sin6_addr.s6_addr[v4v6Index],
919979e997SRajashekar Gade Reddy                             sizeof(remoteIpv4Addr),
929979e997SRajashekar Gade Reddy                             reinterpret_cast<uint8_t*>(&remoteIpv4Addr));
93d92bc324SVernon Mauery             }
949979e997SRajashekar Gade Reddy             retval = inet_ntop(AF_INET6, &(sa->sin6_addr), ipv6addr,
959979e997SRajashekar Gade Reddy                                sizeof(ipv6addr));
969979e997SRajashekar Gade Reddy         }
979979e997SRajashekar Gade Reddy 
98d92bc324SVernon Mauery         if (retval)
99d92bc324SVernon Mauery         {
100d92bc324SVernon Mauery             return retval;
101d92bc324SVernon Mauery         }
1027b7f25f7SGeorge Liu         lg2::error("Error in inet_ntop: {ERROR}", "ERROR", strerror(errno));
103d92bc324SVernon Mauery         return std::string();
1047a0142c5SVernon Mauery     }
105aaeb29e3STom Joseph 
106aaeb29e3STom Joseph     /**
107aaeb29e3STom Joseph      * @brief Fetch the port number of the remote peer
108aaeb29e3STom Joseph      *
109aaeb29e3STom Joseph      * Returns the port number of the remote peer
110aaeb29e3STom Joseph      *
111aaeb29e3STom Joseph      * @return Port number
112aaeb29e3STom Joseph      *
113aaeb29e3STom Joseph      */
getPort() const114d92bc324SVernon Mauery     uint16_t getPort() const
115aaeb29e3STom Joseph     {
116d92bc324SVernon Mauery         if (sockAddrSize == sizeof(sockaddr_in))
117d92bc324SVernon Mauery         {
118d92bc324SVernon Mauery             return ntohs(reinterpret_cast<const sockaddr_in*>(&remoteSockAddr)
119d92bc324SVernon Mauery                              ->sin_port);
120d92bc324SVernon Mauery         }
121d92bc324SVernon Mauery         if (sockAddrSize == sizeof(sockaddr_in6))
122d92bc324SVernon Mauery         {
123d92bc324SVernon Mauery             return ntohs(reinterpret_cast<const sockaddr_in6*>(&remoteSockAddr)
124d92bc324SVernon Mauery                              ->sin6_port);
125d92bc324SVernon Mauery         }
126d92bc324SVernon Mauery         return 0;
127aaeb29e3STom Joseph     }
128aaeb29e3STom Joseph 
129aaeb29e3STom Joseph     /**
130aaeb29e3STom Joseph      * @brief Read the incoming packet
131aaeb29e3STom Joseph      *
132aaeb29e3STom Joseph      * Reads the data available on the socket
133aaeb29e3STom Joseph      *
134aaeb29e3STom Joseph      * @return A tuple with return code and vector with the buffer
135aaeb29e3STom Joseph      *         In case of success, the vector is populated with the data
136aaeb29e3STom Joseph      *         available on the socket and return code is 0.
137aaeb29e3STom Joseph      *         In case of error, the return code is < 0 and vector is set
138aaeb29e3STom Joseph      *         to size 0.
139aaeb29e3STom Joseph      */
read()1407a0142c5SVernon Mauery     std::tuple<int, std::vector<uint8_t>> read()
1417a0142c5SVernon Mauery     {
142d92bc324SVernon Mauery         // cannot use the standard asio reading mechanism because it does not
143d92bc324SVernon Mauery         // provide a mechanism to reach down into the depths and use a msghdr
1447a0142c5SVernon Mauery         std::vector<uint8_t> packet(socket->available());
145d92bc324SVernon Mauery         iovec iov = {packet.data(), packet.size()};
146d92bc324SVernon Mauery         char msgCtrl[1024];
147d92bc324SVernon Mauery         msghdr msg = {&remoteSockAddr, sizeof(remoteSockAddr), &iov, 1,
148d92bc324SVernon Mauery                       msgCtrl,         sizeof(msgCtrl),        0};
149d92bc324SVernon Mauery 
150d92bc324SVernon Mauery         ssize_t bytesReceived = recvmsg(socket->native_handle(), &msg, 0);
151d92bc324SVernon Mauery         // Read of the packet failed
152d92bc324SVernon Mauery         if (bytesReceived < 0)
1537a0142c5SVernon Mauery         {
154d92bc324SVernon Mauery             // something bad happened; bail
1557b7f25f7SGeorge Liu             lg2::error("Error in recvmsg: {ERROR}", "ERROR",
1567b7f25f7SGeorge Liu                        strerror(-bytesReceived));
157d92bc324SVernon Mauery             return std::make_tuple(-errno, std::vector<uint8_t>());
1587a0142c5SVernon Mauery         }
159d92bc324SVernon Mauery         // save the size of either ipv4 or i4v6 sockaddr
160d92bc324SVernon Mauery         sockAddrSize = msg.msg_namelen;
161d92bc324SVernon Mauery 
162d92bc324SVernon Mauery         // extract the destination address from the message
163d92bc324SVernon Mauery         cmsghdr* cmsg;
164d92bc324SVernon Mauery         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0;
165d92bc324SVernon Mauery              cmsg = CMSG_NXTHDR(&msg, cmsg))
1667a0142c5SVernon Mauery         {
167d92bc324SVernon Mauery             if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
168d92bc324SVernon Mauery             {
169d92bc324SVernon Mauery                 // save local address from the pktinfo4
170d92bc324SVernon Mauery                 pktinfo4 = *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg));
171d92bc324SVernon Mauery             }
172d92bc324SVernon Mauery             if (cmsg->cmsg_level == IPPROTO_IPV6 &&
173d92bc324SVernon Mauery                 cmsg->cmsg_type == IPV6_PKTINFO)
174d92bc324SVernon Mauery             {
175d92bc324SVernon Mauery                 // save local address from the pktinfo6
176d92bc324SVernon Mauery                 pktinfo6 = *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg));
177d92bc324SVernon Mauery             }
1787a0142c5SVernon Mauery         }
1797a0142c5SVernon Mauery         return std::make_tuple(0, packet);
1807a0142c5SVernon Mauery     }
181aaeb29e3STom Joseph 
182aaeb29e3STom Joseph     /**
183aaeb29e3STom Joseph      *  @brief Write the outgoing packet
184aaeb29e3STom Joseph      *
185aaeb29e3STom Joseph      *  Writes the data in the vector to the socket
186aaeb29e3STom Joseph      *
187aaeb29e3STom Joseph      *  @param [in] inBuffer
188aaeb29e3STom Joseph      *      The vector would be the buffer of data to write to the socket.
189aaeb29e3STom Joseph      *
190d92bc324SVernon Mauery      *  @return In case of success the return code is the number of bytes
191d92bc324SVernon Mauery      *          written and return code is < 0 in case of failure.
192aaeb29e3STom Joseph      */
write(const std::vector<uint8_t> & inBuffer)1937a0142c5SVernon Mauery     int write(const std::vector<uint8_t>& inBuffer)
1947a0142c5SVernon Mauery     {
195d92bc324SVernon Mauery         // in order to make sure packets go back out from the same
196d92bc324SVernon Mauery         // IP address they came in on, sendmsg must be used instead
197d92bc324SVernon Mauery         // of the boost::asio::ip::send or sendto
198d92bc324SVernon Mauery         iovec iov = {const_cast<uint8_t*>(inBuffer.data()), inBuffer.size()};
199d92bc324SVernon Mauery         char msgCtrl[1024];
200d92bc324SVernon Mauery         msghdr msg = {&remoteSockAddr, sockAddrSize,    &iov, 1,
201d92bc324SVernon Mauery                       msgCtrl,         sizeof(msgCtrl), 0};
202d92bc324SVernon Mauery         int cmsg_space = 0;
203d92bc324SVernon Mauery         cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
204d92bc324SVernon Mauery         if (pktinfo6)
2057a0142c5SVernon Mauery         {
206d92bc324SVernon Mauery             cmsg->cmsg_level = IPPROTO_IPV6;
207d92bc324SVernon Mauery             cmsg->cmsg_type = IPV6_PKTINFO;
208d92bc324SVernon Mauery             cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
209d92bc324SVernon Mauery             *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo6;
210d92bc324SVernon Mauery             cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo));
2117a0142c5SVernon Mauery         }
212d92bc324SVernon Mauery         else if (pktinfo4)
2137a0142c5SVernon Mauery         {
214d92bc324SVernon Mauery             cmsg->cmsg_level = IPPROTO_IP;
215d92bc324SVernon Mauery             cmsg->cmsg_type = IP_PKTINFO;
216d92bc324SVernon Mauery             cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
217d92bc324SVernon Mauery             *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo4;
218d92bc324SVernon Mauery             cmsg_space += CMSG_SPACE(sizeof(in_pktinfo));
2197a0142c5SVernon Mauery         }
220d92bc324SVernon Mauery         msg.msg_controllen = cmsg_space;
221d92bc324SVernon Mauery         int ret = sendmsg(socket->native_handle(), &msg, 0);
222d92bc324SVernon Mauery         if (ret < 0)
223d92bc324SVernon Mauery         {
2247b7f25f7SGeorge Liu             lg2::error("Error in sendmsg: {ERROR}", "ERROR", strerror(-ret));
225d92bc324SVernon Mauery         }
226d92bc324SVernon Mauery         return ret;
2277a0142c5SVernon Mauery     }
228aaeb29e3STom Joseph 
229aaeb29e3STom Joseph     /**
230aaeb29e3STom Joseph      * @brief Returns file descriptor for the socket
231aaeb29e3STom Joseph      */
getHandle(void) const232aaeb29e3STom Joseph     auto getHandle(void) const
233aaeb29e3STom Joseph     {
2347a0142c5SVernon Mauery         return socket->native_handle();
235aaeb29e3STom Joseph     }
236aaeb29e3STom Joseph 
237aaeb29e3STom Joseph   private:
2387a0142c5SVernon Mauery     std::shared_ptr<boost::asio::ip::udp::socket> socket;
239d92bc324SVernon Mauery     sockaddr_storage remoteSockAddr;
240d92bc324SVernon Mauery     socklen_t sockAddrSize;
241d92bc324SVernon Mauery     std::optional<in_pktinfo> pktinfo4;
242d92bc324SVernon Mauery     std::optional<in6_pktinfo> pktinfo6;
243aaeb29e3STom Joseph };
244aaeb29e3STom Joseph 
245aaeb29e3STom Joseph } // namespace udpsocket
246