Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/net: make so_txtime more robust to timer variance

The SO_TXTIME test depends on accurate timers. In some virtualized
environments the test has been reported to be flaky. This is easily
reproduced by disabling kvm acceleration in Qemu.

Allow greater variance in a run and retry to further reduce flakiness.

Observed errors are one of two kinds: either the packet arrives too
early or late at recv(), or it was dropped in the qdisc itself and the
recv() call times out.

In the latter case, the qdisc queues a notification to the error
queue of the send socket. Also explicitly report this cause.

Link: https://lore.kernel.org/netdev/CA+FuTSdYOnJCsGuj43xwV1jxvYsaoa_LzHQF9qMyhrkLrivxKw@mail.gmail.com
Reported-by: Naresh Kamboju <naresh.kamboju@linaro.org>
Signed-off-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: Jakub Kicinski <jakub.kicinski@netronome.com>

authored by

Willem de Bruijn and committed by
Jakub Kicinski
ea6a5476 3a5f494d

+88 -5
+81 -3
tools/testing/selftests/net/so_txtime.c
··· 12 12 #include <arpa/inet.h> 13 13 #include <error.h> 14 14 #include <errno.h> 15 + #include <inttypes.h> 15 16 #include <linux/net_tstamp.h> 17 + #include <linux/errqueue.h> 18 + #include <linux/ipv6.h> 19 + #include <linux/tcp.h> 16 20 #include <stdbool.h> 17 21 #include <stdlib.h> 18 22 #include <stdio.h> ··· 32 28 static bool cfg_do_ipv4; 33 29 static bool cfg_do_ipv6; 34 30 static uint16_t cfg_port = 8000; 35 - static int cfg_variance_us = 2000; 31 + static int cfg_variance_us = 4000; 36 32 37 33 static uint64_t glob_tstart; 38 34 ··· 46 42 static struct timed_send cfg_in[MAX_NUM_PKT]; 47 43 static struct timed_send cfg_out[MAX_NUM_PKT]; 48 44 static int cfg_num_pkt; 45 + 46 + static int cfg_errq_level; 47 + static int cfg_errq_type; 49 48 50 49 static uint64_t gettime_ns(void) 51 50 { ··· 97 90 98 91 } 99 92 100 - static void do_recv_one(int fdr, struct timed_send *ts) 93 + static bool do_recv_one(int fdr, struct timed_send *ts) 101 94 { 102 95 int64_t tstop, texpect; 103 96 char rbuf[2]; 104 97 int ret; 105 98 106 99 ret = recv(fdr, rbuf, sizeof(rbuf), 0); 100 + if (ret == -1 && errno == EAGAIN) 101 + return true; 107 102 if (ret == -1) 108 103 error(1, errno, "read"); 109 104 if (ret != 1) ··· 122 113 123 114 if (labs(tstop - texpect) > cfg_variance_us) 124 115 error(1, 0, "exceeds variance (%d us)", cfg_variance_us); 116 + 117 + return false; 125 118 } 126 119 127 120 static void do_recv_verify_empty(int fdr) ··· 136 125 error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno); 137 126 } 138 127 128 + static void do_recv_errqueue_timeout(int fdt) 129 + { 130 + char control[CMSG_SPACE(sizeof(struct sock_extended_err)) + 131 + CMSG_SPACE(sizeof(struct sockaddr_in6))] = {0}; 132 + char data[sizeof(struct ipv6hdr) + 133 + sizeof(struct tcphdr) + 1]; 134 + struct sock_extended_err *err; 135 + struct msghdr msg = {0}; 136 + struct iovec iov = {0}; 137 + struct cmsghdr *cm; 138 + int64_t tstamp = 0; 139 + int ret; 140 + 141 + iov.iov_base = data; 142 + iov.iov_len = sizeof(data); 143 + 144 + msg.msg_iov = &iov; 145 + msg.msg_iovlen = 1; 146 + 147 + msg.msg_control = control; 148 + msg.msg_controllen = sizeof(control); 149 + 150 + while (1) { 151 + ret = recvmsg(fdt, &msg, MSG_ERRQUEUE); 152 + if (ret == -1 && errno == EAGAIN) 153 + break; 154 + if (ret == -1) 155 + error(1, errno, "errqueue"); 156 + if (msg.msg_flags != MSG_ERRQUEUE) 157 + error(1, 0, "errqueue: flags 0x%x\n", msg.msg_flags); 158 + 159 + cm = CMSG_FIRSTHDR(&msg); 160 + if (cm->cmsg_level != cfg_errq_level || 161 + cm->cmsg_type != cfg_errq_type) 162 + error(1, 0, "errqueue: type 0x%x.0x%x\n", 163 + cm->cmsg_level, cm->cmsg_type); 164 + 165 + err = (struct sock_extended_err *)CMSG_DATA(cm); 166 + if (err->ee_origin != SO_EE_ORIGIN_TXTIME) 167 + error(1, 0, "errqueue: origin 0x%x\n", err->ee_origin); 168 + if (err->ee_code != ECANCELED) 169 + error(1, 0, "errqueue: code 0x%x\n", err->ee_code); 170 + 171 + tstamp = ((int64_t) err->ee_data) << 32 | err->ee_info; 172 + tstamp -= (int64_t) glob_tstart; 173 + tstamp /= 1000 * 1000; 174 + fprintf(stderr, "send: pkt %c at %" PRId64 "ms dropped\n", 175 + data[ret - 1], tstamp); 176 + 177 + msg.msg_flags = 0; 178 + msg.msg_controllen = sizeof(control); 179 + } 180 + 181 + error(1, 0, "recv: timeout"); 182 + } 183 + 139 184 static void setsockopt_txtime(int fd) 140 185 { 141 186 struct sock_txtime so_txtime_val = { .clockid = cfg_clockid }; 142 187 struct sock_txtime so_txtime_val_read = { 0 }; 143 188 socklen_t vallen = sizeof(so_txtime_val); 189 + 190 + so_txtime_val.flags = SOF_TXTIME_REPORT_ERRORS; 144 191 145 192 if (setsockopt(fd, SOL_SOCKET, SO_TXTIME, 146 193 &so_txtime_val, sizeof(so_txtime_val))) ··· 263 194 for (i = 0; i < cfg_num_pkt; i++) 264 195 do_send_one(fdt, &cfg_in[i]); 265 196 for (i = 0; i < cfg_num_pkt; i++) 266 - do_recv_one(fdr, &cfg_out[i]); 197 + if (do_recv_one(fdr, &cfg_out[i])) 198 + do_recv_errqueue_timeout(fdt); 267 199 268 200 do_recv_verify_empty(fdr); 269 201 ··· 350 280 addr6.sin6_family = AF_INET6; 351 281 addr6.sin6_port = htons(cfg_port); 352 282 addr6.sin6_addr = in6addr_loopback; 283 + 284 + cfg_errq_level = SOL_IPV6; 285 + cfg_errq_type = IPV6_RECVERR; 286 + 353 287 do_test((void *)&addr6, sizeof(addr6)); 354 288 } 355 289 ··· 363 289 addr4.sin_family = AF_INET; 364 290 addr4.sin_port = htons(cfg_port); 365 291 addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 292 + 293 + cfg_errq_level = SOL_IP; 294 + cfg_errq_type = IP_RECVERR; 295 + 366 296 do_test((void *)&addr4, sizeof(addr4)); 367 297 } 368 298
+7 -2
tools/testing/selftests/net/so_txtime.sh
··· 5 5 6 6 # Run in network namespace 7 7 if [[ $# -eq 0 ]]; then 8 - ./in_netns.sh $0 __subprocess 8 + if ! ./in_netns.sh $0 __subprocess; then 9 + # test is time sensitive, can be flaky 10 + echo "test failed: retry once" 11 + ./in_netns.sh $0 __subprocess 12 + fi 13 + 9 14 exit $? 10 15 fi 11 16 ··· 23 18 ./so_txtime -4 -6 -c mono a,10,b,20 a,10,b,20 24 19 ./so_txtime -4 -6 -c mono a,20,b,10 b,20,a,20 25 20 26 - if tc qdisc replace dev lo root etf clockid CLOCK_TAI delta 200000; then 21 + if tc qdisc replace dev lo root etf clockid CLOCK_TAI delta 400000; then 27 22 ! ./so_txtime -4 -6 -c tai a,-1 a,-1 28 23 ! ./so_txtime -4 -6 -c tai a,0 a,0 29 24 ./so_txtime -4 -6 -c tai a,10 a,10