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

selftests/net: so_txtime multi-host support

SO_TXTIME hardware offload requires testing across devices, either
between machines or separate network namespaces.

Split up SO_TXTIME test into tx and rx modes, so traffic can be
sent from one process to another. Create a veth-pair on different
namespaces and bind each process to an end point via [-S]ource and
[-D]estination parameters. Optional start [-t]ime parameter can be
passed to synchronize the test across the hosts (with synchorinzed
clocks).

Signed-off-by: Carlos Llamas <cmllamas@google.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Carlos Llamas and committed by
David S. Miller
04080634 917e2e6c

+260 -86
+184 -63
tools/testing/selftests/net/so_txtime.c
··· 2 2 /* 3 3 * Test the SO_TXTIME API 4 4 * 5 - * Takes two streams of { payload, delivery time }[], one input and one output. 6 - * Sends the input stream and verifies arrival matches the output stream. 7 - * The two streams can differ due to out-of-order delivery and drops. 5 + * Takes a stream of { payload, delivery time }[], to be sent across two 6 + * processes. Start this program on two separate network namespaces or 7 + * connected hosts, one instance in transmit mode and the other in receive 8 + * mode using the '-r' option. Receiver will compare arrival timestamps to 9 + * the expected stream. Sender will read transmit timestamps from the error 10 + * queue. The streams can differ due to out-of-order delivery and drops. 8 11 */ 9 12 10 13 #define _GNU_SOURCE ··· 31 28 #include <sys/types.h> 32 29 #include <time.h> 33 30 #include <unistd.h> 31 + #include <poll.h> 34 32 35 33 static int cfg_clockid = CLOCK_TAI; 36 - static bool cfg_do_ipv4; 37 - static bool cfg_do_ipv6; 38 34 static uint16_t cfg_port = 8000; 39 35 static int cfg_variance_us = 4000; 36 + static uint64_t cfg_start_time_ns; 37 + static int cfg_mark; 38 + static bool cfg_rx; 40 39 41 40 static uint64_t glob_tstart; 41 + static uint64_t tdeliver_max; 42 42 43 43 /* encode one timed transmission (of a 1B payload) */ 44 44 struct timed_send { ··· 50 44 }; 51 45 52 46 #define MAX_NUM_PKT 8 53 - static struct timed_send cfg_in[MAX_NUM_PKT]; 54 - static struct timed_send cfg_out[MAX_NUM_PKT]; 47 + static struct timed_send cfg_buf[MAX_NUM_PKT]; 55 48 static int cfg_num_pkt; 56 49 57 50 static int cfg_errq_level; 58 51 static int cfg_errq_type; 59 52 60 - static uint64_t gettime_ns(void) 53 + static struct sockaddr_storage cfg_dst_addr; 54 + static struct sockaddr_storage cfg_src_addr; 55 + static socklen_t cfg_alen; 56 + 57 + static uint64_t gettime_ns(clockid_t clock) 61 58 { 62 59 struct timespec ts; 63 60 64 - if (clock_gettime(cfg_clockid, &ts)) 61 + if (clock_gettime(clock, &ts)) 65 62 error(1, errno, "gettime"); 66 63 67 64 return ts.tv_sec * (1000ULL * 1000 * 1000) + ts.tv_nsec; ··· 84 75 85 76 msg.msg_iov = &iov; 86 77 msg.msg_iovlen = 1; 78 + msg.msg_name = (struct sockaddr *)&cfg_dst_addr; 79 + msg.msg_namelen = cfg_alen; 87 80 88 81 if (ts->delay_us >= 0) { 89 82 memset(control, 0, sizeof(control)); ··· 93 82 msg.msg_controllen = sizeof(control); 94 83 95 84 tdeliver = glob_tstart + ts->delay_us * 1000; 85 + tdeliver_max = tdeliver_max > tdeliver ? 86 + tdeliver_max : tdeliver; 96 87 97 88 cm = CMSG_FIRSTHDR(&msg); 98 89 cm->cmsg_level = SOL_SOCKET; ··· 111 98 112 99 } 113 100 114 - static bool do_recv_one(int fdr, struct timed_send *ts) 101 + static void do_recv_one(int fdr, struct timed_send *ts) 115 102 { 116 103 int64_t tstop, texpect; 117 104 char rbuf[2]; ··· 119 106 120 107 ret = recv(fdr, rbuf, sizeof(rbuf), 0); 121 108 if (ret == -1 && errno == EAGAIN) 122 - return true; 109 + error(1, EAGAIN, "recv: timeout"); 123 110 if (ret == -1) 124 111 error(1, errno, "read"); 125 112 if (ret != 1) 126 113 error(1, 0, "read: %dB", ret); 127 114 128 - tstop = (gettime_ns() - glob_tstart) / 1000; 115 + tstop = (gettime_ns(cfg_clockid) - glob_tstart) / 1000; 129 116 texpect = ts->delay_us >= 0 ? ts->delay_us : 0; 130 117 131 118 fprintf(stderr, "payload:%c delay:%lld expected:%lld (us)\n", ··· 136 123 137 124 if (llabs(tstop - texpect) > cfg_variance_us) 138 125 error(1, 0, "exceeds variance (%d us)", cfg_variance_us); 139 - 140 - return false; 141 126 } 142 127 143 128 static void do_recv_verify_empty(int fdr) ··· 148 137 error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno); 149 138 } 150 139 151 - static void do_recv_errqueue_timeout(int fdt) 140 + static int do_recv_errqueue_timeout(int fdt) 152 141 { 153 142 char control[CMSG_SPACE(sizeof(struct sock_extended_err)) + 154 143 CMSG_SPACE(sizeof(struct sockaddr_in6))] = {0}; 155 144 char data[sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + 156 145 sizeof(struct udphdr) + 1]; 157 146 struct sock_extended_err *err; 147 + int ret, num_tstamp = 0; 158 148 struct msghdr msg = {0}; 159 149 struct iovec iov = {0}; 160 150 struct cmsghdr *cm; 161 151 int64_t tstamp = 0; 162 - int ret; 163 152 164 153 iov.iov_base = data; 165 154 iov.iov_len = sizeof(data); ··· 217 206 218 207 msg.msg_flags = 0; 219 208 msg.msg_controllen = sizeof(control); 209 + num_tstamp++; 220 210 } 221 211 222 - error(1, 0, "recv: timeout"); 212 + return num_tstamp; 213 + } 214 + 215 + static void recv_errqueue_msgs(int fdt) 216 + { 217 + struct pollfd pfd = { .fd = fdt, .events = POLLERR }; 218 + const int timeout_ms = 10; 219 + int ret, num_tstamp = 0; 220 + 221 + do { 222 + ret = poll(&pfd, 1, timeout_ms); 223 + if (ret == -1) 224 + error(1, errno, "poll"); 225 + 226 + if (ret && (pfd.revents & POLLERR)) 227 + num_tstamp += do_recv_errqueue_timeout(fdt); 228 + 229 + if (num_tstamp == cfg_num_pkt) 230 + break; 231 + 232 + } while (gettime_ns(cfg_clockid) < tdeliver_max); 233 + } 234 + 235 + static void start_time_wait(void) 236 + { 237 + uint64_t now; 238 + int err; 239 + 240 + if (!cfg_start_time_ns) 241 + return; 242 + 243 + now = gettime_ns(CLOCK_REALTIME); 244 + if (cfg_start_time_ns < now) 245 + return; 246 + 247 + err = usleep((cfg_start_time_ns - now) / 1000); 248 + if (err) 249 + error(1, errno, "usleep"); 223 250 } 224 251 225 252 static void setsockopt_txtime(int fd) ··· 294 245 295 246 setsockopt_txtime(fd); 296 247 248 + if (cfg_mark && 249 + setsockopt(fd, SOL_SOCKET, SO_MARK, &cfg_mark, sizeof(cfg_mark))) 250 + error(1, errno, "setsockopt mark"); 251 + 297 252 return fd; 298 253 } 299 254 ··· 319 266 return fd; 320 267 } 321 268 322 - static void do_test(struct sockaddr *addr, socklen_t alen) 269 + static void do_test_tx(struct sockaddr *addr, socklen_t alen) 323 270 { 324 - int fdt, fdr, i; 271 + int fdt, i; 325 272 326 273 fprintf(stderr, "\nSO_TXTIME ipv%c clock %s\n", 327 274 addr->sa_family == PF_INET ? '4' : '6', 328 275 cfg_clockid == CLOCK_TAI ? "tai" : "monotonic"); 329 276 330 277 fdt = setup_tx(addr, alen); 278 + 279 + start_time_wait(); 280 + glob_tstart = gettime_ns(cfg_clockid); 281 + 282 + for (i = 0; i < cfg_num_pkt; i++) 283 + do_send_one(fdt, &cfg_buf[i]); 284 + 285 + recv_errqueue_msgs(fdt); 286 + 287 + if (close(fdt)) 288 + error(1, errno, "close t"); 289 + } 290 + 291 + static void do_test_rx(struct sockaddr *addr, socklen_t alen) 292 + { 293 + int fdr, i; 294 + 331 295 fdr = setup_rx(addr, alen); 332 296 333 - glob_tstart = gettime_ns(); 297 + start_time_wait(); 298 + glob_tstart = gettime_ns(cfg_clockid); 334 299 335 300 for (i = 0; i < cfg_num_pkt; i++) 336 - do_send_one(fdt, &cfg_in[i]); 337 - for (i = 0; i < cfg_num_pkt; i++) 338 - if (do_recv_one(fdr, &cfg_out[i])) 339 - do_recv_errqueue_timeout(fdt); 301 + do_recv_one(fdr, &cfg_buf[i]); 340 302 341 303 do_recv_verify_empty(fdr); 342 304 343 305 if (close(fdr)) 344 306 error(1, errno, "close r"); 345 - if (close(fdt)) 346 - error(1, errno, "close t"); 307 + } 308 + 309 + static void setup_sockaddr(int domain, const char *str_addr, 310 + struct sockaddr_storage *sockaddr) 311 + { 312 + struct sockaddr_in6 *addr6 = (void *) sockaddr; 313 + struct sockaddr_in *addr4 = (void *) sockaddr; 314 + 315 + switch (domain) { 316 + case PF_INET: 317 + memset(addr4, 0, sizeof(*addr4)); 318 + addr4->sin_family = AF_INET; 319 + addr4->sin_port = htons(cfg_port); 320 + if (str_addr && 321 + inet_pton(AF_INET, str_addr, &(addr4->sin_addr)) != 1) 322 + error(1, 0, "ipv4 parse error: %s", str_addr); 323 + break; 324 + case PF_INET6: 325 + memset(addr6, 0, sizeof(*addr6)); 326 + addr6->sin6_family = AF_INET6; 327 + addr6->sin6_port = htons(cfg_port); 328 + if (str_addr && 329 + inet_pton(AF_INET6, str_addr, &(addr6->sin6_addr)) != 1) 330 + error(1, 0, "ipv6 parse error: %s", str_addr); 331 + break; 332 + } 347 333 } 348 334 349 335 static int parse_io(const char *optarg, struct timed_send *array) ··· 415 323 return aoff / 2; 416 324 } 417 325 326 + static void usage(const char *progname) 327 + { 328 + fprintf(stderr, "\nUsage: %s [options] <payload>\n" 329 + "Options:\n" 330 + " -4 only IPv4\n" 331 + " -6 only IPv6\n" 332 + " -c <clock> monotonic (default) or tai\n" 333 + " -D <addr> destination IP address (server)\n" 334 + " -S <addr> source IP address (client)\n" 335 + " -r run rx mode\n" 336 + " -t <nsec> start time (UTC nanoseconds)\n" 337 + " -m <mark> socket mark\n" 338 + "\n", 339 + progname); 340 + exit(1); 341 + } 342 + 418 343 static void parse_opts(int argc, char **argv) 419 344 { 420 - int c, ilen, olen; 345 + char *daddr = NULL, *saddr = NULL; 346 + int domain = PF_UNSPEC; 347 + int c; 421 348 422 - while ((c = getopt(argc, argv, "46c:")) != -1) { 349 + while ((c = getopt(argc, argv, "46c:S:D:rt:m:")) != -1) { 423 350 switch (c) { 424 351 case '4': 425 - cfg_do_ipv4 = true; 352 + if (domain != PF_UNSPEC) 353 + error(1, 0, "Pass one of -4 or -6"); 354 + domain = PF_INET; 355 + cfg_alen = sizeof(struct sockaddr_in); 356 + cfg_errq_level = SOL_IP; 357 + cfg_errq_type = IP_RECVERR; 426 358 break; 427 359 case '6': 428 - cfg_do_ipv6 = true; 360 + if (domain != PF_UNSPEC) 361 + error(1, 0, "Pass one of -4 or -6"); 362 + domain = PF_INET6; 363 + cfg_alen = sizeof(struct sockaddr_in6); 364 + cfg_errq_level = SOL_IPV6; 365 + cfg_errq_type = IPV6_RECVERR; 429 366 break; 430 367 case 'c': 431 368 if (!strcmp(optarg, "tai")) ··· 465 344 else 466 345 error(1, 0, "unknown clock id %s", optarg); 467 346 break; 347 + case 'S': 348 + saddr = optarg; 349 + break; 350 + case 'D': 351 + daddr = optarg; 352 + break; 353 + case 'r': 354 + cfg_rx = true; 355 + break; 356 + case 't': 357 + cfg_start_time_ns = strtol(optarg, NULL, 0); 358 + break; 359 + case 'm': 360 + cfg_mark = strtol(optarg, NULL, 0); 361 + break; 468 362 default: 469 - error(1, 0, "parse error at %d", optind); 363 + usage(argv[0]); 470 364 } 471 365 } 472 366 473 - if (argc - optind != 2) 474 - error(1, 0, "Usage: %s [-46] -c <clock> <in> <out>", argv[0]); 367 + if (argc - optind != 1) 368 + usage(argv[0]); 475 369 476 - ilen = parse_io(argv[optind], cfg_in); 477 - olen = parse_io(argv[optind + 1], cfg_out); 478 - if (ilen != olen) 479 - error(1, 0, "i/o streams len mismatch (%d, %d)\n", ilen, olen); 480 - cfg_num_pkt = ilen; 370 + if (domain == PF_UNSPEC) 371 + error(1, 0, "Pass one of -4 or -6"); 372 + if (!daddr) 373 + error(1, 0, "-D <server addr> required\n"); 374 + if (!cfg_rx && !saddr) 375 + error(1, 0, "-S <client addr> required\n"); 376 + 377 + setup_sockaddr(domain, daddr, &cfg_dst_addr); 378 + setup_sockaddr(domain, saddr, &cfg_src_addr); 379 + 380 + cfg_num_pkt = parse_io(argv[optind], cfg_buf); 481 381 } 482 382 483 383 int main(int argc, char **argv) 484 384 { 485 385 parse_opts(argc, argv); 486 386 487 - if (cfg_do_ipv6) { 488 - struct sockaddr_in6 addr6 = {0}; 489 - 490 - addr6.sin6_family = AF_INET6; 491 - addr6.sin6_port = htons(cfg_port); 492 - addr6.sin6_addr = in6addr_loopback; 493 - 494 - cfg_errq_level = SOL_IPV6; 495 - cfg_errq_type = IPV6_RECVERR; 496 - 497 - do_test((void *)&addr6, sizeof(addr6)); 498 - } 499 - 500 - if (cfg_do_ipv4) { 501 - struct sockaddr_in addr4 = {0}; 502 - 503 - addr4.sin_family = AF_INET; 504 - addr4.sin_port = htons(cfg_port); 505 - addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 506 - 507 - cfg_errq_level = SOL_IP; 508 - cfg_errq_type = IP_RECVERR; 509 - 510 - do_test((void *)&addr4, sizeof(addr4)); 511 - } 387 + if (cfg_rx) 388 + do_test_rx((void *)&cfg_dst_addr, cfg_alen); 389 + else 390 + do_test_tx((void *)&cfg_src_addr, cfg_alen); 512 391 513 392 return 0; 514 393 }
+76 -23
tools/testing/selftests/net/so_txtime.sh
··· 3 3 # 4 4 # Regression tests for the SO_TXTIME interface 5 5 6 - # Run in network namespace 7 - if [[ $# -eq 0 ]]; then 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 - 14 - exit $? 15 - fi 16 - 17 6 set -e 18 7 19 - tc qdisc add dev lo root fq 20 - ./so_txtime -4 -6 -c mono a,-1 a,-1 21 - ./so_txtime -4 -6 -c mono a,0 a,0 22 - ./so_txtime -4 -6 -c mono a,10 a,10 23 - ./so_txtime -4 -6 -c mono a,10,b,20 a,10,b,20 24 - ./so_txtime -4 -6 -c mono a,20,b,10 b,20,a,20 8 + readonly DEV="veth0" 9 + readonly BIN="./so_txtime" 25 10 26 - if tc qdisc replace dev lo root etf clockid CLOCK_TAI delta 400000; then 27 - ! ./so_txtime -4 -6 -c tai a,-1 a,-1 28 - ! ./so_txtime -4 -6 -c tai a,0 a,0 29 - ./so_txtime -4 -6 -c tai a,10 a,10 30 - ./so_txtime -4 -6 -c tai a,10,b,20 a,10,b,20 31 - ./so_txtime -4 -6 -c tai a,20,b,10 b,10,a,20 11 + readonly RAND="$(mktemp -u XXXXXX)" 12 + readonly NSPREFIX="ns-${RAND}" 13 + readonly NS1="${NSPREFIX}1" 14 + readonly NS2="${NSPREFIX}2" 15 + 16 + readonly SADDR4='192.168.1.1' 17 + readonly DADDR4='192.168.1.2' 18 + readonly SADDR6='fd::1' 19 + readonly DADDR6='fd::2' 20 + 21 + cleanup() { 22 + ip netns del "${NS2}" 23 + ip netns del "${NS1}" 24 + } 25 + 26 + trap cleanup EXIT 27 + 28 + # Create virtual ethernet pair between network namespaces 29 + ip netns add "${NS1}" 30 + ip netns add "${NS2}" 31 + 32 + ip link add "${DEV}" netns "${NS1}" type veth \ 33 + peer name "${DEV}" netns "${NS2}" 34 + 35 + # Bring the devices up 36 + ip -netns "${NS1}" link set "${DEV}" up 37 + ip -netns "${NS2}" link set "${DEV}" up 38 + 39 + # Set fixed MAC addresses on the devices 40 + ip -netns "${NS1}" link set dev "${DEV}" address 02:02:02:02:02:02 41 + ip -netns "${NS2}" link set dev "${DEV}" address 06:06:06:06:06:06 42 + 43 + # Add fixed IP addresses to the devices 44 + ip -netns "${NS1}" addr add 192.168.1.1/24 dev "${DEV}" 45 + ip -netns "${NS2}" addr add 192.168.1.2/24 dev "${DEV}" 46 + ip -netns "${NS1}" addr add fd::1/64 dev "${DEV}" nodad 47 + ip -netns "${NS2}" addr add fd::2/64 dev "${DEV}" nodad 48 + 49 + do_test() { 50 + local readonly IP="$1" 51 + local readonly CLOCK="$2" 52 + local readonly TXARGS="$3" 53 + local readonly RXARGS="$4" 54 + 55 + if [[ "${IP}" == "4" ]]; then 56 + local readonly SADDR="${SADDR4}" 57 + local readonly DADDR="${DADDR4}" 58 + elif [[ "${IP}" == "6" ]]; then 59 + local readonly SADDR="${SADDR6}" 60 + local readonly DADDR="${DADDR6}" 61 + else 62 + echo "Invalid IP version ${IP}" 63 + exit 1 64 + fi 65 + 66 + local readonly START="$(date +%s%N --date="+ 0.1 seconds")" 67 + ip netns exec "${NS2}" "${BIN}" -"${IP}" -c "${CLOCK}" -t "${START}" -S "${SADDR}" -D "${DADDR}" "${RXARGS}" -r & 68 + ip netns exec "${NS1}" "${BIN}" -"${IP}" -c "${CLOCK}" -t "${START}" -S "${SADDR}" -D "${DADDR}" "${TXARGS}" 69 + wait "$!" 70 + } 71 + 72 + ip netns exec "${NS1}" tc qdisc add dev "${DEV}" root fq 73 + do_test 4 mono a,-1 a,-1 74 + do_test 6 mono a,0 a,0 75 + do_test 6 mono a,10 a,10 76 + do_test 4 mono a,10,b,20 a,10,b,20 77 + do_test 6 mono a,20,b,10 b,20,a,20 78 + 79 + if ip netns exec "${NS1}" tc qdisc replace dev "${DEV}" root etf clockid CLOCK_TAI delta 400000; then 80 + ! do_test 4 tai a,-1 a,-1 81 + ! do_test 6 tai a,0 a,0 82 + do_test 6 tai a,10 a,10 83 + do_test 4 tai a,10,b,20 a,10,b,20 84 + do_test 6 tai a,20,b,10 b,10,a,20 32 85 else 33 86 echo "tc ($(tc -V)) does not support qdisc etf. skipping" 34 87 fi