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