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 = 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 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