-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
selftests/bpf: add simple bpf tests in the tx path for timestamping f…
…eature BPF program calculates a couple of latency deltas between each tx timestamping callbacks. It can be used in the real world to diagnose the kernel behaviour in the tx path. Check the safety issues by accessing a few bpf calls in bpf_test_access_bpf_calls() which are implemented in the patch 3 and 4. Check if the bpf timestamping can co-exist with socket timestamping. There remains a few realistic things[1][2] to highlight: 1. in general a packet may pass through multiple qdiscs. For instance with bonding or tunnel virtual devices in the egress path. 2. packets may be resent, in which case an ACK might precede a repeat SCHED and SND. 3. erroneous or malicious peers may also just never send an ACK. [1]: https://lore.kernel.org/all/[email protected]/ [2]: https://lore.kernel.org/all/[email protected]/ Signed-off-by: Jason Xing <[email protected]>
- Loading branch information
Showing
2 changed files
with
475 additions
and
0 deletions.
There are no files selected for viewing
231 changes: 231 additions & 0 deletions
231
tools/testing/selftests/bpf/prog_tests/net_timestamping.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
#include <linux/net_tstamp.h> | ||
#include <sys/time.h> | ||
#include <linux/errqueue.h> | ||
#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 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_TRUE((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 = &usr_ts; | ||
|
||
validate_key(tskey, tstype); | ||
|
||
switch (tstype) { | ||
case SCM_TSTAMP_SCHED: | ||
validate_timestamp(&tss->ts[0], prev_ts); | ||
SK_TS_SCHED = 1; | ||
SK_TS_TXSW = SK_TS_ACK = 0; | ||
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"); | ||
} | ||
|
||
static void test_tcp(int family) | ||
{ | ||
struct net_timestamping__bss *bss = skel->bss; | ||
char buf[cfg_payload_len]; | ||
int sfd = -1, cfd = -1; | ||
unsigned int sock_opt; | ||
int ret; | ||
|
||
memset(bss, 0, sizeof(*bss)); | ||
|
||
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; | ||
|
||
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; | ||
|
||
/* Test if socket timestamping works correctly even with bpf | ||
* extension enabled. | ||
*/ | ||
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); | ||
} | ||
|
||
void test_net_timestamping(void) | ||
{ | ||
struct netns_obj *ns; | ||
int cg_fd; | ||
|
||
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 done; | ||
|
||
skel = net_timestamping__open_and_load(); | ||
if (!ASSERT_OK_PTR(skel, "open and load skel")) | ||
goto done; | ||
|
||
if (!ASSERT_OK(net_timestamping__attach(skel), "attach skel")) | ||
goto done; | ||
|
||
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 done; | ||
|
||
test_tcp(AF_INET6); | ||
test_tcp(AF_INET); | ||
|
||
done: | ||
net_timestamping__destroy(skel); | ||
netns_free(ns); | ||
close(cg_fd); | ||
} |
Oops, something went wrong.