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