mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
240 lines
5.7 KiB
C
240 lines
5.7 KiB
C
![]() |
#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 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);
|
||
|
}
|