2019-06-01 10:08:55 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2017-10-05 16:46:54 -04:00
/*
* vsock_diag_test - vsock_diag . ko test suite
*
* Copyright ( C ) 2017 Red Hat , Inc .
*
* Author : Stefan Hajnoczi < stefanha @ redhat . com >
*/
# include <getopt.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include <unistd.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <linux/list.h>
# include <linux/net.h>
# include <linux/netlink.h>
# include <linux/sock_diag.h>
2019-12-18 19:06:58 +01:00
# include <linux/vm_sockets_diag.h>
2017-10-05 16:46:54 -04:00
# include <netinet/tcp.h>
# include "timeout.h"
# include "control.h"
2019-12-18 19:07:00 +01:00
# include "util.h"
2017-10-05 16:46:54 -04:00
/* Per-socket status */
struct vsock_stat {
struct list_head list ;
struct vsock_diag_msg msg ;
} ;
static const char * sock_type_str ( int type )
{
switch ( type ) {
case SOCK_DGRAM :
return " DGRAM " ;
case SOCK_STREAM :
return " STREAM " ;
2024-01-24 22:32:55 +03:00
case SOCK_SEQPACKET :
return " SEQPACKET " ;
2017-10-05 16:46:54 -04:00
default :
return " INVALID TYPE " ;
}
}
static const char * sock_state_str ( int state )
{
switch ( state ) {
case TCP_CLOSE :
return " UNCONNECTED " ;
case TCP_SYN_SENT :
return " CONNECTING " ;
case TCP_ESTABLISHED :
return " CONNECTED " ;
case TCP_CLOSING :
return " DISCONNECTING " ;
case TCP_LISTEN :
return " LISTEN " ;
default :
return " INVALID STATE " ;
}
}
static const char * sock_shutdown_str ( int shutdown )
{
switch ( shutdown ) {
case 1 :
return " RCV_SHUTDOWN " ;
case 2 :
return " SEND_SHUTDOWN " ;
case 3 :
return " RCV_SHUTDOWN | SEND_SHUTDOWN " ;
default :
return " 0 " ;
}
}
static void print_vsock_addr ( FILE * fp , unsigned int cid , unsigned int port )
{
if ( cid = = VMADDR_CID_ANY )
fprintf ( fp , " *: " ) ;
else
fprintf ( fp , " %u: " , cid ) ;
if ( port = = VMADDR_PORT_ANY )
fprintf ( fp , " * " ) ;
else
fprintf ( fp , " %u " , port ) ;
}
static void print_vsock_stat ( FILE * fp , struct vsock_stat * st )
{
print_vsock_addr ( fp , st - > msg . vdiag_src_cid , st - > msg . vdiag_src_port ) ;
fprintf ( fp , " " ) ;
print_vsock_addr ( fp , st - > msg . vdiag_dst_cid , st - > msg . vdiag_dst_port ) ;
fprintf ( fp , " %s %s %s %u \n " ,
sock_type_str ( st - > msg . vdiag_type ) ,
sock_state_str ( st - > msg . vdiag_state ) ,
sock_shutdown_str ( st - > msg . vdiag_shutdown ) ,
st - > msg . vdiag_ino ) ;
}
static void print_vsock_stats ( FILE * fp , struct list_head * head )
{
struct vsock_stat * st ;
list_for_each_entry ( st , head , list )
print_vsock_stat ( fp , st ) ;
}
static struct vsock_stat * find_vsock_stat ( struct list_head * head , int fd )
{
struct vsock_stat * st ;
struct stat stat ;
if ( fstat ( fd , & stat ) < 0 ) {
perror ( " fstat " ) ;
exit ( EXIT_FAILURE ) ;
}
list_for_each_entry ( st , head , list )
if ( st - > msg . vdiag_ino = = stat . st_ino )
return st ;
fprintf ( stderr , " cannot find fd %d \n " , fd ) ;
exit ( EXIT_FAILURE ) ;
}
static void check_no_sockets ( struct list_head * head )
{
if ( ! list_empty ( head ) ) {
fprintf ( stderr , " expected no sockets \n " ) ;
print_vsock_stats ( stderr , head ) ;
exit ( 1 ) ;
}
}
static void check_num_sockets ( struct list_head * head , int expected )
{
struct list_head * node ;
int n = 0 ;
list_for_each ( node , head )
n + + ;
if ( n ! = expected ) {
fprintf ( stderr , " expected %d sockets, found %d \n " ,
expected , n ) ;
print_vsock_stats ( stderr , head ) ;
exit ( EXIT_FAILURE ) ;
}
}
static void check_socket_state ( struct vsock_stat * st , __u8 state )
{
if ( st - > msg . vdiag_state ! = state ) {
fprintf ( stderr , " expected socket state %#x, got %#x \n " ,
state , st - > msg . vdiag_state ) ;
exit ( EXIT_FAILURE ) ;
}
}
static void send_req ( int fd )
{
struct sockaddr_nl nladdr = {
. nl_family = AF_NETLINK ,
} ;
struct {
struct nlmsghdr nlh ;
struct vsock_diag_req vreq ;
} req = {
. nlh = {
. nlmsg_len = sizeof ( req ) ,
. nlmsg_type = SOCK_DIAG_BY_FAMILY ,
. nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP ,
} ,
. vreq = {
. sdiag_family = AF_VSOCK ,
. vdiag_states = ~ ( __u32 ) 0 ,
} ,
} ;
struct iovec iov = {
. iov_base = & req ,
. iov_len = sizeof ( req ) ,
} ;
struct msghdr msg = {
. msg_name = & nladdr ,
. msg_namelen = sizeof ( nladdr ) ,
. msg_iov = & iov ,
. msg_iovlen = 1 ,
} ;
for ( ; ; ) {
if ( sendmsg ( fd , & msg , 0 ) < 0 ) {
if ( errno = = EINTR )
continue ;
perror ( " sendmsg " ) ;
exit ( EXIT_FAILURE ) ;
}
return ;
}
}
static ssize_t recv_resp ( int fd , void * buf , size_t len )
{
struct sockaddr_nl nladdr = {
. nl_family = AF_NETLINK ,
} ;
struct iovec iov = {
. iov_base = buf ,
. iov_len = len ,
} ;
struct msghdr msg = {
. msg_name = & nladdr ,
. msg_namelen = sizeof ( nladdr ) ,
. msg_iov = & iov ,
. msg_iovlen = 1 ,
} ;
ssize_t ret ;
do {
ret = recvmsg ( fd , & msg , 0 ) ;
} while ( ret < 0 & & errno = = EINTR ) ;
if ( ret < 0 ) {
perror ( " recvmsg " ) ;
exit ( EXIT_FAILURE ) ;
}
return ret ;
}
static void add_vsock_stat ( struct list_head * sockets ,
const struct vsock_diag_msg * resp )
{
struct vsock_stat * st ;
st = malloc ( sizeof ( * st ) ) ;
if ( ! st ) {
perror ( " malloc " ) ;
exit ( EXIT_FAILURE ) ;
}
st - > msg = * resp ;
list_add_tail ( & st - > list , sockets ) ;
}
/*
* Read vsock stats into a list .
*/
static void read_vsock_stat ( struct list_head * sockets )
{
long buf [ 8192 / sizeof ( long ) ] ;
int fd ;
fd = socket ( AF_NETLINK , SOCK_RAW , NETLINK_SOCK_DIAG ) ;
if ( fd < 0 ) {
perror ( " socket " ) ;
exit ( EXIT_FAILURE ) ;
}
send_req ( fd ) ;
for ( ; ; ) {
const struct nlmsghdr * h ;
ssize_t ret ;
ret = recv_resp ( fd , buf , sizeof ( buf ) ) ;
if ( ret = = 0 )
goto done ;
if ( ret < sizeof ( * h ) ) {
fprintf ( stderr , " short read of %zd bytes \n " , ret ) ;
exit ( EXIT_FAILURE ) ;
}
h = ( struct nlmsghdr * ) buf ;
while ( NLMSG_OK ( h , ret ) ) {
if ( h - > nlmsg_type = = NLMSG_DONE )
goto done ;
if ( h - > nlmsg_type = = NLMSG_ERROR ) {
const struct nlmsgerr * err = NLMSG_DATA ( h ) ;
if ( h - > nlmsg_len < NLMSG_LENGTH ( sizeof ( * err ) ) )
fprintf ( stderr , " NLMSG_ERROR \n " ) ;
else {
errno = - err - > error ;
perror ( " NLMSG_ERROR " ) ;
}
exit ( EXIT_FAILURE ) ;
}
if ( h - > nlmsg_type ! = SOCK_DIAG_BY_FAMILY ) {
fprintf ( stderr , " unexpected nlmsg_type %#x \n " ,
h - > nlmsg_type ) ;
exit ( EXIT_FAILURE ) ;
}
if ( h - > nlmsg_len <
NLMSG_LENGTH ( sizeof ( struct vsock_diag_msg ) ) ) {
fprintf ( stderr , " short vsock_diag_msg \n " ) ;
exit ( EXIT_FAILURE ) ;
}
add_vsock_stat ( sockets , NLMSG_DATA ( h ) ) ;
h = NLMSG_NEXT ( h , ret ) ;
}
}
done :
close ( fd ) ;
}
static void free_sock_stat ( struct list_head * sockets )
{
struct vsock_stat * st ;
struct vsock_stat * next ;
list_for_each_entry_safe ( st , next , sockets , list )
free ( st ) ;
}
2019-12-18 19:07:00 +01:00
static void test_no_sockets ( const struct test_opts * opts )
2017-10-05 16:46:54 -04:00
{
LIST_HEAD ( sockets ) ;
read_vsock_stat ( & sockets ) ;
check_no_sockets ( & sockets ) ;
}
2019-12-18 19:07:00 +01:00
static void test_listen_socket_server ( const struct test_opts * opts )
2017-10-05 16:46:54 -04:00
{
union {
struct sockaddr sa ;
struct sockaddr_vm svm ;
} addr = {
. svm = {
. svm_family = AF_VSOCK ,
2024-01-23 10:27:50 +03:00
. svm_port = opts - > peer_port ,
2017-10-05 16:46:54 -04:00
. svm_cid = VMADDR_CID_ANY ,
} ,
} ;
LIST_HEAD ( sockets ) ;
struct vsock_stat * st ;
int fd ;
fd = socket ( AF_VSOCK , SOCK_STREAM , 0 ) ;
if ( bind ( fd , & addr . sa , sizeof ( addr . svm ) ) < 0 ) {
perror ( " bind " ) ;
exit ( EXIT_FAILURE ) ;
}
if ( listen ( fd , 1 ) < 0 ) {
perror ( " listen " ) ;
exit ( EXIT_FAILURE ) ;
}
read_vsock_stat ( & sockets ) ;
check_num_sockets ( & sockets , 1 ) ;
st = find_vsock_stat ( & sockets , fd ) ;
check_socket_state ( st , TCP_LISTEN ) ;
close ( fd ) ;
free_sock_stat ( & sockets ) ;
}
2019-12-18 19:07:00 +01:00
static void test_connect_client ( const struct test_opts * opts )
2017-10-05 16:46:54 -04:00
{
int fd ;
LIST_HEAD ( sockets ) ;
struct vsock_stat * st ;
2024-01-23 10:27:50 +03:00
fd = vsock_stream_connect ( opts - > peer_cid , opts - > peer_port ) ;
2019-12-18 19:07:01 +01:00
if ( fd < 0 ) {
2017-10-05 16:46:54 -04:00
perror ( " connect " ) ;
exit ( EXIT_FAILURE ) ;
}
read_vsock_stat ( & sockets ) ;
check_num_sockets ( & sockets , 1 ) ;
st = find_vsock_stat ( & sockets , fd ) ;
check_socket_state ( st , TCP_ESTABLISHED ) ;
control_expectln ( " DONE " ) ;
control_writeln ( " DONE " ) ;
close ( fd ) ;
free_sock_stat ( & sockets ) ;
}
2019-12-18 19:07:00 +01:00
static void test_connect_server ( const struct test_opts * opts )
2017-10-05 16:46:54 -04:00
{
struct vsock_stat * st ;
2019-12-18 19:07:01 +01:00
LIST_HEAD ( sockets ) ;
2017-10-05 16:46:54 -04:00
int client_fd ;
2024-01-23 10:27:50 +03:00
client_fd = vsock_stream_accept ( VMADDR_CID_ANY , opts - > peer_port , NULL ) ;
2017-10-05 16:46:54 -04:00
if ( client_fd < 0 ) {
perror ( " accept " ) ;
exit ( EXIT_FAILURE ) ;
}
read_vsock_stat ( & sockets ) ;
2019-12-18 19:07:01 +01:00
check_num_sockets ( & sockets , 1 ) ;
2017-10-05 16:46:54 -04:00
st = find_vsock_stat ( & sockets , client_fd ) ;
check_socket_state ( st , TCP_ESTABLISHED ) ;
control_writeln ( " DONE " ) ;
control_expectln ( " DONE " ) ;
close ( client_fd ) ;
free_sock_stat ( & sockets ) ;
}
2019-12-18 19:07:00 +01:00
static struct test_case test_cases [ ] = {
2017-10-05 16:46:54 -04:00
{
. name = " No sockets " ,
. run_server = test_no_sockets ,
} ,
{
. name = " Listen socket " ,
. run_server = test_listen_socket_server ,
} ,
{
. name = " Connect " ,
. run_client = test_connect_client ,
. run_server = test_connect_server ,
} ,
{ } ,
} ;
static const char optstring [ ] = " " ;
static const struct option longopts [ ] = {
{
. name = " control-host " ,
. has_arg = required_argument ,
. val = ' H ' ,
} ,
{
. name = " control-port " ,
. has_arg = required_argument ,
. val = ' P ' ,
} ,
{
. name = " mode " ,
. has_arg = required_argument ,
. val = ' m ' ,
} ,
{
. name = " peer-cid " ,
. has_arg = required_argument ,
. val = ' p ' ,
} ,
2024-01-23 10:27:50 +03:00
{
. name = " peer-port " ,
. has_arg = required_argument ,
. val = ' q ' ,
} ,
2019-12-18 19:07:06 +01:00
{
. name = " list " ,
. has_arg = no_argument ,
. val = ' l ' ,
} ,
{
. name = " skip " ,
. has_arg = required_argument ,
. val = ' s ' ,
} ,
2017-10-05 16:46:54 -04:00
{
. name = " help " ,
. has_arg = no_argument ,
. val = ' ? ' ,
} ,
{ } ,
} ;
static void usage ( void )
{
2024-01-23 10:27:50 +03:00
fprintf ( stderr , " Usage: vsock_diag_test [--help] [--control-host=<host>] --control-port=<port> --mode=client|server --peer-cid=<cid> [--peer-port=<port>] [--list] [--skip=<test_id>] \n "
2017-10-05 16:46:54 -04:00
" \n "
" Server: vsock_diag_test --control-port=1234 --mode=server --peer-cid=3 \n "
" Client: vsock_diag_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2 \n "
" \n "
" Run vsock_diag.ko tests. Must be launched in both \n "
" guest and host. One side must use --mode=client and \n "
" the other side must use --mode=server. \n "
" \n "
" A TCP control socket connection is used to coordinate tests \n "
" between the client and the server. The server requires a \n "
" listen address and the client requires an address to \n "
" connect to. \n "
" \n "
2019-12-18 19:07:07 +01:00
" The CID of the other side must be given with --peer-cid=<cid>. \n "
" \n "
" Options: \n "
" --help This help message \n "
" --control-host <host> Server IP address to connect to \n "
" --control-port <port> Server port to listen on/connect to \n "
" --mode client|server Server or client mode \n "
" --peer-cid <cid> CID of the other side \n "
2024-01-23 10:27:50 +03:00
" --peer-port <port> AF_VSOCK port used for the test [default: %d] \n "
2019-12-18 19:07:07 +01:00
" --list List of tests that will be executed \n "
" --skip <test_id> Test ID to skip; \n "
2024-01-23 10:27:50 +03:00
" use multiple --skip options to skip more tests \n " ,
DEFAULT_PEER_PORT
2019-12-18 19:07:07 +01:00
) ;
2017-10-05 16:46:54 -04:00
exit ( EXIT_FAILURE ) ;
}
int main ( int argc , char * * argv )
{
const char * control_host = NULL ;
const char * control_port = NULL ;
2019-12-18 19:07:00 +01:00
struct test_opts opts = {
. mode = TEST_MODE_UNSET ,
. peer_cid = VMADDR_CID_ANY ,
2024-01-23 10:27:50 +03:00
. peer_port = DEFAULT_PEER_PORT ,
2019-12-18 19:07:00 +01:00
} ;
2017-10-05 16:46:54 -04:00
init_signals ( ) ;
for ( ; ; ) {
int opt = getopt_long ( argc , argv , optstring , longopts , NULL ) ;
if ( opt = = - 1 )
break ;
switch ( opt ) {
case ' H ' :
control_host = optarg ;
break ;
case ' m ' :
if ( strcmp ( optarg , " client " ) = = 0 )
2019-12-18 19:07:00 +01:00
opts . mode = TEST_MODE_CLIENT ;
2017-10-05 16:46:54 -04:00
else if ( strcmp ( optarg , " server " ) = = 0 )
2019-12-18 19:07:00 +01:00
opts . mode = TEST_MODE_SERVER ;
2017-10-05 16:46:54 -04:00
else {
fprintf ( stderr , " --mode must be \" client \" or \" server \" \n " ) ;
return EXIT_FAILURE ;
}
break ;
case ' p ' :
2019-12-18 19:07:00 +01:00
opts . peer_cid = parse_cid ( optarg ) ;
2017-10-05 16:46:54 -04:00
break ;
2024-01-23 10:27:50 +03:00
case ' q ' :
opts . peer_port = parse_port ( optarg ) ;
break ;
2017-10-05 16:46:54 -04:00
case ' P ' :
control_port = optarg ;
break ;
2019-12-18 19:07:06 +01:00
case ' l ' :
list_tests ( test_cases ) ;
break ;
case ' s ' :
skip_test ( test_cases , ARRAY_SIZE ( test_cases ) - 1 ,
optarg ) ;
break ;
2017-10-05 16:46:54 -04:00
case ' ? ' :
default :
usage ( ) ;
}
}
if ( ! control_port )
usage ( ) ;
2019-12-18 19:07:00 +01:00
if ( opts . mode = = TEST_MODE_UNSET )
2017-10-05 16:46:54 -04:00
usage ( ) ;
2019-12-18 19:07:00 +01:00
if ( opts . peer_cid = = VMADDR_CID_ANY )
2017-10-05 16:46:54 -04:00
usage ( ) ;
if ( ! control_host ) {
2019-12-18 19:07:00 +01:00
if ( opts . mode ! = TEST_MODE_SERVER )
2017-10-05 16:46:54 -04:00
usage ( ) ;
control_host = " 0.0.0.0 " ;
}
2019-12-18 19:07:00 +01:00
control_init ( control_host , control_port ,
opts . mode = = TEST_MODE_SERVER ) ;
2017-10-05 16:46:54 -04:00
2019-12-18 19:07:00 +01:00
run_tests ( test_cases , & opts ) ;
2017-10-05 16:46:54 -04:00
control_cleanup ( ) ;
return EXIT_SUCCESS ;
}