2019-05-23 20:48:46 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Test the SO_TXTIME API
*
2021-04-01 03:40:20 +03:00
* Takes a stream of { payload , delivery time } [ ] , to be sent across two
* processes . Start this program on two separate network namespaces or
* connected hosts , one instance in transmit mode and the other in receive
* mode using the ' - r ' option . Receiver will compare arrival timestamps to
* the expected stream . Sender will read transmit timestamps from the error
* queue . The streams can differ due to out - of - order delivery and drops .
2019-05-23 20:48:46 +03:00
*/
# define _GNU_SOURCE
# include <arpa/inet.h>
# include <error.h>
# include <errno.h>
2019-12-12 19:36:46 +03:00
# include <inttypes.h>
2019-05-23 20:48:46 +03:00
# include <linux/net_tstamp.h>
2019-12-12 19:36:46 +03:00
# include <linux/errqueue.h>
2020-06-18 19:40:43 +03:00
# include <linux/if_ether.h>
2019-12-12 19:36:46 +03:00
# include <linux/ipv6.h>
2020-06-18 19:40:43 +03:00
# include <linux/udp.h>
2019-05-23 20:48:46 +03:00
# include <stdbool.h>
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <sys/socket.h>
# include <sys/stat.h>
# include <sys/time.h>
# include <sys/types.h>
# include <time.h>
# include <unistd.h>
2021-04-01 03:40:20 +03:00
# include <poll.h>
2019-05-23 20:48:46 +03:00
static int cfg_clockid = CLOCK_TAI ;
static uint16_t cfg_port = 8000 ;
2019-12-12 19:36:46 +03:00
static int cfg_variance_us = 4000 ;
2021-04-01 03:40:20 +03:00
static uint64_t cfg_start_time_ns ;
static int cfg_mark ;
static bool cfg_rx ;
2019-05-23 20:48:46 +03:00
static uint64_t glob_tstart ;
2021-04-01 03:40:20 +03:00
static uint64_t tdeliver_max ;
2019-05-23 20:48:46 +03:00
/* encode one timed transmission (of a 1B payload) */
struct timed_send {
char data ;
int64_t delay_us ;
} ;
# define MAX_NUM_PKT 8
2021-04-01 03:40:20 +03:00
static struct timed_send cfg_buf [ MAX_NUM_PKT ] ;
2019-05-23 20:48:46 +03:00
static int cfg_num_pkt ;
2019-12-12 19:36:46 +03:00
static int cfg_errq_level ;
static int cfg_errq_type ;
2021-04-01 03:40:20 +03:00
static struct sockaddr_storage cfg_dst_addr ;
static struct sockaddr_storage cfg_src_addr ;
static socklen_t cfg_alen ;
static uint64_t gettime_ns ( clockid_t clock )
2019-05-23 20:48:46 +03:00
{
struct timespec ts ;
2021-04-01 03:40:20 +03:00
if ( clock_gettime ( clock , & ts ) )
2019-05-23 20:48:46 +03:00
error ( 1 , errno , " gettime " ) ;
return ts . tv_sec * ( 1000ULL * 1000 * 1000 ) + ts . tv_nsec ;
}
static void do_send_one ( int fdt , struct timed_send * ts )
{
char control [ CMSG_SPACE ( sizeof ( uint64_t ) ) ] ;
struct msghdr msg = { 0 } ;
struct iovec iov = { 0 } ;
struct cmsghdr * cm ;
uint64_t tdeliver ;
int ret ;
iov . iov_base = & ts - > data ;
iov . iov_len = 1 ;
msg . msg_iov = & iov ;
msg . msg_iovlen = 1 ;
2021-04-01 03:40:20 +03:00
msg . msg_name = ( struct sockaddr * ) & cfg_dst_addr ;
msg . msg_namelen = cfg_alen ;
2019-05-23 20:48:46 +03:00
if ( ts - > delay_us > = 0 ) {
memset ( control , 0 , sizeof ( control ) ) ;
msg . msg_control = & control ;
msg . msg_controllen = sizeof ( control ) ;
tdeliver = glob_tstart + ts - > delay_us * 1000 ;
2021-04-01 03:40:20 +03:00
tdeliver_max = tdeliver_max > tdeliver ?
tdeliver_max : tdeliver ;
2019-05-23 20:48:46 +03:00
cm = CMSG_FIRSTHDR ( & msg ) ;
cm - > cmsg_level = SOL_SOCKET ;
cm - > cmsg_type = SCM_TXTIME ;
cm - > cmsg_len = CMSG_LEN ( sizeof ( tdeliver ) ) ;
memcpy ( CMSG_DATA ( cm ) , & tdeliver , sizeof ( tdeliver ) ) ;
}
ret = sendmsg ( fdt , & msg , 0 ) ;
if ( ret = = - 1 )
error ( 1 , errno , " write " ) ;
if ( ret = = 0 )
error ( 1 , 0 , " write: 0B " ) ;
}
2021-04-01 03:40:20 +03:00
static void do_recv_one ( int fdr , struct timed_send * ts )
2019-05-23 20:48:46 +03:00
{
int64_t tstop , texpect ;
char rbuf [ 2 ] ;
int ret ;
ret = recv ( fdr , rbuf , sizeof ( rbuf ) , 0 ) ;
2019-12-12 19:36:46 +03:00
if ( ret = = - 1 & & errno = = EAGAIN )
2021-04-01 03:40:20 +03:00
error ( 1 , EAGAIN , " recv: timeout " ) ;
2019-05-23 20:48:46 +03:00
if ( ret = = - 1 )
error ( 1 , errno , " read " ) ;
if ( ret ! = 1 )
error ( 1 , 0 , " read: %dB " , ret ) ;
2021-04-01 03:40:20 +03:00
tstop = ( gettime_ns ( cfg_clockid ) - glob_tstart ) / 1000 ;
2019-05-23 20:48:46 +03:00
texpect = ts - > delay_us > = 0 ? ts - > delay_us : 0 ;
2019-10-23 07:58:07 +03:00
fprintf ( stderr , " payload:%c delay:%lld expected:%lld (us) \n " ,
rbuf [ 0 ] , ( long long ) tstop , ( long long ) texpect ) ;
2019-05-23 20:48:46 +03:00
if ( rbuf [ 0 ] ! = ts - > data )
error ( 1 , 0 , " payload mismatch. expected %c " , ts - > data ) ;
2020-07-27 19:25:30 +03:00
if ( llabs ( tstop - texpect ) > cfg_variance_us )
2019-05-23 20:48:46 +03:00
error ( 1 , 0 , " exceeds variance (%d us) " , cfg_variance_us ) ;
}
static void do_recv_verify_empty ( int fdr )
{
char rbuf [ 1 ] ;
int ret ;
ret = recv ( fdr , rbuf , sizeof ( rbuf ) , 0 ) ;
if ( ret ! = - 1 | | errno ! = EAGAIN )
error ( 1 , 0 , " recv: not empty as expected (%d, %d) " , ret , errno ) ;
}
2021-04-01 03:40:20 +03:00
static int do_recv_errqueue_timeout ( int fdt )
2019-12-12 19:36:46 +03:00
{
char control [ CMSG_SPACE ( sizeof ( struct sock_extended_err ) ) +
CMSG_SPACE ( sizeof ( struct sockaddr_in6 ) ) ] = { 0 } ;
2020-06-18 19:40:43 +03:00
char data [ sizeof ( struct ethhdr ) + sizeof ( struct ipv6hdr ) +
sizeof ( struct udphdr ) + 1 ] ;
2019-12-12 19:36:46 +03:00
struct sock_extended_err * err ;
2021-04-01 03:40:20 +03:00
int ret , num_tstamp = 0 ;
2019-12-12 19:36:46 +03:00
struct msghdr msg = { 0 } ;
struct iovec iov = { 0 } ;
struct cmsghdr * cm ;
int64_t tstamp = 0 ;
iov . iov_base = data ;
iov . iov_len = sizeof ( data ) ;
msg . msg_iov = & iov ;
msg . msg_iovlen = 1 ;
msg . msg_control = control ;
msg . msg_controllen = sizeof ( control ) ;
while ( 1 ) {
2020-06-18 19:40:43 +03:00
const char * reason ;
2019-12-12 19:36:46 +03:00
ret = recvmsg ( fdt , & msg , MSG_ERRQUEUE ) ;
if ( ret = = - 1 & & errno = = EAGAIN )
break ;
if ( ret = = - 1 )
error ( 1 , errno , " errqueue " ) ;
if ( msg . msg_flags ! = MSG_ERRQUEUE )
error ( 1 , 0 , " errqueue: flags 0x%x \n " , msg . msg_flags ) ;
cm = CMSG_FIRSTHDR ( & msg ) ;
if ( cm - > cmsg_level ! = cfg_errq_level | |
cm - > cmsg_type ! = cfg_errq_type )
error ( 1 , 0 , " errqueue: type 0x%x.0x%x \n " ,
cm - > cmsg_level , cm - > cmsg_type ) ;
err = ( struct sock_extended_err * ) CMSG_DATA ( cm ) ;
if ( err - > ee_origin ! = SO_EE_ORIGIN_TXTIME )
error ( 1 , 0 , " errqueue: origin 0x%x \n " , err - > ee_origin ) ;
2020-06-18 19:40:43 +03:00
switch ( err - > ee_errno ) {
case ECANCELED :
if ( err - > ee_code ! = SO_EE_CODE_TXTIME_MISSED )
error ( 1 , 0 , " errqueue: unknown ECANCELED %u \n " ,
err - > ee_code ) ;
reason = " missed txtime " ;
break ;
case EINVAL :
if ( err - > ee_code ! = SO_EE_CODE_TXTIME_INVALID_PARAM )
error ( 1 , 0 , " errqueue: unknown EINVAL %u \n " ,
err - > ee_code ) ;
reason = " invalid txtime " ;
break ;
default :
error ( 1 , 0 , " errqueue: errno %u code %u \n " ,
err - > ee_errno , err - > ee_code ) ;
2021-02-08 12:33:38 +03:00
}
2019-12-12 19:36:46 +03:00
tstamp = ( ( int64_t ) err - > ee_data ) < < 32 | err - > ee_info ;
tstamp - = ( int64_t ) glob_tstart ;
tstamp / = 1000 * 1000 ;
2020-06-18 19:40:43 +03:00
fprintf ( stderr , " send: pkt %c at % " PRId64 " ms dropped: %s \n " ,
data [ ret - 1 ] , tstamp , reason ) ;
2019-12-12 19:36:46 +03:00
msg . msg_flags = 0 ;
msg . msg_controllen = sizeof ( control ) ;
2021-04-01 03:40:20 +03:00
num_tstamp + + ;
2019-12-12 19:36:46 +03:00
}
2021-04-01 03:40:20 +03:00
return num_tstamp ;
}
static void recv_errqueue_msgs ( int fdt )
{
struct pollfd pfd = { . fd = fdt , . events = POLLERR } ;
const int timeout_ms = 10 ;
int ret , num_tstamp = 0 ;
do {
ret = poll ( & pfd , 1 , timeout_ms ) ;
if ( ret = = - 1 )
error ( 1 , errno , " poll " ) ;
if ( ret & & ( pfd . revents & POLLERR ) )
num_tstamp + = do_recv_errqueue_timeout ( fdt ) ;
if ( num_tstamp = = cfg_num_pkt )
break ;
} while ( gettime_ns ( cfg_clockid ) < tdeliver_max ) ;
}
static void start_time_wait ( void )
{
uint64_t now ;
int err ;
if ( ! cfg_start_time_ns )
return ;
now = gettime_ns ( CLOCK_REALTIME ) ;
if ( cfg_start_time_ns < now )
return ;
err = usleep ( ( cfg_start_time_ns - now ) / 1000 ) ;
if ( err )
error ( 1 , errno , " usleep " ) ;
2019-12-12 19:36:46 +03:00
}
2019-05-23 20:48:46 +03:00
static void setsockopt_txtime ( int fd )
{
struct sock_txtime so_txtime_val = { . clockid = cfg_clockid } ;
struct sock_txtime so_txtime_val_read = { 0 } ;
socklen_t vallen = sizeof ( so_txtime_val ) ;
2019-12-12 19:36:46 +03:00
so_txtime_val . flags = SOF_TXTIME_REPORT_ERRORS ;
2019-05-23 20:48:46 +03:00
if ( setsockopt ( fd , SOL_SOCKET , SO_TXTIME ,
& so_txtime_val , sizeof ( so_txtime_val ) ) )
error ( 1 , errno , " setsockopt txtime " ) ;
if ( getsockopt ( fd , SOL_SOCKET , SO_TXTIME ,
& so_txtime_val_read , & vallen ) )
error ( 1 , errno , " getsockopt txtime " ) ;
if ( vallen ! = sizeof ( so_txtime_val ) | |
memcmp ( & so_txtime_val , & so_txtime_val_read , vallen ) )
error ( 1 , 0 , " getsockopt txtime: mismatch " ) ;
}
static int setup_tx ( struct sockaddr * addr , socklen_t alen )
{
int fd ;
fd = socket ( addr - > sa_family , SOCK_DGRAM , 0 ) ;
if ( fd = = - 1 )
error ( 1 , errno , " socket t " ) ;
if ( connect ( fd , addr , alen ) )
error ( 1 , errno , " connect " ) ;
setsockopt_txtime ( fd ) ;
2021-04-01 03:40:20 +03:00
if ( cfg_mark & &
setsockopt ( fd , SOL_SOCKET , SO_MARK , & cfg_mark , sizeof ( cfg_mark ) ) )
error ( 1 , errno , " setsockopt mark " ) ;
2019-05-23 20:48:46 +03:00
return fd ;
}
static int setup_rx ( struct sockaddr * addr , socklen_t alen )
{
struct timeval tv = { . tv_usec = 100 * 1000 } ;
int fd ;
fd = socket ( addr - > sa_family , SOCK_DGRAM , 0 ) ;
if ( fd = = - 1 )
error ( 1 , errno , " socket r " ) ;
if ( bind ( fd , addr , alen ) )
error ( 1 , errno , " bind " ) ;
if ( setsockopt ( fd , SOL_SOCKET , SO_RCVTIMEO , & tv , sizeof ( tv ) ) )
error ( 1 , errno , " setsockopt rcv timeout " ) ;
return fd ;
}
2021-04-01 03:40:20 +03:00
static void do_test_tx ( struct sockaddr * addr , socklen_t alen )
2019-05-23 20:48:46 +03:00
{
2021-04-01 03:40:20 +03:00
int fdt , i ;
2019-05-23 20:48:46 +03:00
fprintf ( stderr , " \n SO_TXTIME ipv%c clock %s \n " ,
addr - > sa_family = = PF_INET ? ' 4 ' : ' 6 ' ,
cfg_clockid = = CLOCK_TAI ? " tai " : " monotonic " ) ;
fdt = setup_tx ( addr , alen ) ;
2021-04-01 03:40:20 +03:00
start_time_wait ( ) ;
glob_tstart = gettime_ns ( cfg_clockid ) ;
2019-05-23 20:48:46 +03:00
for ( i = 0 ; i < cfg_num_pkt ; i + + )
2021-04-01 03:40:20 +03:00
do_send_one ( fdt , & cfg_buf [ i ] ) ;
recv_errqueue_msgs ( fdt ) ;
if ( close ( fdt ) )
error ( 1 , errno , " close t " ) ;
}
static void do_test_rx ( struct sockaddr * addr , socklen_t alen )
{
int fdr , i ;
fdr = setup_rx ( addr , alen ) ;
start_time_wait ( ) ;
glob_tstart = gettime_ns ( cfg_clockid ) ;
2019-05-23 20:48:46 +03:00
for ( i = 0 ; i < cfg_num_pkt ; i + + )
2021-04-01 03:40:20 +03:00
do_recv_one ( fdr , & cfg_buf [ i ] ) ;
2019-05-23 20:48:46 +03:00
do_recv_verify_empty ( fdr ) ;
if ( close ( fdr ) )
error ( 1 , errno , " close r " ) ;
2021-04-01 03:40:20 +03:00
}
static void setup_sockaddr ( int domain , const char * str_addr ,
struct sockaddr_storage * sockaddr )
{
struct sockaddr_in6 * addr6 = ( void * ) sockaddr ;
struct sockaddr_in * addr4 = ( void * ) sockaddr ;
switch ( domain ) {
case PF_INET :
memset ( addr4 , 0 , sizeof ( * addr4 ) ) ;
addr4 - > sin_family = AF_INET ;
addr4 - > sin_port = htons ( cfg_port ) ;
if ( str_addr & &
inet_pton ( AF_INET , str_addr , & ( addr4 - > sin_addr ) ) ! = 1 )
error ( 1 , 0 , " ipv4 parse error: %s " , str_addr ) ;
break ;
case PF_INET6 :
memset ( addr6 , 0 , sizeof ( * addr6 ) ) ;
addr6 - > sin6_family = AF_INET6 ;
addr6 - > sin6_port = htons ( cfg_port ) ;
if ( str_addr & &
inet_pton ( AF_INET6 , str_addr , & ( addr6 - > sin6_addr ) ) ! = 1 )
error ( 1 , 0 , " ipv6 parse error: %s " , str_addr ) ;
break ;
}
2019-05-23 20:48:46 +03:00
}
static int parse_io ( const char * optarg , struct timed_send * array )
{
char * arg , * tok ;
int aoff = 0 ;
arg = strdup ( optarg ) ;
if ( ! arg )
error ( 1 , errno , " strdup " ) ;
while ( ( tok = strtok ( arg , " , " ) ) ) {
arg = NULL ; /* only pass non-zero on first call */
if ( aoff / 2 = = MAX_NUM_PKT )
error ( 1 , 0 , " exceeds max pkt count (%d) " , MAX_NUM_PKT ) ;
if ( aoff & 1 ) { /* parse delay */
array - > delay_us = strtol ( tok , NULL , 0 ) * 1000 ;
array + + ;
} else { /* parse character */
array - > data = tok [ 0 ] ;
}
aoff + + ;
}
free ( arg ) ;
return aoff / 2 ;
}
2021-04-01 03:40:20 +03:00
static void usage ( const char * progname )
{
fprintf ( stderr , " \n Usage: %s [options] <payload> \n "
" Options: \n "
" -4 only IPv4 \n "
" -6 only IPv6 \n "
2022-05-02 12:46:38 +03:00
" -c <clock> monotonic or tai (default) \n "
2021-04-01 03:40:20 +03:00
" -D <addr> destination IP address (server) \n "
" -S <addr> source IP address (client) \n "
" -r run rx mode \n "
" -t <nsec> start time (UTC nanoseconds) \n "
" -m <mark> socket mark \n "
" \n " ,
progname ) ;
exit ( 1 ) ;
}
2019-05-23 20:48:46 +03:00
static void parse_opts ( int argc , char * * argv )
{
2021-04-01 03:40:20 +03:00
char * daddr = NULL , * saddr = NULL ;
int domain = PF_UNSPEC ;
int c ;
2019-05-23 20:48:46 +03:00
2021-04-01 03:40:20 +03:00
while ( ( c = getopt ( argc , argv , " 46c:S:D:rt:m: " ) ) ! = - 1 ) {
2019-05-23 20:48:46 +03:00
switch ( c ) {
case ' 4 ' :
2021-04-01 03:40:20 +03:00
if ( domain ! = PF_UNSPEC )
error ( 1 , 0 , " Pass one of -4 or -6 " ) ;
domain = PF_INET ;
cfg_alen = sizeof ( struct sockaddr_in ) ;
cfg_errq_level = SOL_IP ;
cfg_errq_type = IP_RECVERR ;
2019-05-23 20:48:46 +03:00
break ;
case ' 6 ' :
2021-04-01 03:40:20 +03:00
if ( domain ! = PF_UNSPEC )
error ( 1 , 0 , " Pass one of -4 or -6 " ) ;
domain = PF_INET6 ;
cfg_alen = sizeof ( struct sockaddr_in6 ) ;
cfg_errq_level = SOL_IPV6 ;
cfg_errq_type = IPV6_RECVERR ;
2019-05-23 20:48:46 +03:00
break ;
case ' c ' :
if ( ! strcmp ( optarg , " tai " ) )
cfg_clockid = CLOCK_TAI ;
else if ( ! strcmp ( optarg , " monotonic " ) | |
! strcmp ( optarg , " mono " ) )
cfg_clockid = CLOCK_MONOTONIC ;
else
error ( 1 , 0 , " unknown clock id %s " , optarg ) ;
break ;
2021-04-01 03:40:20 +03:00
case ' S ' :
saddr = optarg ;
break ;
case ' D ' :
daddr = optarg ;
break ;
case ' r ' :
cfg_rx = true ;
break ;
case ' t ' :
2022-05-02 12:46:37 +03:00
cfg_start_time_ns = strtoll ( optarg , NULL , 0 ) ;
2021-04-01 03:40:20 +03:00
break ;
case ' m ' :
cfg_mark = strtol ( optarg , NULL , 0 ) ;
break ;
2019-05-23 20:48:46 +03:00
default :
2021-04-01 03:40:20 +03:00
usage ( argv [ 0 ] ) ;
2019-05-23 20:48:46 +03:00
}
}
2021-04-01 03:40:20 +03:00
if ( argc - optind ! = 1 )
usage ( argv [ 0 ] ) ;
if ( domain = = PF_UNSPEC )
error ( 1 , 0 , " Pass one of -4 or -6 " ) ;
if ( ! daddr )
error ( 1 , 0 , " -D <server addr> required \n " ) ;
if ( ! cfg_rx & & ! saddr )
error ( 1 , 0 , " -S <client addr> required \n " ) ;
2019-05-23 20:48:46 +03:00
2021-04-01 03:40:20 +03:00
setup_sockaddr ( domain , daddr , & cfg_dst_addr ) ;
setup_sockaddr ( domain , saddr , & cfg_src_addr ) ;
cfg_num_pkt = parse_io ( argv [ optind ] , cfg_buf ) ;
2019-05-23 20:48:46 +03:00
}
int main ( int argc , char * * argv )
{
parse_opts ( argc , argv ) ;
2021-04-01 03:40:20 +03:00
if ( cfg_rx )
do_test_rx ( ( void * ) & cfg_dst_addr , cfg_alen ) ;
else
do_test_tx ( ( void * ) & cfg_src_addr , cfg_alen ) ;
2019-05-23 20:48:46 +03:00
return 0 ;
}