#include #include #include #include "test_progs.h" #include "network_helpers.h" #include "net_timestamping.skel.h" #define CG_NAME "/net-timestamping-test" #define NSEC_PER_SEC 1000000000LL static const char addr4_str[] = "127.0.0.1"; static const char addr6_str[] = "::1"; static struct net_timestamping *skel; static const int cfg_payload_len = 30; static struct timespec usr_ts; static u64 delay_tolerance_nsec = 10000000000; /* 10 seconds */ int SK_TS_SCHED; int SK_TS_TXSW; int SK_TS_ACK; static int64_t timespec_to_ns64(struct timespec *ts) { return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; } static void validate_key(int tskey, int tstype) { static int expected_tskey = -1; if (tstype == SCM_TSTAMP_SCHED) expected_tskey = cfg_payload_len - 1; ASSERT_EQ(expected_tskey, tskey, "tskey mismatch"); expected_tskey = tskey; } static void validate_timestamp(struct timespec *cur, struct timespec *prev) { int64_t cur_ns, prev_ns; cur_ns = timespec_to_ns64(cur); prev_ns = timespec_to_ns64(prev); ASSERT_LT(cur_ns - prev_ns, delay_tolerance_nsec, "latency"); } static void test_socket_timestamp(struct scm_timestamping *tss, int tstype, int tskey) { static struct timespec prev_ts; validate_key(tskey, tstype); switch (tstype) { case SCM_TSTAMP_SCHED: validate_timestamp(&tss->ts[0], &usr_ts); SK_TS_SCHED += 1; break; case SCM_TSTAMP_SND: validate_timestamp(&tss->ts[0], &prev_ts); SK_TS_TXSW += 1; break; case SCM_TSTAMP_ACK: validate_timestamp(&tss->ts[0], &prev_ts); SK_TS_ACK += 1; break; } prev_ts = tss->ts[0]; } static void test_recv_errmsg_cmsg(struct msghdr *msg) { struct sock_extended_err *serr = NULL; struct scm_timestamping *tss = NULL; struct cmsghdr *cm; for (cm = CMSG_FIRSTHDR(msg); cm && cm->cmsg_len; cm = CMSG_NXTHDR(msg, cm)) { if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_TIMESTAMPING) { tss = (void *)CMSG_DATA(cm); } else if ((cm->cmsg_level == SOL_IP && cm->cmsg_type == IP_RECVERR) || (cm->cmsg_level == SOL_IPV6 && cm->cmsg_type == IPV6_RECVERR) || (cm->cmsg_level == SOL_PACKET && cm->cmsg_type == PACKET_TX_TIMESTAMP)) { serr = (void *)CMSG_DATA(cm); ASSERT_EQ(serr->ee_origin, SO_EE_ORIGIN_TIMESTAMPING, "cmsg type"); } if (serr && tss) test_socket_timestamp(tss, serr->ee_info, serr->ee_data); } } static bool socket_recv_errmsg(int fd) { static char ctrl[1024 /* overprovision*/]; char data[cfg_payload_len]; static struct msghdr msg; struct iovec entry; int n = 0; memset(&msg, 0, sizeof(msg)); memset(&entry, 0, sizeof(entry)); memset(ctrl, 0, sizeof(ctrl)); entry.iov_base = data; entry.iov_len = cfg_payload_len; msg.msg_iov = &entry; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = ctrl; msg.msg_controllen = sizeof(ctrl); n = recvmsg(fd, &msg, MSG_ERRQUEUE); if (n == -1) ASSERT_EQ(errno, EAGAIN, "recvmsg MSG_ERRQUEUE"); if (n >= 0) test_recv_errmsg_cmsg(&msg); return n == -1; } static void test_socket_timestamping(int fd) { while (!socket_recv_errmsg(fd)); ASSERT_EQ(SK_TS_SCHED, 1, "SCM_TSTAMP_SCHED"); ASSERT_EQ(SK_TS_TXSW, 1, "SCM_TSTAMP_SND"); ASSERT_EQ(SK_TS_ACK, 1, "SCM_TSTAMP_ACK"); SK_TS_SCHED = 0; SK_TS_TXSW = 0; SK_TS_ACK = 0; } static void test_tcp(int family, bool enable_socket_timestamping) { struct net_timestamping__bss *bss; char buf[cfg_payload_len]; int sfd = -1, cfd = -1; unsigned int sock_opt; struct netns_obj *ns; int cg_fd; int ret; cg_fd = test__join_cgroup(CG_NAME); if (!ASSERT_OK_FD(cg_fd, "join cgroup")) return; ns = netns_new("net_timestamping_ns", true); if (!ASSERT_OK_PTR(ns, "create ns")) goto out; skel = net_timestamping__open_and_load(); if (!ASSERT_OK_PTR(skel, "open and load skel")) goto out; if (!ASSERT_OK(net_timestamping__attach(skel), "attach skel")) goto out; skel->links.skops_sockopt = bpf_program__attach_cgroup(skel->progs.skops_sockopt, cg_fd); if (!ASSERT_OK_PTR(skel->links.skops_sockopt, "attach cgroup")) goto out; bss = skel->bss; memset(bss, 0, sizeof(*bss)); skel->bss->monitored_pid = getpid(); sfd = start_server(family, SOCK_STREAM, family == AF_INET6 ? addr6_str : addr4_str, 0, 0); if (!ASSERT_OK_FD(sfd, "start_server")) goto out; cfd = connect_to_fd(sfd, 0); if (!ASSERT_OK_FD(cfd, "connect_to_fd_server")) goto out; if (enable_socket_timestamping) { sock_opt = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_TX_SCHED | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_TX_ACK; ret = setsockopt(cfd, SOL_SOCKET, SO_TIMESTAMPING, (char *) &sock_opt, sizeof(sock_opt)); if (!ASSERT_OK(ret, "setsockopt SO_TIMESTAMPING")) goto out; ret = clock_gettime(CLOCK_REALTIME, &usr_ts); if (!ASSERT_OK(ret, "get user time")) goto out; } ret = write(cfd, buf, sizeof(buf)); if (!ASSERT_EQ(ret, sizeof(buf), "send to server")) goto out; if (enable_socket_timestamping) test_socket_timestamping(cfd); ASSERT_EQ(bss->nr_active, 1, "nr_active"); ASSERT_EQ(bss->nr_snd, 2, "nr_snd"); ASSERT_EQ(bss->nr_sched, 1, "nr_sched"); ASSERT_EQ(bss->nr_txsw, 1, "nr_txsw"); ASSERT_EQ(bss->nr_ack, 1, "nr_ack"); out: if (sfd >= 0) close(sfd); if (cfd >= 0) close(cfd); net_timestamping__destroy(skel); netns_free(ns); close(cg_fd); } void test_net_timestamping(void) { if (test__start_subtest("INET4: bpf timestamping")) test_tcp(AF_INET, false); if (test__start_subtest("INET4: bpf and socket timestamping")) test_tcp(AF_INET, true); if (test__start_subtest("INET6: bpf timestamping")) test_tcp(AF_INET6, false); if (test__start_subtest("INET6: bpf and socket timestamping")) test_tcp(AF_INET6, true); }