#pragma once #include #include #include #include #include #include #include #include #include #include #include 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 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(&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(&remoteSockAddr); if (isIpv4InIpv6(sa->sin6_addr)) { std::copy_n(&sa->sin6_addr.s6_addr[v4v6Index], sizeof(remoteIpv4Addr), reinterpret_cast(&remoteIpv4Addr)); } retval = inet_ntop(AF_INET6, &(sa->sin6_addr), ipv6addr, sizeof(ipv6addr)); } if (retval) { return retval; } lg2::error("Error in inet_ntop: {ERROR}", "ERROR", 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(&remoteSockAddr) ->sin_port); } if (sockAddrSize == sizeof(sockaddr_in6)) { return ntohs(reinterpret_cast(&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> 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 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 lg2::error("Error in recvmsg: {ERROR}", "ERROR", strerror(-bytesReceived)); return std::make_tuple(-errno, std::vector()); } // 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(CMSG_DATA(cmsg)); } if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { // save local address from the pktinfo6 pktinfo6 = *reinterpret_cast(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& 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(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(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(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) { lg2::error("Error in sendmsg: {ERROR}", "ERROR", strerror(-ret)); } return ret; } /** * @brief Returns file descriptor for the socket */ auto getHandle(void) const { return socket->native_handle(); } private: std::shared_ptr socket; sockaddr_storage remoteSockAddr; socklen_t sockAddrSize; std::optional pktinfo4; std::optional pktinfo6; }; } // namespace udpsocket