diff --git a/test/Makefile b/test/Makefile index 5c9a5538..70c2714b 100644 --- a/test/Makefile +++ b/test/Makefile @@ -70,6 +70,9 @@ $(INITRAMFS)/lib/x86_64-linux-gnu: | $(VDSO_LIB) @cp -L /lib/x86_64-linux-gnu/libevent-2.1.so.7 $@ @# required for VDSO @cp -L $(VDSO_LIB) $@ + @# required for netlink test + @cp -L /lib/x86_64-linux-gnu/libnl-3.so.200 $@ + @cp -L /lib/x86_64-linux-gnu/libnl-route-3.so.200 $@ $(VDSO_LIB): | $(VDSO_DIR) $(BINARY_CACHE_DIR)/vdso64.so @# TODO: use a custom compiled vdso.so file in the future. diff --git a/test/apps/network/Makefile b/test/apps/network/Makefile index c603a781..1f5a12ee 100644 --- a/test/apps/network/Makefile +++ b/test/apps/network/Makefile @@ -2,4 +2,4 @@ include ../test_common.mk -EXTRA_C_FLAGS := +EXTRA_C_FLAGS := -I/usr/include/libnl3 -lnl-3 -lnl-route-3 diff --git a/test/apps/network/netlink_route.c b/test/apps/network/netlink_route.c new file mode 100644 index 00000000..2b2ae9eb --- /dev/null +++ b/test/apps/network/netlink_route.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: MPL-2.0 + +#include +#include +#include +#include +#include +#include +#include + +#include "test.h" + +#define ETHER_NAME "eth0" +#define LOOPBACK_NAME "lo" + +#define SUCC(expr) ((expr), 0) + +int find_lo_and_eth0_by_libc(struct if_nameindex *if_ni) +{ + int found_links = 0; + + for (struct if_nameindex *i = if_ni; + !(i->if_index == 0 && i->if_name == NULL); i++) { + if (strcmp(i->if_name, LOOPBACK_NAME) == 0) { + found_links++; + } + + if (strcmp(i->if_name, ETHER_NAME) == 0) { + found_links++; + } + } + + return found_links; +} + +FN_TEST(if_nameindex) +{ + struct if_nameindex *if_ni; + + CHECK_WITH(SUCC(if_ni = if_nameindex()), if_ni != NULL); + + TEST_RES(find_lo_and_eth0_by_libc(if_ni), _ret == 2); + + if_freenameindex(if_ni); +} +END_TEST() + +void find_lo_and_eth0_by_libnl(struct nl_object *obj, void *arg) +{ + int *found_links = (int *)arg; + struct rtnl_link *link = (struct rtnl_link *)obj; + + if (strcmp(rtnl_link_get_name(link), LOOPBACK_NAME) == 0) { + *found_links += 1; + } + + if (strcmp(rtnl_link_get_name(link), ETHER_NAME) == 0) { + *found_links += 1; + } +} + +FN_TEST(get_link_by_libnl) +{ + struct nl_sock *sock; + struct nl_cache *link_cache; + + // 1. Create netlink socket and connect + sock = nl_socket_alloc(); + TEST_RES(nl_connect(sock, NETLINK_ROUTE), _ret >= 0); + + // 2. Allocate and retrieve link cache + TEST_RES(rtnl_link_alloc_cache(sock, AF_UNSPEC, &link_cache), + _ret >= 0); + + // 3. Iterate over all links to find lo and eth0 + int found_links = 0; + TEST_RES(SUCC(nl_cache_foreach(link_cache, find_lo_and_eth0_by_libnl, + &found_links)), + found_links == 2); + + // 4. Cleanup + nl_cache_free(link_cache); + nl_close(sock); + nl_socket_free(sock); +} +END_TEST() + +void find_loopback_address(struct nl_object *obj, void *arg) +{ + int *found_loopback = (int *)arg; + struct rtnl_addr *addr = (struct rtnl_addr *)obj; + struct nl_addr *local; + char buf[INET_ADDRSTRLEN]; + + int family = rtnl_addr_get_family(addr); + if (family != AF_INET) { + return; + } + + local = rtnl_addr_get_local(addr); + if (local) { + nl_addr2str(local, buf, sizeof(buf)); + if (strcmp(buf, "127.0.0.1/8") == 0) { + *found_loopback = 1; + } + } +} + +FN_TEST(get_loopback_address) +{ + struct nl_sock *sock; + struct nl_cache *addr_cache; + + // 1. Create netlink socket and connect + sock = nl_socket_alloc(); + TEST_RES(nl_connect(sock, NETLINK_ROUTE), _ret >= 0); + + // 2. Allocate and retrieve address cache + TEST_RES(rtnl_addr_alloc_cache(sock, &addr_cache), _ret >= 0); + + // 3. Iterate over all addresses to find loopback address + int found_loopback = 0; + TEST_RES(SUCC(nl_cache_foreach(addr_cache, find_loopback_address, + &found_loopback)), + found_loopback == 1); + + // 4. Cleanup + nl_cache_free(addr_cache); + nl_close(sock); + nl_socket_free(sock); +} +END_TEST() + +int find_new_addr_until_done(char *buffer, size_t len, int *found_new_addr) +{ + struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; + + for (; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { + if (nlh->nlmsg_type == NLMSG_DONE) { + return *found_new_addr ? 1 : -1; + } + + if (nlh->nlmsg_type == RTM_NEWADDR) { + *found_new_addr += 1; + } else { + return -1; + } + } + + return 0; +} + +FN_TEST(get_addr_error) +{ +#define BUFFER_SIZE 8192 + + struct nl_req { + struct nlmsghdr hdr; + struct ifaddrmsg ifa; + }; + + int sock_fd; + struct sockaddr_nl sa; + char buffer[BUFFER_SIZE]; + + sock_fd = TEST_SUCC(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + memset(&sa, 0, sizeof(sa)); + sa.nl_family = AF_NETLINK; + + TEST_SUCC(bind(sock_fd, (struct sockaddr *)&sa, sizeof(sa))); + + // 1. Without NLM_F_DUMP flag + struct nl_req req; + memset(&req, 0, sizeof(req)); + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.hdr.nlmsg_type = RTM_GETADDR; + req.hdr.nlmsg_flags = NLM_F_REQUEST; + req.hdr.nlmsg_seq = 1; + req.ifa.ifa_family = AF_UNSPEC; + + struct iovec iov = { &req, req.hdr.nlmsg_len }; + struct msghdr msg = { &sa, sizeof(sa), &iov, 1, NULL, 0, 0 }; + + TEST_SUCC(sendmsg(sock_fd, &msg, 0)); + TEST_RES(recv(sock_fd, buffer, BUFFER_SIZE, 0), + ((struct nlmsghdr *)buffer)->nlmsg_type == NLMSG_ERROR && + ((struct nlmsgerr *)NLMSG_DATA(buffer))->error == + -EOPNOTSUPP); + + // 2. Invalid required index + req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK; + req.ifa.ifa_index = 9999; + TEST_SUCC(sendmsg(sock_fd, &msg, 0)); + + int found_new_addr = 0; + while (1) { + size_t recv_len = + TEST_SUCC(recv(sock_fd, buffer, BUFFER_SIZE, 0)); + + int found_done = TEST_SUCC(find_new_addr_until_done( + buffer, recv_len, &found_new_addr)); + + if (found_done != 0) { + break; + } + } + + // 3. Invalid required family + req.ifa.ifa_family = 255; + req.ifa.ifa_index = 0; + TEST_SUCC(sendmsg(sock_fd, &msg, 0)); + + found_new_addr = 0; + while (1) { + size_t recv_len = + TEST_SUCC(recv(sock_fd, buffer, BUFFER_SIZE, 0)); + + int found_done = TEST_SUCC(find_new_addr_until_done( + buffer, recv_len, &found_new_addr)); + + if (found_done != 0) { + break; + } + } +} +END_TEST() diff --git a/test/apps/test_common.mk b/test/apps/test_common.mk index 466576b7..64afa40d 100644 --- a/test/apps/test_common.mk +++ b/test/apps/test_common.mk @@ -22,7 +22,7 @@ $(OBJ_OUTPUT_DIR) $(DEP_OUTPUT_DIR): @mkdir -p $@ $(OBJ_OUTPUT_DIR)/%: %.c | $(OBJ_OUTPUT_DIR) $(DEP_OUTPUT_DIR) - @$(CC) $(C_FLAGS) $(EXTRA_C_FLAGS) $< -o $@ \ + @$(CC) $(C_FLAGS) $< -o $@ $(EXTRA_C_FLAGS) \ -MMD -MF $(DEP_OUTPUT_DIR)/$*.d @echo "CC <= $@" diff --git a/test/syscall_test/Makefile b/test/syscall_test/Makefile index 8f5b5a32..edf45106 100644 --- a/test/syscall_test/Makefile +++ b/test/syscall_test/Makefile @@ -46,6 +46,7 @@ TESTS ?= \ sigaction_test \ sigaltstack_test \ signalfd_test \ + socket_netlink_route_test \ stat_test \ stat_times_test \ statfs_test \ diff --git a/test/syscall_test/blocklists/socket_netlink_route_test b/test/syscall_test/blocklists/socket_netlink_route_test new file mode 100644 index 00000000..dda3c257 --- /dev/null +++ b/test/syscall_test/blocklists/socket_netlink_route_test @@ -0,0 +1,12 @@ +NetlinkRouteTest.MsgHdrMsgUnsuppType +NetlinkRouteTest.MsgHdrMsgTrunc +NetlinkRouteTest.MsgTruncMsgHdrMsgTrunc +NetlinkRouteTest.ControlMessageIgnored +NetlinkRouteTest.AddAddr +NetlinkRouteTest.GetRouteDump +NetlinkRouteTest.GetRouteRequest +NetlinkRouteTest.RecvmsgTrunc +NetlinkRouteTest.RecvmsgTruncPeek +NetlinkRouteTest.NoPasscredNoCreds +NetlinkRouteTest.PasscredCreds +NetlinkRouteTest/SockOptTest.GetSockOpt/*