| #pragma once |
| #include <netinet/in.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| |
| #include <boost/asio/ip/udp.hpp> |
| #include <phosphor-logging/log.hpp> |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <tuple> |
| #include <variant> |
| #include <vector> |
| |
| namespace udpsocket |
| { |
| static constexpr uint8_t v4v6Index = 12; |
| |
| /** @class Channel |
| * |
| * @brief Provides encapsulation for UDP socket operations like Read, Peek, |
| * Write, Remote peer's IP Address and Port. |
| */ |
| class Channel |
| { |
| public: |
| Channel() = delete; |
| ~Channel() = default; |
| Channel(const Channel& right) = delete; |
| Channel& operator=(const Channel& right) = delete; |
| Channel(Channel&&) = delete; |
| Channel& operator=(Channel&&) = delete; |
| |
| /** |
| * @brief Constructor |
| * |
| * Initialize the IPMI socket object with the socket descriptor |
| * |
| * @param [in] pointer to a boost::asio udp socket object |
| * |
| * @return None |
| */ |
| explicit Channel(std::shared_ptr<boost::asio::ip::udp::socket> socket) : |
| socket(socket) |
| {} |
| /** |
| * @brief Check if ip address is ipv4 mapped ipv6 |
| * |
| * @param v6Addr : in6_addr obj |
| * |
| * @return true if ipv4 mapped ipv6 else return false |
| */ |
| bool isIpv4InIpv6(const struct in6_addr& v6Addr) const |
| { |
| constexpr uint8_t prefix[v4v6Index] = {0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0xff, 0xff}; |
| return 0 == std::memcmp(&v6Addr.s6_addr[0], &prefix[0], sizeof(prefix)); |
| } |
| /** |
| * @brief Fetch the IP address of the remote peer |
| * |
| * @param remoteIpv4Addr : ipv4 address is assigned to it. |
| * |
| * Returns the IP address of the remote peer which is connected to this |
| * socket |
| * |
| * @return IP address of the remote peer |
| */ |
| std::string getRemoteAddress(uint32_t& remoteIpv4Addr) const |
| { |
| const char* retval = nullptr; |
| if (sockAddrSize == sizeof(sockaddr_in)) |
| { |
| char ipv4addr[INET_ADDRSTRLEN]; |
| const sockaddr_in* sa = |
| reinterpret_cast<const sockaddr_in*>(&remoteSockAddr); |
| remoteIpv4Addr = sa->sin_addr.s_addr; |
| retval = |
| inet_ntop(AF_INET, &(sa->sin_addr), ipv4addr, sizeof(ipv4addr)); |
| } |
| else if (sockAddrSize == sizeof(sockaddr_in6)) |
| { |
| char ipv6addr[INET6_ADDRSTRLEN]; |
| const sockaddr_in6* sa = |
| reinterpret_cast<const sockaddr_in6*>(&remoteSockAddr); |
| |
| if (isIpv4InIpv6(sa->sin6_addr)) |
| { |
| std::copy_n(&sa->sin6_addr.s6_addr[v4v6Index], |
| sizeof(remoteIpv4Addr), |
| reinterpret_cast<uint8_t*>(&remoteIpv4Addr)); |
| } |
| retval = inet_ntop(AF_INET6, &(sa->sin6_addr), ipv6addr, |
| sizeof(ipv6addr)); |
| } |
| |
| if (retval) |
| { |
| return retval; |
| } |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error in inet_ntop", |
| phosphor::logging::entry("ERROR=%s", strerror(errno))); |
| return std::string(); |
| } |
| |
| /** |
| * @brief Fetch the port number of the remote peer |
| * |
| * Returns the port number of the remote peer |
| * |
| * @return Port number |
| * |
| */ |
| uint16_t getPort() const |
| { |
| if (sockAddrSize == sizeof(sockaddr_in)) |
| { |
| return ntohs(reinterpret_cast<const sockaddr_in*>(&remoteSockAddr) |
| ->sin_port); |
| } |
| if (sockAddrSize == sizeof(sockaddr_in6)) |
| { |
| return ntohs(reinterpret_cast<const sockaddr_in6*>(&remoteSockAddr) |
| ->sin6_port); |
| } |
| return 0; |
| } |
| |
| /** |
| * @brief Read the incoming packet |
| * |
| * Reads the data available on the socket |
| * |
| * @return A tuple with return code and vector with the buffer |
| * In case of success, the vector is populated with the data |
| * available on the socket and return code is 0. |
| * In case of error, the return code is < 0 and vector is set |
| * to size 0. |
| */ |
| std::tuple<int, std::vector<uint8_t>> read() |
| { |
| // cannot use the standard asio reading mechanism because it does not |
| // provide a mechanism to reach down into the depths and use a msghdr |
| std::vector<uint8_t> packet(socket->available()); |
| iovec iov = {packet.data(), packet.size()}; |
| char msgCtrl[1024]; |
| msghdr msg = {&remoteSockAddr, sizeof(remoteSockAddr), &iov, 1, |
| msgCtrl, sizeof(msgCtrl), 0}; |
| |
| ssize_t bytesReceived = recvmsg(socket->native_handle(), &msg, 0); |
| // Read of the packet failed |
| if (bytesReceived < 0) |
| { |
| // something bad happened; bail |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error in recvmsg", |
| phosphor::logging::entry("ERROR=%s", strerror(errno))); |
| return std::make_tuple(-errno, std::vector<uint8_t>()); |
| } |
| // save the size of either ipv4 or i4v6 sockaddr |
| sockAddrSize = msg.msg_namelen; |
| |
| // extract the destination address from the message |
| cmsghdr* cmsg; |
| for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0; |
| cmsg = CMSG_NXTHDR(&msg, cmsg)) |
| { |
| if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) |
| { |
| // save local address from the pktinfo4 |
| pktinfo4 = *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)); |
| } |
| if (cmsg->cmsg_level == IPPROTO_IPV6 && |
| cmsg->cmsg_type == IPV6_PKTINFO) |
| { |
| // save local address from the pktinfo6 |
| pktinfo6 = *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)); |
| } |
| } |
| return std::make_tuple(0, packet); |
| } |
| |
| /** |
| * @brief Write the outgoing packet |
| * |
| * Writes the data in the vector to the socket |
| * |
| * @param [in] inBuffer |
| * The vector would be the buffer of data to write to the socket. |
| * |
| * @return In case of success the return code is the number of bytes |
| * written and return code is < 0 in case of failure. |
| */ |
| int write(const std::vector<uint8_t>& inBuffer) |
| { |
| // in order to make sure packets go back out from the same |
| // IP address they came in on, sendmsg must be used instead |
| // of the boost::asio::ip::send or sendto |
| iovec iov = {const_cast<uint8_t*>(inBuffer.data()), inBuffer.size()}; |
| char msgCtrl[1024]; |
| msghdr msg = {&remoteSockAddr, sockAddrSize, &iov, 1, |
| msgCtrl, sizeof(msgCtrl), 0}; |
| int cmsg_space = 0; |
| cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); |
| if (pktinfo6) |
| { |
| cmsg->cmsg_level = IPPROTO_IPV6; |
| cmsg->cmsg_type = IPV6_PKTINFO; |
| cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); |
| *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo6; |
| cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo)); |
| } |
| else if (pktinfo4) |
| { |
| cmsg->cmsg_level = IPPROTO_IP; |
| cmsg->cmsg_type = IP_PKTINFO; |
| cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); |
| *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo4; |
| cmsg_space += CMSG_SPACE(sizeof(in_pktinfo)); |
| } |
| msg.msg_controllen = cmsg_space; |
| int ret = sendmsg(socket->native_handle(), &msg, 0); |
| if (ret < 0) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error in sendmsg", |
| phosphor::logging::entry("ERROR=%s", strerror(errno))); |
| } |
| return ret; |
| } |
| |
| /** |
| * @brief Returns file descriptor for the socket |
| */ |
| auto getHandle(void) const |
| { |
| return socket->native_handle(); |
| } |
| |
| private: |
| std::shared_ptr<boost::asio::ip::udp::socket> socket; |
| sockaddr_storage remoteSockAddr; |
| socklen_t sockAddrSize; |
| std::optional<in_pktinfo> pktinfo4; |
| std::optional<in6_pktinfo> pktinfo6; |
| }; |
| |
| } // namespace udpsocket |