1 // SPDX-License-Identifier: GPL-2.0
3 * Test the SO_TXTIME API
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.
12 #include <arpa/inet.h>
16 #include <linux/net_tstamp.h>
17 #include <linux/errqueue.h>
18 #include <linux/if_ether.h>
19 #include <linux/ipv6.h>
20 #include <linux/udp.h>
25 #include <sys/socket.h>
28 #include <sys/types.h>
32 static int cfg_clockid
= CLOCK_TAI
;
33 static bool cfg_do_ipv4
;
34 static bool cfg_do_ipv6
;
35 static uint16_t cfg_port
= 8000;
36 static int cfg_variance_us
= 4000;
38 static uint64_t glob_tstart
;
40 /* encode one timed transmission (of a 1B payload) */
47 static struct timed_send cfg_in
[MAX_NUM_PKT
];
48 static struct timed_send cfg_out
[MAX_NUM_PKT
];
49 static int cfg_num_pkt
;
51 static int cfg_errq_level
;
52 static int cfg_errq_type
;
54 static uint64_t gettime_ns(void)
58 if (clock_gettime(cfg_clockid
, &ts
))
59 error(1, errno
, "gettime");
61 return ts
.tv_sec
* (1000ULL * 1000 * 1000) + ts
.tv_nsec
;
64 static void do_send_one(int fdt
, struct timed_send
*ts
)
66 char control
[CMSG_SPACE(sizeof(uint64_t))];
67 struct msghdr msg
= {0};
68 struct iovec iov
= {0};
73 iov
.iov_base
= &ts
->data
;
79 if (ts
->delay_us
>= 0) {
80 memset(control
, 0, sizeof(control
));
81 msg
.msg_control
= &control
;
82 msg
.msg_controllen
= sizeof(control
);
84 tdeliver
= glob_tstart
+ ts
->delay_us
* 1000;
86 cm
= CMSG_FIRSTHDR(&msg
);
87 cm
->cmsg_level
= SOL_SOCKET
;
88 cm
->cmsg_type
= SCM_TXTIME
;
89 cm
->cmsg_len
= CMSG_LEN(sizeof(tdeliver
));
90 memcpy(CMSG_DATA(cm
), &tdeliver
, sizeof(tdeliver
));
93 ret
= sendmsg(fdt
, &msg
, 0);
95 error(1, errno
, "write");
97 error(1, 0, "write: 0B");
101 static bool do_recv_one(int fdr
, struct timed_send
*ts
)
103 int64_t tstop
, texpect
;
107 ret
= recv(fdr
, rbuf
, sizeof(rbuf
), 0);
108 if (ret
== -1 && errno
== EAGAIN
)
111 error(1, errno
, "read");
113 error(1, 0, "read: %dB", ret
);
115 tstop
= (gettime_ns() - glob_tstart
) / 1000;
116 texpect
= ts
->delay_us
>= 0 ? ts
->delay_us
: 0;
118 fprintf(stderr
, "payload:%c delay:%lld expected:%lld (us)\n",
119 rbuf
[0], (long long)tstop
, (long long)texpect
);
121 if (rbuf
[0] != ts
->data
)
122 error(1, 0, "payload mismatch. expected %c", ts
->data
);
124 if (llabs(tstop
- texpect
) > cfg_variance_us
)
125 error(1, 0, "exceeds variance (%d us)", cfg_variance_us
);
130 static void do_recv_verify_empty(int fdr
)
135 ret
= recv(fdr
, rbuf
, sizeof(rbuf
), 0);
136 if (ret
!= -1 || errno
!= EAGAIN
)
137 error(1, 0, "recv: not empty as expected (%d, %d)", ret
, errno
);
140 static void do_recv_errqueue_timeout(int fdt
)
142 char control
[CMSG_SPACE(sizeof(struct sock_extended_err
)) +
143 CMSG_SPACE(sizeof(struct sockaddr_in6
))] = {0};
144 char data
[sizeof(struct ethhdr
) + sizeof(struct ipv6hdr
) +
145 sizeof(struct udphdr
) + 1];
146 struct sock_extended_err
*err
;
147 struct msghdr msg
= {0};
148 struct iovec iov
= {0};
154 iov
.iov_len
= sizeof(data
);
159 msg
.msg_control
= control
;
160 msg
.msg_controllen
= sizeof(control
);
165 ret
= recvmsg(fdt
, &msg
, MSG_ERRQUEUE
);
166 if (ret
== -1 && errno
== EAGAIN
)
169 error(1, errno
, "errqueue");
170 if (msg
.msg_flags
!= MSG_ERRQUEUE
)
171 error(1, 0, "errqueue: flags 0x%x\n", msg
.msg_flags
);
173 cm
= CMSG_FIRSTHDR(&msg
);
174 if (cm
->cmsg_level
!= cfg_errq_level
||
175 cm
->cmsg_type
!= cfg_errq_type
)
176 error(1, 0, "errqueue: type 0x%x.0x%x\n",
177 cm
->cmsg_level
, cm
->cmsg_type
);
179 err
= (struct sock_extended_err
*)CMSG_DATA(cm
);
180 if (err
->ee_origin
!= SO_EE_ORIGIN_TXTIME
)
181 error(1, 0, "errqueue: origin 0x%x\n", err
->ee_origin
);
183 switch (err
->ee_errno
) {
185 if (err
->ee_code
!= SO_EE_CODE_TXTIME_MISSED
)
186 error(1, 0, "errqueue: unknown ECANCELED %u\n",
188 reason
= "missed txtime";
191 if (err
->ee_code
!= SO_EE_CODE_TXTIME_INVALID_PARAM
)
192 error(1, 0, "errqueue: unknown EINVAL %u\n",
194 reason
= "invalid txtime";
197 error(1, 0, "errqueue: errno %u code %u\n",
198 err
->ee_errno
, err
->ee_code
);
201 tstamp
= ((int64_t) err
->ee_data
) << 32 | err
->ee_info
;
202 tstamp
-= (int64_t) glob_tstart
;
203 tstamp
/= 1000 * 1000;
204 fprintf(stderr
, "send: pkt %c at %" PRId64
"ms dropped: %s\n",
205 data
[ret
- 1], tstamp
, reason
);
208 msg
.msg_controllen
= sizeof(control
);
211 error(1, 0, "recv: timeout");
214 static void setsockopt_txtime(int fd
)
216 struct sock_txtime so_txtime_val
= { .clockid
= cfg_clockid
};
217 struct sock_txtime so_txtime_val_read
= { 0 };
218 socklen_t vallen
= sizeof(so_txtime_val
);
220 so_txtime_val
.flags
= SOF_TXTIME_REPORT_ERRORS
;
222 if (setsockopt(fd
, SOL_SOCKET
, SO_TXTIME
,
223 &so_txtime_val
, sizeof(so_txtime_val
)))
224 error(1, errno
, "setsockopt txtime");
226 if (getsockopt(fd
, SOL_SOCKET
, SO_TXTIME
,
227 &so_txtime_val_read
, &vallen
))
228 error(1, errno
, "getsockopt txtime");
230 if (vallen
!= sizeof(so_txtime_val
) ||
231 memcmp(&so_txtime_val
, &so_txtime_val_read
, vallen
))
232 error(1, 0, "getsockopt txtime: mismatch");
235 static int setup_tx(struct sockaddr
*addr
, socklen_t alen
)
239 fd
= socket(addr
->sa_family
, SOCK_DGRAM
, 0);
241 error(1, errno
, "socket t");
243 if (connect(fd
, addr
, alen
))
244 error(1, errno
, "connect");
246 setsockopt_txtime(fd
);
251 static int setup_rx(struct sockaddr
*addr
, socklen_t alen
)
253 struct timeval tv
= { .tv_usec
= 100 * 1000 };
256 fd
= socket(addr
->sa_family
, SOCK_DGRAM
, 0);
258 error(1, errno
, "socket r");
260 if (bind(fd
, addr
, alen
))
261 error(1, errno
, "bind");
263 if (setsockopt(fd
, SOL_SOCKET
, SO_RCVTIMEO
, &tv
, sizeof(tv
)))
264 error(1, errno
, "setsockopt rcv timeout");
269 static void do_test(struct sockaddr
*addr
, socklen_t alen
)
273 fprintf(stderr
, "\nSO_TXTIME ipv%c clock %s\n",
274 addr
->sa_family
== PF_INET
? '4' : '6',
275 cfg_clockid
== CLOCK_TAI
? "tai" : "monotonic");
277 fdt
= setup_tx(addr
, alen
);
278 fdr
= setup_rx(addr
, alen
);
280 glob_tstart
= gettime_ns();
282 for (i
= 0; i
< cfg_num_pkt
; i
++)
283 do_send_one(fdt
, &cfg_in
[i
]);
284 for (i
= 0; i
< cfg_num_pkt
; i
++)
285 if (do_recv_one(fdr
, &cfg_out
[i
]))
286 do_recv_errqueue_timeout(fdt
);
288 do_recv_verify_empty(fdr
);
291 error(1, errno
, "close r");
293 error(1, errno
, "close t");
296 static int parse_io(const char *optarg
, struct timed_send
*array
)
301 arg
= strdup(optarg
);
303 error(1, errno
, "strdup");
305 while ((tok
= strtok(arg
, ","))) {
306 arg
= NULL
; /* only pass non-zero on first call */
308 if (aoff
/ 2 == MAX_NUM_PKT
)
309 error(1, 0, "exceeds max pkt count (%d)", MAX_NUM_PKT
);
311 if (aoff
& 1) { /* parse delay */
312 array
->delay_us
= strtol(tok
, NULL
, 0) * 1000;
314 } else { /* parse character */
315 array
->data
= tok
[0];
326 static void parse_opts(int argc
, char **argv
)
330 while ((c
= getopt(argc
, argv
, "46c:")) != -1) {
339 if (!strcmp(optarg
, "tai"))
340 cfg_clockid
= CLOCK_TAI
;
341 else if (!strcmp(optarg
, "monotonic") ||
342 !strcmp(optarg
, "mono"))
343 cfg_clockid
= CLOCK_MONOTONIC
;
345 error(1, 0, "unknown clock id %s", optarg
);
348 error(1, 0, "parse error at %d", optind
);
352 if (argc
- optind
!= 2)
353 error(1, 0, "Usage: %s [-46] -c <clock> <in> <out>", argv
[0]);
355 ilen
= parse_io(argv
[optind
], cfg_in
);
356 olen
= parse_io(argv
[optind
+ 1], cfg_out
);
358 error(1, 0, "i/o streams len mismatch (%d, %d)\n", ilen
, olen
);
362 int main(int argc
, char **argv
)
364 parse_opts(argc
, argv
);
367 struct sockaddr_in6 addr6
= {0};
369 addr6
.sin6_family
= AF_INET6
;
370 addr6
.sin6_port
= htons(cfg_port
);
371 addr6
.sin6_addr
= in6addr_loopback
;
373 cfg_errq_level
= SOL_IPV6
;
374 cfg_errq_type
= IPV6_RECVERR
;
376 do_test((void *)&addr6
, sizeof(addr6
));
380 struct sockaddr_in addr4
= {0};
382 addr4
.sin_family
= AF_INET
;
383 addr4
.sin_port
= htons(cfg_port
);
384 addr4
.sin_addr
.s_addr
= htonl(INADDR_LOOPBACK
);
386 cfg_errq_level
= SOL_IP
;
387 cfg_errq_type
= IP_RECVERR
;
389 do_test((void *)&addr4
, sizeof(addr4
));