#include "mock_syscall.hpp" #include "util.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include std::map> mock_rtnetlinks; using phosphor::network::InterfaceInfo; std::map mock_if; void phosphor::network::system::mock_clear() { mock_rtnetlinks.clear(); mock_if.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); } 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(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(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& msgs, std::string_view in) { if (const auto& hdrin = *reinterpret_cast(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(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(*i.mac)); } if (i.mtu) { appendRTAttr(msgBuf, IFLA_MTU, stdplus::raw::asView(*i.mtu)); } auto& hdr = *reinterpret_cast(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(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& 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" { 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(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(dlsym(RTLD_NEXT, "ioctl")); return real_ioctl(fd, request, data); } int socket(int domain, int type, int protocol) { static auto real_socket = reinterpret_cast(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(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(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(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(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(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"