| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// Internal templatized implementation of if_nameindex for Linux. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #ifndef LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_IMPL_H | |
| #define LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_IMPL_H | |
| #include "hdr/errno_macros.h" | |
| #include "hdr/net_if_macros.h" | |
| #include "hdr/sys_socket_macros.h" | |
| #include "hdr/types/socklen_t.h" | |
| #include "hdr/types/ssize_t.h" | |
| #include "hdr/types/struct_if_nameindex.h" | |
| #include "src/__support/CPP/new.h" | |
| #include "src/__support/CPP/scope.h" | |
| #include "src/__support/CPP/span.h" | |
| #include "src/__support/alloc-checker.h" | |
| #include "src/__support/blockstore.h" | |
| #include "src/__support/common.h" | |
| #include "src/__support/error_or.h" | |
| #include "src/string/memory_utils/inline_memcpy.h" | |
| #include "src/string/string_utils.h" | |
| #include <linux/if_link.h> | |
| #include <linux/netlink.h> | |
| #include <linux/rtnetlink.h> | |
| namespace LIBC_NAMESPACE_DECL { | |
| namespace net { | |
| namespace detail { | |
| /// Sends a RTM_GETLINK dump request packet on the given socket. The kernel will | |
| /// respond with one or more messages containing the list of network interfaces | |
| /// and their attributes. | |
| template <typename Policy> | |
| LIBC_INLINE ErrorOr<ssize_t> send_netlink_dump_request(int sockfd) { | |
| // The message consists of a standard netlink header followed by the | |
| // RTM_GETLINK payload. | |
| struct { | |
| struct nlmsghdr nlh; | |
| struct ifinfomsg ifm; | |
| } req = {}; | |
| static_assert(sizeof(req) >= NLMSG_LENGTH(sizeof(req.ifm))); | |
| req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifm)); | |
| req.nlh.nlmsg_type = RTM_GETLINK; | |
| req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; | |
| req.ifm.ifi_family = AF_UNSPEC; | |
| return Policy::sendto(sockfd, &req, req.nlh.nlmsg_len, 0, nullptr, 0); | |
| } | |
| /// A reasonable buffer size for netlink messages (see NLMSG_GOODSIZE in the | |
| /// kernel). | |
| constexpr size_t NLMSG_BUFFER_SIZE = 8192; | |
| struct InterfaceEntry { | |
| unsigned int index; | |
| uint8_t name_length; // Length of next field. | |
| char name[IF_NAMESIZE - 1]; // No null terminator. | |
| static_assert(IF_NAMESIZE - 1 < (1u << 8)); | |
| }; | |
| LIBC_INLINE ErrorOr<void> | |
| parse_netlink_messages(cpp::span<uint8_t> buf, | |
| BlockStore<InterfaceEntry, 16> &store) { | |
| size_t len = buf.size(); | |
| for (auto *nh = reinterpret_cast<struct nlmsghdr *>(buf.data()); | |
| NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { | |
| if (nh->nlmsg_type == NLMSG_DONE) | |
| break; | |
| if (nh->nlmsg_type == NLMSG_ERROR) { | |
| if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) | |
| return Error(EINVAL); | |
| auto *err = reinterpret_cast<struct nlmsgerr *>(NLMSG_DATA(nh)); | |
| if (err->error == 0) { | |
| // Zero means an ACK, which we shouldn't get because we didn't ask for | |
| // it... | |
| continue; | |
| } | |
| return Error(-err->error); | |
| } | |
| if (nh->nlmsg_type != RTM_NEWLINK) | |
| continue; | |
| if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) | |
| continue; | |
| auto *ifm = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(nh)); | |
| size_t attrlen = nh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg)); | |
| struct rtattr *rta = IFLA_RTA(ifm); | |
| for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) { | |
| if (rta->rta_type != IFLA_IFNAME) | |
| continue; | |
| size_t rta_payload_len = RTA_PAYLOAD(rta); | |
| const char *name_data = reinterpret_cast<const char *>(RTA_DATA(rta)); | |
| size_t name_len = internal::strnlen(name_data, rta_payload_len); | |
| // Defensive check: kernel should not be providing us with names that | |
| // don't fit. | |
| if (name_len >= IF_NAMESIZE) | |
| name_len = IF_NAMESIZE - 1; | |
| InterfaceEntry entry; | |
| entry.index = static_cast<unsigned int>(ifm->ifi_index); | |
| entry.name_length = static_cast<uint8_t>(name_len); | |
| inline_memcpy(entry.name, name_data, name_len); | |
| if (!store.push_back(entry)) | |
| return Error(ENOBUFS); | |
| break; | |
| } | |
| } | |
| return {}; | |
| } | |
| LIBC_INLINE ErrorOr<struct if_nameindex *> | |
| build_if_nameindex_list(BlockStore<InterfaceEntry, 16> &store) { | |
| size_t count = 0; | |
| size_t strings_size = 0; | |
| for (const InterfaceEntry &entry : store) { | |
| ++count; | |
| strings_size += entry.name_length + 1; | |
| } | |
| size_t total_size = (count + 1) * sizeof(struct if_nameindex) + strings_size; | |
| AllocChecker ac; | |
| uint8_t *buffer = new (ac) uint8_t[total_size]; | |
| if (!ac) | |
| return Error(ENOBUFS); | |
| cpp::span<struct if_nameindex> result( | |
| reinterpret_cast<struct if_nameindex *>(buffer), count + 1); | |
| char *str_ptr = reinterpret_cast<char *>(result.end()); | |
| size_t idx = 0; | |
| for (const InterfaceEntry &entry : store) { | |
| result[idx].if_index = entry.index; | |
| result[idx].if_name = str_ptr; | |
| inline_memcpy(str_ptr, entry.name, entry.name_length); | |
| str_ptr[entry.name_length] = '\0'; | |
| str_ptr += entry.name_length + 1; | |
| ++idx; | |
| } | |
| result[count].if_index = 0; | |
| result[count].if_name = nullptr; | |
| return result.data(); | |
| } | |
| } // namespace detail | |
| template <typename Policy> | |
| LIBC_INLINE ErrorOr<struct if_nameindex *> if_nameindex() { | |
| ErrorOr<int> fd_or_err = | |
| Policy::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); | |
| if (!fd_or_err.has_value()) | |
| return Error(fd_or_err.error()); | |
| int fd = *fd_or_err; | |
| cpp::scope_exit close_fd([fd]() { Policy::close(fd); }); | |
| ErrorOr<ssize_t> send_res = detail::send_netlink_dump_request<Policy>(fd); | |
| if (!send_res.has_value()) | |
| return Error(send_res.error()); | |
| // TODO: Figure out if we need to dynamically allocate a buffer. | |
| alignas(struct nlmsghdr) uint8_t buf[detail::NLMSG_BUFFER_SIZE]; | |
| ErrorOr<ssize_t> recv_res = | |
| Policy::recvfrom(fd, buf, sizeof(buf), 0, nullptr, nullptr); | |
| if (!recv_res.has_value()) | |
| return Error(recv_res.error()); | |
| close_fd.release(); | |
| if (ErrorOr<int> close_res = Policy::close(fd); !close_res.has_value()) | |
| return Error(close_res.error()); | |
| // TODO: Read more than one message. | |
| // TODO: Deduplicate interfaces to handle restarts. | |
| BlockStore<detail::InterfaceEntry, 16> store; | |
| cpp::scope_exit destroy_store( | |
| [&store]() { BlockStore<detail::InterfaceEntry, 16>::destroy(&store); }); | |
| if (ErrorOr<void> parse_res = detail::parse_netlink_messages( | |
| {buf, static_cast<size_t>(*recv_res)}, store); | |
| !parse_res.has_value()) | |
| return Error(parse_res.error()); | |
| return detail::build_if_nameindex_list(store); | |
| } | |
| } // namespace net | |
| } // namespace LIBC_NAMESPACE_DECL | |
| #endif // LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_IMPL_H |