| #include "mock_syscall.hpp" |
| |
| #include "util.hpp" |
| |
| #include <arpa/inet.h> |
| #include <dlfcn.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <net/ethernet.h> |
| #include <net/if.h> |
| #include <netinet/in.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cstdarg> |
| #include <cstdio> |
| #include <cstring> |
| #include <map> |
| #include <queue> |
| #include <stdexcept> |
| #include <stdplus/raw.hpp> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| std::map<int, std::queue<std::string>> mock_rtnetlinks; |
| |
| using phosphor::network::system::InterfaceInfo; |
| |
| std::map<std::string, InterfaceInfo> mock_if; |
| std::map<int, std::string> mock_if_indextoname; |
| |
| void phosphor::network::system::mock_clear() |
| { |
| mock_rtnetlinks.clear(); |
| mock_if.clear(); |
| mock_if_indextoname.clear(); |
| } |
| |
| void phosphor::network::system::mock_addIF(const InterfaceInfo& info) |
| { |
| if (info.idx == 0) |
| { |
| throw std::invalid_argument("Bad interface index"); |
| } |
| for (const auto& [_, iinfo] : mock_if) |
| { |
| if (iinfo.idx == info.idx || iinfo.name == info.name) |
| { |
| throw std::invalid_argument("Interface already exists"); |
| } |
| } |
| mock_if.emplace(info.name.value(), info); |
| mock_if_indextoname.emplace(info.idx, info.name.value()); |
| } |
| |
| void validateMsgHdr(const struct msghdr* msg) |
| { |
| if (msg->msg_namelen != sizeof(sockaddr_nl)) |
| { |
| fprintf(stderr, "bad namelen: %u\n", msg->msg_namelen); |
| abort(); |
| } |
| const auto& from = *reinterpret_cast<sockaddr_nl*>(msg->msg_name); |
| if (from.nl_family != AF_NETLINK) |
| { |
| fprintf(stderr, "recvmsg bad family data\n"); |
| abort(); |
| } |
| if (msg->msg_iovlen != 1) |
| { |
| fprintf(stderr, "recvmsg unsupported iov configuration\n"); |
| abort(); |
| } |
| } |
| |
| void appendRTAttr(std::string& msgBuf, unsigned short type, |
| std::string_view data) |
| { |
| const auto rta_begin = msgBuf.size(); |
| msgBuf.append(RTA_SPACE(data.size()), '\0'); |
| auto& rta = *reinterpret_cast<rtattr*>(msgBuf.data() + rta_begin); |
| rta.rta_len = RTA_LENGTH(data.size()); |
| rta.rta_type = type; |
| std::copy(data.begin(), data.end(), |
| msgBuf.data() + rta_begin + RTA_LENGTH(0)); |
| } |
| |
| ssize_t sendmsg_link_dump(std::queue<std::string>& msgs, std::string_view in) |
| { |
| if (const auto& hdrin = *reinterpret_cast<const nlmsghdr*>(in.data()); |
| hdrin.nlmsg_type != RTM_GETLINK) |
| { |
| return 0; |
| } |
| |
| std::string msgBuf; |
| msgBuf.reserve(8192); |
| for (const auto& [name, i] : mock_if) |
| { |
| if (msgBuf.size() > 4096) |
| { |
| msgs.emplace(std::move(msgBuf)); |
| } |
| const auto nlbegin = msgBuf.size(); |
| msgBuf.append(NLMSG_SPACE(sizeof(ifinfomsg)), '\0'); |
| { |
| auto& info = *reinterpret_cast<ifinfomsg*>(msgBuf.data() + nlbegin + |
| NLMSG_HDRLEN); |
| info.ifi_index = i.idx; |
| info.ifi_flags = i.flags; |
| } |
| if (i.name) |
| { |
| appendRTAttr(msgBuf, IFLA_IFNAME, {name.data(), name.size() + 1}); |
| } |
| if (i.mac) |
| { |
| appendRTAttr(msgBuf, IFLA_ADDRESS, |
| stdplus::raw::asView<char>(*i.mac)); |
| } |
| if (i.mtu) |
| { |
| appendRTAttr(msgBuf, IFLA_MTU, stdplus::raw::asView<char>(*i.mtu)); |
| } |
| auto& hdr = *reinterpret_cast<nlmsghdr*>(msgBuf.data() + nlbegin); |
| hdr.nlmsg_len = msgBuf.size() - nlbegin; |
| hdr.nlmsg_type = RTM_NEWLINK; |
| hdr.nlmsg_flags = NLM_F_MULTI; |
| msgBuf.resize(NLMSG_ALIGN(msgBuf.size()), '\0'); |
| } |
| const auto nlbegin = msgBuf.size(); |
| msgBuf.append(NLMSG_SPACE(0), '\0'); |
| auto& hdr = *reinterpret_cast<nlmsghdr*>(msgBuf.data() + nlbegin); |
| hdr.nlmsg_len = NLMSG_LENGTH(0); |
| hdr.nlmsg_type = NLMSG_DONE; |
| hdr.nlmsg_flags = NLM_F_MULTI; |
| |
| msgs.emplace(std::move(msgBuf)); |
| return in.size(); |
| } |
| |
| ssize_t sendmsg_ack(std::queue<std::string>& msgs, std::string_view in) |
| { |
| nlmsgerr ack{}; |
| nlmsghdr hdr{}; |
| hdr.nlmsg_len = NLMSG_LENGTH(sizeof(ack)); |
| hdr.nlmsg_type = NLMSG_ERROR; |
| auto& out = msgs.emplace(hdr.nlmsg_len, '\0'); |
| memcpy(out.data(), &hdr, sizeof(hdr)); |
| memcpy(NLMSG_DATA(out.data()), &ack, sizeof(ack)); |
| return in.size(); |
| } |
| |
| extern "C" { |
| |
| char* if_indextoname(unsigned ifindex, char* ifname) |
| { |
| auto it = mock_if_indextoname.find(ifindex); |
| if (it == mock_if_indextoname.end()) |
| { |
| errno = ENXIO; |
| return NULL; |
| } |
| return std::strcpy(ifname, it->second.c_str()); |
| } |
| |
| int ioctl(int fd, unsigned long int request, ...) |
| { |
| va_list vl; |
| va_start(vl, request); |
| void* data = va_arg(vl, void*); |
| va_end(vl); |
| |
| auto req = reinterpret_cast<ifreq*>(data); |
| if (request == SIOCGIFFLAGS) |
| { |
| auto it = mock_if.find(req->ifr_name); |
| if (it == mock_if.end()) |
| { |
| errno = ENXIO; |
| return -1; |
| } |
| req->ifr_flags = it->second.flags; |
| return 0; |
| } |
| else if (request == SIOCGIFMTU) |
| { |
| auto it = mock_if.find(req->ifr_name); |
| if (it == mock_if.end()) |
| { |
| errno = ENXIO; |
| return -1; |
| } |
| if (!it->second.mtu) |
| { |
| errno = EOPNOTSUPP; |
| return -1; |
| } |
| req->ifr_mtu = *it->second.mtu; |
| return 0; |
| } |
| |
| static auto real_ioctl = |
| reinterpret_cast<decltype(&ioctl)>(dlsym(RTLD_NEXT, "ioctl")); |
| return real_ioctl(fd, request, data); |
| } |
| |
| int socket(int domain, int type, int protocol) |
| { |
| static auto real_socket = |
| reinterpret_cast<decltype(&socket)>(dlsym(RTLD_NEXT, "socket")); |
| int fd = real_socket(domain, type, protocol); |
| if (domain == AF_NETLINK && !(type & SOCK_RAW)) |
| { |
| fprintf(stderr, "Netlink sockets must be RAW\n"); |
| abort(); |
| } |
| if (domain == AF_NETLINK && protocol == NETLINK_ROUTE) |
| { |
| mock_rtnetlinks[fd] = {}; |
| } |
| return fd; |
| } |
| |
| int close(int fd) |
| { |
| auto it = mock_rtnetlinks.find(fd); |
| if (it != mock_rtnetlinks.end()) |
| { |
| mock_rtnetlinks.erase(it); |
| } |
| |
| static auto real_close = |
| reinterpret_cast<decltype(&close)>(dlsym(RTLD_NEXT, "close")); |
| return real_close(fd); |
| } |
| |
| ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags) |
| { |
| auto it = mock_rtnetlinks.find(sockfd); |
| if (it == mock_rtnetlinks.end()) |
| { |
| static auto real_sendmsg = |
| reinterpret_cast<decltype(&sendmsg)>(dlsym(RTLD_NEXT, "sendmsg")); |
| return real_sendmsg(sockfd, msg, flags); |
| } |
| auto& msgs = it->second; |
| |
| validateMsgHdr(msg); |
| if (!msgs.empty()) |
| { |
| fprintf(stderr, "Unread netlink responses\n"); |
| abort(); |
| } |
| |
| ssize_t ret; |
| std::string_view iov(reinterpret_cast<char*>(msg->msg_iov[0].iov_base), |
| msg->msg_iov[0].iov_len); |
| |
| ret = sendmsg_link_dump(msgs, iov); |
| if (ret != 0) |
| { |
| return ret; |
| } |
| |
| ret = sendmsg_ack(msgs, iov); |
| if (ret != 0) |
| { |
| return ret; |
| } |
| |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags) |
| { |
| auto it = mock_rtnetlinks.find(sockfd); |
| if (it == mock_rtnetlinks.end()) |
| { |
| static auto real_recvmsg = |
| reinterpret_cast<decltype(&recvmsg)>(dlsym(RTLD_NEXT, "recvmsg")); |
| return real_recvmsg(sockfd, msg, flags); |
| } |
| auto& msgs = it->second; |
| |
| validateMsgHdr(msg); |
| constexpr size_t required_buf_size = 8192; |
| if (msg->msg_iov[0].iov_len < required_buf_size) |
| { |
| fprintf(stderr, "recvmsg iov too short: %zu\n", |
| msg->msg_iov[0].iov_len); |
| abort(); |
| } |
| if (msgs.empty()) |
| { |
| fprintf(stderr, "No pending netlink responses\n"); |
| abort(); |
| } |
| |
| ssize_t ret = 0; |
| auto data = reinterpret_cast<char*>(msg->msg_iov[0].iov_base); |
| while (!msgs.empty()) |
| { |
| const auto& msg = msgs.front(); |
| if (NLMSG_ALIGN(ret) + msg.size() > required_buf_size) |
| { |
| break; |
| } |
| ret = NLMSG_ALIGN(ret); |
| memcpy(data + ret, msg.data(), msg.size()); |
| ret += msg.size(); |
| msgs.pop(); |
| } |
| return ret; |
| } |
| |
| } // extern "C" |