2019-08-01 21:56:34 +03:00
// SPDX-License-Identifier: GPL-2.0
/* nettest - used for functional tests of networking APIs
*
* Copyright ( c ) 2013 - 2019 David Ahern < dsahern @ gmail . com > . All rights reserved .
*/
# define _GNU_SOURCE
# include <features.h>
# include <sys/types.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
2021-01-14 06:09:40 +03:00
# include <sys/wait.h>
2019-08-01 21:56:34 +03:00
# include <linux/tcp.h>
2021-07-20 23:35:29 +03:00
# include <linux/udp.h>
2019-08-01 21:56:34 +03:00
# include <arpa/inet.h>
# include <net/if.h>
# include <netinet/in.h>
2021-07-20 23:35:29 +03:00
# include <netinet/ip.h>
2019-08-01 21:56:34 +03:00
# include <netdb.h>
# include <fcntl.h>
# include <libgen.h>
# include <limits.h>
2021-01-14 06:09:40 +03:00
# include <sched.h>
2019-08-01 21:56:34 +03:00
# include <stdarg.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
# include <time.h>
# include <errno.h>
2021-07-20 23:35:29 +03:00
# include <linux/xfrm.h>
# include <linux/ipsec.h>
# include <linux/pfkeyv2.h>
2019-08-01 21:56:34 +03:00
# ifndef IPV6_UNICAST_IF
# define IPV6_UNICAST_IF 76
# endif
# ifndef IPV6_MULTICAST_IF
# define IPV6_MULTICAST_IF 17
# endif
# define DEFAULT_PORT 12345
2021-01-14 06:09:40 +03:00
# define NS_PREFIX " / run / netns / "
2019-08-01 21:56:34 +03:00
# ifndef MAX
# define MAX(a, b) ((a) > (b) ? (a) : (b))
# endif
# ifndef MIN
# define MIN(a, b) ((a) < (b) ? (a) : (b))
# endif
struct sock_args {
/* local address */
2021-01-14 06:09:39 +03:00
const char * local_addr_str ;
2021-01-14 06:09:49 +03:00
const char * client_local_addr_str ;
2019-08-01 21:56:34 +03:00
union {
struct in_addr in ;
struct in6_addr in6 ;
} local_addr ;
/* remote address */
2021-01-14 06:09:39 +03:00
const char * remote_addr_str ;
2019-08-01 21:56:34 +03:00
union {
struct in_addr in ;
struct in6_addr in6 ;
} remote_addr ;
int scope_id ; /* remote scope; v6 send only */
struct in_addr grp ; /* multicast group */
unsigned int has_local_ip : 1 ,
has_remote_ip : 1 ,
has_grp : 1 ,
has_expected_laddr : 1 ,
has_expected_raddr : 1 ,
bind_test_only : 1 ;
unsigned short port ;
int type ; /* DGRAM, STREAM, RAW */
int protocol ;
int version ; /* AF_INET/AF_INET6 */
int use_setsockopt ;
int use_cmsg ;
const char * dev ;
2021-01-14 06:09:47 +03:00
const char * server_dev ;
2019-08-01 21:56:34 +03:00
int ifindex ;
2019-12-31 01:14:31 +03:00
2021-01-14 06:09:40 +03:00
const char * clientns ;
const char * serverns ;
2019-08-01 21:56:34 +03:00
const char * password ;
2021-01-14 06:09:46 +03:00
const char * client_pw ;
2019-12-31 01:14:31 +03:00
/* prefix for MD5 password */
2021-01-14 06:09:39 +03:00
const char * md5_prefix_str ;
2019-12-31 01:14:31 +03:00
union {
struct sockaddr_in v4 ;
struct sockaddr_in6 v6 ;
} md5_prefix ;
unsigned int prefix_len ;
2019-08-01 21:56:34 +03:00
/* expected addresses and device index for connection */
2021-01-14 06:09:37 +03:00
const char * expected_dev ;
2021-01-14 06:09:47 +03:00
const char * expected_server_dev ;
2019-08-01 21:56:34 +03:00
int expected_ifindex ;
/* local address */
2021-01-14 06:09:39 +03:00
const char * expected_laddr_str ;
2019-08-01 21:56:34 +03:00
union {
struct in_addr in ;
struct in6_addr in6 ;
} expected_laddr ;
/* remote address */
2021-01-14 06:09:39 +03:00
const char * expected_raddr_str ;
2019-08-01 21:56:34 +03:00
union {
struct in_addr in ;
struct in6_addr in6 ;
} expected_raddr ;
2021-07-20 23:35:29 +03:00
/* ESP in UDP encap test */
int use_xfrm ;
2019-08-01 21:56:34 +03:00
} ;
static int server_mode ;
static unsigned int prog_timeout = 5 ;
static unsigned int interactive ;
static int iter = 1 ;
static char * msg = " Hello world! " ;
static int msglen ;
static int quiet ;
static int try_broadcast = 1 ;
static char * timestamp ( char * timebuf , int buflen )
{
time_t now ;
now = time ( NULL ) ;
if ( strftime ( timebuf , buflen , " %T " , localtime ( & now ) ) = = 0 ) {
memset ( timebuf , 0 , buflen ) ;
strncpy ( timebuf , " 00:00:00 " , buflen - 1 ) ;
}
return timebuf ;
}
static void log_msg ( const char * format , . . . )
{
char timebuf [ 64 ] ;
va_list args ;
if ( quiet )
return ;
fprintf ( stdout , " %s %s: " ,
timestamp ( timebuf , sizeof ( timebuf ) ) ,
server_mode ? " server " : " client " ) ;
va_start ( args , format ) ;
vfprintf ( stdout , format , args ) ;
va_end ( args ) ;
fflush ( stdout ) ;
}
static void log_error ( const char * format , . . . )
{
char timebuf [ 64 ] ;
va_list args ;
if ( quiet )
return ;
fprintf ( stderr , " %s %s: " ,
timestamp ( timebuf , sizeof ( timebuf ) ) ,
server_mode ? " server " : " client " ) ;
va_start ( args , format ) ;
vfprintf ( stderr , format , args ) ;
va_end ( args ) ;
fflush ( stderr ) ;
}
static void log_err_errno ( const char * fmt , . . . )
{
char timebuf [ 64 ] ;
va_list args ;
if ( quiet )
return ;
fprintf ( stderr , " %s %s: " ,
timestamp ( timebuf , sizeof ( timebuf ) ) ,
server_mode ? " server " : " client " ) ;
va_start ( args , fmt ) ;
vfprintf ( stderr , fmt , args ) ;
va_end ( args ) ;
fprintf ( stderr , " : %d: %s \n " , errno , strerror ( errno ) ) ;
fflush ( stderr ) ;
}
static void log_address ( const char * desc , struct sockaddr * sa )
{
char addrstr [ 64 ] ;
if ( quiet )
return ;
if ( sa - > sa_family = = AF_INET ) {
struct sockaddr_in * s = ( struct sockaddr_in * ) sa ;
2021-01-14 06:09:43 +03:00
log_msg ( " %s %s:%d \n " ,
2019-08-01 21:56:34 +03:00
desc ,
inet_ntop ( AF_INET , & s - > sin_addr , addrstr ,
sizeof ( addrstr ) ) ,
ntohs ( s - > sin_port ) ) ;
} else if ( sa - > sa_family = = AF_INET6 ) {
struct sockaddr_in6 * s6 = ( struct sockaddr_in6 * ) sa ;
2021-01-14 06:09:43 +03:00
log_msg ( " %s [%s]:%d \n " ,
2019-08-01 21:56:34 +03:00
desc ,
inet_ntop ( AF_INET6 , & s6 - > sin6_addr , addrstr ,
sizeof ( addrstr ) ) ,
ntohs ( s6 - > sin6_port ) ) ;
}
fflush ( stdout ) ;
}
2021-01-14 06:09:40 +03:00
static int switch_ns ( const char * ns )
{
char path [ PATH_MAX ] ;
int fd , ret ;
if ( geteuid ( ) )
log_error ( " warning: likely need root to set netns %s! \n " , ns ) ;
snprintf ( path , sizeof ( path ) , " %s%s " , NS_PREFIX , ns ) ;
fd = open ( path , 0 ) ;
if ( fd < 0 ) {
log_err_errno ( " Failed to open netns path; can not switch netns " ) ;
return 1 ;
}
ret = setns ( fd , CLONE_NEWNET ) ;
close ( fd ) ;
return ret ;
}
2019-12-31 01:14:31 +03:00
static int tcp_md5sig ( int sd , void * addr , socklen_t alen , struct sock_args * args )
2019-08-01 21:56:34 +03:00
{
2019-12-31 01:14:31 +03:00
int keylen = strlen ( args - > password ) ;
struct tcp_md5sig md5sig = { } ;
int opt = TCP_MD5SIG ;
2019-08-01 21:56:34 +03:00
int rc ;
md5sig . tcpm_keylen = keylen ;
2019-12-31 01:14:31 +03:00
memcpy ( md5sig . tcpm_key , args - > password , keylen ) ;
2019-08-01 21:56:34 +03:00
2019-12-31 01:14:31 +03:00
if ( args - > prefix_len ) {
opt = TCP_MD5SIG_EXT ;
md5sig . tcpm_flags | = TCP_MD5SIG_FLAG_PREFIX ;
md5sig . tcpm_prefixlen = args - > prefix_len ;
addr = & args - > md5_prefix ;
}
memcpy ( & md5sig . tcpm_addr , addr , alen ) ;
2019-08-01 21:56:34 +03:00
2019-12-31 01:14:31 +03:00
if ( args - > ifindex ) {
opt = TCP_MD5SIG_EXT ;
md5sig . tcpm_flags | = TCP_MD5SIG_FLAG_IFINDEX ;
md5sig . tcpm_ifindex = args - > ifindex ;
}
rc = setsockopt ( sd , IPPROTO_TCP , opt , & md5sig , sizeof ( md5sig ) ) ;
2019-08-01 21:56:34 +03:00
if ( rc < 0 ) {
/* ENOENT is harmless. Returned when a password is cleared */
if ( errno = = ENOENT )
rc = 0 ;
else
log_err_errno ( " setsockopt(TCP_MD5SIG) " ) ;
}
return rc ;
}
static int tcp_md5_remote ( int sd , struct sock_args * args )
{
struct sockaddr_in sin = {
. sin_family = AF_INET ,
} ;
struct sockaddr_in6 sin6 = {
. sin6_family = AF_INET6 ,
} ;
void * addr ;
int alen ;
switch ( args - > version ) {
case AF_INET :
sin . sin_port = htons ( args - > port ) ;
2021-01-14 06:09:45 +03:00
sin . sin_addr = args - > md5_prefix . v4 . sin_addr ;
2019-08-01 21:56:34 +03:00
addr = & sin ;
alen = sizeof ( sin ) ;
break ;
case AF_INET6 :
sin6 . sin6_port = htons ( args - > port ) ;
2021-01-14 06:09:45 +03:00
sin6 . sin6_addr = args - > md5_prefix . v6 . sin6_addr ;
2019-08-01 21:56:34 +03:00
addr = & sin6 ;
alen = sizeof ( sin6 ) ;
break ;
default :
log_error ( " unknown address family \n " ) ;
exit ( 1 ) ;
}
2019-12-31 01:14:31 +03:00
if ( tcp_md5sig ( sd , addr , alen , args ) )
2019-08-01 21:56:34 +03:00
return - 1 ;
return 0 ;
}
static int get_ifidx ( const char * ifname )
{
struct ifreq ifdata ;
int sd , rc ;
if ( ! ifname | | * ifname = = ' \0 ' )
2019-08-14 20:11:51 +03:00
return - 1 ;
2019-08-01 21:56:34 +03:00
memset ( & ifdata , 0 , sizeof ( ifdata ) ) ;
strcpy ( ifdata . ifr_name , ifname ) ;
sd = socket ( PF_INET , SOCK_DGRAM , IPPROTO_IP ) ;
if ( sd < 0 ) {
log_err_errno ( " socket failed " ) ;
2019-08-14 20:11:51 +03:00
return - 1 ;
2019-08-01 21:56:34 +03:00
}
rc = ioctl ( sd , SIOCGIFINDEX , ( char * ) & ifdata ) ;
close ( sd ) ;
if ( rc ! = 0 ) {
log_err_errno ( " ioctl(SIOCGIFINDEX) failed " ) ;
2019-08-14 20:11:51 +03:00
return - 1 ;
2019-08-01 21:56:34 +03:00
}
return ifdata . ifr_ifindex ;
}
static int bind_to_device ( int sd , const char * name )
{
int rc ;
rc = setsockopt ( sd , SOL_SOCKET , SO_BINDTODEVICE , name , strlen ( name ) + 1 ) ;
if ( rc < 0 )
log_err_errno ( " setsockopt(SO_BINDTODEVICE) " ) ;
return rc ;
}
static int get_bind_to_device ( int sd , char * name , size_t len )
{
int rc ;
socklen_t optlen = len ;
name [ 0 ] = ' \0 ' ;
rc = getsockopt ( sd , SOL_SOCKET , SO_BINDTODEVICE , name , & optlen ) ;
if ( rc < 0 )
log_err_errno ( " setsockopt(SO_BINDTODEVICE) " ) ;
return rc ;
}
static int check_device ( int sd , struct sock_args * args )
{
int ifindex = 0 ;
char name [ 32 ] ;
if ( get_bind_to_device ( sd , name , sizeof ( name ) ) )
* name = ' \0 ' ;
else
ifindex = get_ifidx ( name ) ;
log_msg ( " bound to device %s/%d \n " ,
* name ? name : " <none> " , ifindex ) ;
if ( ! args - > expected_ifindex )
return 0 ;
if ( args - > expected_ifindex ! = ifindex ) {
log_error ( " Device index mismatch: expected %d have %d \n " ,
args - > expected_ifindex , ifindex ) ;
return 1 ;
}
log_msg ( " Device index matches: expected %d have %d \n " ,
args - > expected_ifindex , ifindex ) ;
return 0 ;
}
static int set_pktinfo_v4 ( int sd )
{
int one = 1 ;
int rc ;
rc = setsockopt ( sd , SOL_IP , IP_PKTINFO , & one , sizeof ( one ) ) ;
if ( rc < 0 & & rc ! = - ENOTSUP )
log_err_errno ( " setsockopt(IP_PKTINFO) " ) ;
return rc ;
}
static int set_recvpktinfo_v6 ( int sd )
{
int one = 1 ;
int rc ;
rc = setsockopt ( sd , SOL_IPV6 , IPV6_RECVPKTINFO , & one , sizeof ( one ) ) ;
if ( rc < 0 & & rc ! = - ENOTSUP )
log_err_errno ( " setsockopt(IPV6_RECVPKTINFO) " ) ;
return rc ;
}
static int set_recverr_v4 ( int sd )
{
int one = 1 ;
int rc ;
rc = setsockopt ( sd , SOL_IP , IP_RECVERR , & one , sizeof ( one ) ) ;
if ( rc < 0 & & rc ! = - ENOTSUP )
log_err_errno ( " setsockopt(IP_RECVERR) " ) ;
return rc ;
}
static int set_recverr_v6 ( int sd )
{
int one = 1 ;
int rc ;
rc = setsockopt ( sd , SOL_IPV6 , IPV6_RECVERR , & one , sizeof ( one ) ) ;
if ( rc < 0 & & rc ! = - ENOTSUP )
log_err_errno ( " setsockopt(IPV6_RECVERR) " ) ;
return rc ;
}
static int set_unicast_if ( int sd , int ifindex , int version )
{
int opt = IP_UNICAST_IF ;
int level = SOL_IP ;
int rc ;
ifindex = htonl ( ifindex ) ;
if ( version = = AF_INET6 ) {
opt = IPV6_UNICAST_IF ;
level = SOL_IPV6 ;
}
rc = setsockopt ( sd , level , opt , & ifindex , sizeof ( ifindex ) ) ;
if ( rc < 0 )
log_err_errno ( " setsockopt(IP_UNICAST_IF) " ) ;
return rc ;
}
static int set_multicast_if ( int sd , int ifindex )
{
struct ip_mreqn mreq = { . imr_ifindex = ifindex } ;
int rc ;
rc = setsockopt ( sd , SOL_IP , IP_MULTICAST_IF , & mreq , sizeof ( mreq ) ) ;
if ( rc < 0 )
log_err_errno ( " setsockopt(IP_MULTICAST_IF) " ) ;
return rc ;
}
2019-08-14 20:11:51 +03:00
static int set_membership ( int sd , uint32_t grp , uint32_t addr , int ifindex )
2019-08-01 21:56:34 +03:00
{
uint32_t if_addr = addr ;
struct ip_mreqn mreq ;
int rc ;
2019-08-14 20:11:51 +03:00
if ( addr = = htonl ( INADDR_ANY ) & & ! ifindex ) {
2019-08-01 21:56:34 +03:00
log_error ( " Either local address or device needs to be given for multicast membership \n " ) ;
return - 1 ;
}
mreq . imr_multiaddr . s_addr = grp ;
mreq . imr_address . s_addr = if_addr ;
2019-08-14 20:11:51 +03:00
mreq . imr_ifindex = ifindex ;
2019-08-01 21:56:34 +03:00
rc = setsockopt ( sd , IPPROTO_IP , IP_ADD_MEMBERSHIP , & mreq , sizeof ( mreq ) ) ;
if ( rc < 0 ) {
log_err_errno ( " setsockopt(IP_ADD_MEMBERSHIP) " ) ;
return - 1 ;
}
return 0 ;
}
static int set_broadcast ( int sd )
{
unsigned int one = 1 ;
int rc = 0 ;
if ( setsockopt ( sd , SOL_SOCKET , SO_BROADCAST , & one , sizeof ( one ) ) ! = 0 ) {
log_err_errno ( " setsockopt(SO_BROADCAST) " ) ;
rc = - 1 ;
}
return rc ;
}
static int set_reuseport ( int sd )
{
unsigned int one = 1 ;
int rc = 0 ;
if ( setsockopt ( sd , SOL_SOCKET , SO_REUSEPORT , & one , sizeof ( one ) ) ! = 0 ) {
log_err_errno ( " setsockopt(SO_REUSEPORT) " ) ;
rc = - 1 ;
}
return rc ;
}
static int set_reuseaddr ( int sd )
{
unsigned int one = 1 ;
int rc = 0 ;
if ( setsockopt ( sd , SOL_SOCKET , SO_REUSEADDR , & one , sizeof ( one ) ) ! = 0 ) {
log_err_errno ( " setsockopt(SO_REUSEADDR) " ) ;
rc = - 1 ;
}
return rc ;
}
static int str_to_uint ( const char * str , int min , int max , unsigned int * value )
{
int number ;
char * end ;
errno = 0 ;
number = ( unsigned int ) strtoul ( str , & end , 0 ) ;
/* entire string should be consumed by conversion
* and value should be between min and max
*/
if ( ( ( * end = = ' \0 ' ) | | ( * end = = ' \n ' ) ) & & ( end ! = str ) & &
( errno ! = ERANGE ) & & ( min < = number ) & & ( number < = max ) ) {
* value = number ;
return 0 ;
}
return - 1 ;
}
2021-01-14 06:09:37 +03:00
static int resolve_devices ( struct sock_args * args )
{
if ( args - > dev ) {
args - > ifindex = get_ifidx ( args - > dev ) ;
if ( args - > ifindex < 0 ) {
log_error ( " Invalid device name \n " ) ;
return 1 ;
}
}
if ( args - > expected_dev ) {
unsigned int tmp ;
if ( str_to_uint ( args - > expected_dev , 0 , INT_MAX , & tmp ) = = 0 ) {
args - > expected_ifindex = ( int ) tmp ;
} else {
args - > expected_ifindex = get_ifidx ( args - > expected_dev ) ;
if ( args - > expected_ifindex < 0 ) {
fprintf ( stderr , " Invalid expected device \n " ) ;
return 1 ;
}
}
}
return 0 ;
}
2019-08-01 21:56:34 +03:00
static int expected_addr_match ( struct sockaddr * sa , void * expected ,
const char * desc )
{
char addrstr [ 64 ] ;
int rc = 0 ;
if ( sa - > sa_family = = AF_INET ) {
struct sockaddr_in * s = ( struct sockaddr_in * ) sa ;
struct in_addr * exp_in = ( struct in_addr * ) expected ;
if ( s - > sin_addr . s_addr ! = exp_in - > s_addr ) {
2021-01-14 06:09:43 +03:00
log_error ( " %s address does not match expected %s \n " ,
2019-08-01 21:56:34 +03:00
desc ,
inet_ntop ( AF_INET , exp_in ,
addrstr , sizeof ( addrstr ) ) ) ;
rc = 1 ;
}
} else if ( sa - > sa_family = = AF_INET6 ) {
struct sockaddr_in6 * s6 = ( struct sockaddr_in6 * ) sa ;
struct in6_addr * exp_in = ( struct in6_addr * ) expected ;
if ( memcmp ( & s6 - > sin6_addr , exp_in , sizeof ( * exp_in ) ) ) {
2021-01-14 06:09:43 +03:00
log_error ( " %s address does not match expected %s \n " ,
2019-08-01 21:56:34 +03:00
desc ,
inet_ntop ( AF_INET6 , exp_in ,
addrstr , sizeof ( addrstr ) ) ) ;
rc = 1 ;
}
} else {
2021-01-14 06:09:43 +03:00
log_error ( " %s address does not match expected - unknown family \n " ,
2019-08-01 21:56:34 +03:00
desc ) ;
rc = 1 ;
}
if ( ! rc )
log_msg ( " %s address matches expected \n " , desc ) ;
return rc ;
}
static int show_sockstat ( int sd , struct sock_args * args )
{
struct sockaddr_in6 local_addr , remote_addr ;
socklen_t alen = sizeof ( local_addr ) ;
struct sockaddr * sa ;
const char * desc ;
int rc = 0 ;
desc = server_mode ? " server local: " : " client local: " ;
sa = ( struct sockaddr * ) & local_addr ;
if ( getsockname ( sd , sa , & alen ) = = 0 ) {
log_address ( desc , sa ) ;
if ( args - > has_expected_laddr ) {
rc = expected_addr_match ( sa , & args - > expected_laddr ,
" local " ) ;
}
} else {
log_err_errno ( " getsockname failed " ) ;
}
sa = ( struct sockaddr * ) & remote_addr ;
desc = server_mode ? " server peer: " : " client peer: " ;
if ( getpeername ( sd , sa , & alen ) = = 0 ) {
log_address ( desc , sa ) ;
if ( args - > has_expected_raddr ) {
rc | = expected_addr_match ( sa , & args - > expected_raddr ,
" remote " ) ;
}
} else {
log_err_errno ( " getpeername failed " ) ;
}
return rc ;
}
2021-01-14 06:09:38 +03:00
enum addr_type {
ADDR_TYPE_LOCAL ,
ADDR_TYPE_REMOTE ,
ADDR_TYPE_MCAST ,
ADDR_TYPE_EXPECTED_LOCAL ,
ADDR_TYPE_EXPECTED_REMOTE ,
ADDR_TYPE_MD5_PREFIX ,
} ;
static int convert_addr ( struct sock_args * args , const char * _str ,
enum addr_type atype )
{
int pfx_len_max = args - > version = = AF_INET6 ? 128 : 32 ;
int family = args - > version ;
char * str , * dev , * sep ;
struct in6_addr * in6 ;
struct in_addr * in ;
const char * desc ;
void * addr ;
int rc = 0 ;
str = strdup ( _str ) ;
if ( ! str )
return - ENOMEM ;
switch ( atype ) {
case ADDR_TYPE_LOCAL :
desc = " local " ;
addr = & args - > local_addr ;
break ;
case ADDR_TYPE_REMOTE :
desc = " remote " ;
addr = & args - > remote_addr ;
break ;
case ADDR_TYPE_MCAST :
desc = " mcast grp " ;
addr = & args - > grp ;
break ;
case ADDR_TYPE_EXPECTED_LOCAL :
desc = " expected local " ;
addr = & args - > expected_laddr ;
break ;
case ADDR_TYPE_EXPECTED_REMOTE :
desc = " expected remote " ;
addr = & args - > expected_raddr ;
break ;
case ADDR_TYPE_MD5_PREFIX :
desc = " md5 prefix " ;
if ( family = = AF_INET ) {
args - > md5_prefix . v4 . sin_family = AF_INET ;
addr = & args - > md5_prefix . v4 . sin_addr ;
} else if ( family = = AF_INET6 ) {
args - > md5_prefix . v6 . sin6_family = AF_INET6 ;
addr = & args - > md5_prefix . v6 . sin6_addr ;
} else
return 1 ;
sep = strchr ( str , ' / ' ) ;
if ( sep ) {
* sep = ' \0 ' ;
sep + + ;
if ( str_to_uint ( sep , 1 , pfx_len_max ,
& args - > prefix_len ) ! = 0 ) {
fprintf ( stderr , " Invalid port \n " ) ;
return 1 ;
}
} else {
2021-01-14 06:09:45 +03:00
args - > prefix_len = 0 ;
2021-01-14 06:09:38 +03:00
}
break ;
default :
2021-01-14 06:09:43 +03:00
log_error ( " unknown address type \n " ) ;
2021-01-14 06:09:38 +03:00
exit ( 1 ) ;
}
switch ( family ) {
case AF_INET :
in = ( struct in_addr * ) addr ;
if ( str ) {
if ( inet_pton ( AF_INET , str , in ) = = 0 ) {
log_error ( " Invalid %s IP address \n " , desc ) ;
rc = - 1 ;
goto out ;
}
} else {
in - > s_addr = htonl ( INADDR_ANY ) ;
}
break ;
case AF_INET6 :
dev = strchr ( str , ' % ' ) ;
if ( dev ) {
* dev = ' \0 ' ;
dev + + ;
}
in6 = ( struct in6_addr * ) addr ;
if ( str ) {
if ( inet_pton ( AF_INET6 , str , in6 ) = = 0 ) {
log_error ( " Invalid %s IPv6 address \n " , desc ) ;
rc = - 1 ;
goto out ;
}
} else {
* in6 = in6addr_any ;
}
if ( dev ) {
args - > scope_id = get_ifidx ( dev ) ;
if ( args - > scope_id < 0 ) {
log_error ( " Invalid scope on %s IPv6 address \n " ,
desc ) ;
rc = - 1 ;
goto out ;
}
}
break ;
default :
log_error ( " Invalid address family \n " ) ;
}
out :
free ( str ) ;
return rc ;
}
2021-01-14 06:09:39 +03:00
static int validate_addresses ( struct sock_args * args )
{
if ( args - > local_addr_str & &
convert_addr ( args , args - > local_addr_str , ADDR_TYPE_LOCAL ) < 0 )
return 1 ;
if ( args - > remote_addr_str & &
convert_addr ( args , args - > remote_addr_str , ADDR_TYPE_REMOTE ) < 0 )
return 1 ;
if ( args - > md5_prefix_str & &
convert_addr ( args , args - > md5_prefix_str ,
ADDR_TYPE_MD5_PREFIX ) < 0 )
return 1 ;
if ( args - > expected_laddr_str & &
convert_addr ( args , args - > expected_laddr_str ,
ADDR_TYPE_EXPECTED_LOCAL ) )
return 1 ;
if ( args - > expected_raddr_str & &
convert_addr ( args , args - > expected_raddr_str ,
ADDR_TYPE_EXPECTED_REMOTE ) )
return 1 ;
return 0 ;
}
2019-08-01 21:56:34 +03:00
static int get_index_from_cmsg ( struct msghdr * m )
{
struct cmsghdr * cm ;
int ifindex = 0 ;
char buf [ 64 ] ;
for ( cm = ( struct cmsghdr * ) CMSG_FIRSTHDR ( m ) ;
m - > msg_controllen ! = 0 & & cm ;
cm = ( struct cmsghdr * ) CMSG_NXTHDR ( m , cm ) ) {
if ( cm - > cmsg_level = = SOL_IP & &
cm - > cmsg_type = = IP_PKTINFO ) {
struct in_pktinfo * pi ;
pi = ( struct in_pktinfo * ) ( CMSG_DATA ( cm ) ) ;
inet_ntop ( AF_INET , & pi - > ipi_addr , buf , sizeof ( buf ) ) ;
ifindex = pi - > ipi_ifindex ;
} else if ( cm - > cmsg_level = = SOL_IPV6 & &
cm - > cmsg_type = = IPV6_PKTINFO ) {
struct in6_pktinfo * pi6 ;
pi6 = ( struct in6_pktinfo * ) ( CMSG_DATA ( cm ) ) ;
inet_ntop ( AF_INET6 , & pi6 - > ipi6_addr , buf , sizeof ( buf ) ) ;
ifindex = pi6 - > ipi6_ifindex ;
}
}
if ( ifindex ) {
log_msg ( " pktinfo: ifindex %d dest addr %s \n " ,
ifindex , buf ) ;
}
return ifindex ;
}
static int send_msg_no_cmsg ( int sd , void * addr , socklen_t alen )
{
int err ;
again :
err = sendto ( sd , msg , msglen , 0 , addr , alen ) ;
if ( err < 0 ) {
if ( errno = = EACCES & & try_broadcast ) {
try_broadcast = 0 ;
if ( ! set_broadcast ( sd ) )
goto again ;
errno = EACCES ;
}
log_err_errno ( " sendto failed " ) ;
return 1 ;
}
return 0 ;
}
static int send_msg_cmsg ( int sd , void * addr , socklen_t alen ,
int ifindex , int version )
{
unsigned char cmsgbuf [ 64 ] ;
struct iovec iov [ 2 ] ;
struct cmsghdr * cm ;
struct msghdr m ;
int err ;
iov [ 0 ] . iov_base = msg ;
iov [ 0 ] . iov_len = msglen ;
m . msg_iov = iov ;
m . msg_iovlen = 1 ;
m . msg_name = ( caddr_t ) addr ;
m . msg_namelen = alen ;
memset ( cmsgbuf , 0 , sizeof ( cmsgbuf ) ) ;
cm = ( struct cmsghdr * ) cmsgbuf ;
m . msg_control = ( caddr_t ) cm ;
if ( version = = AF_INET ) {
struct in_pktinfo * pi ;
cm - > cmsg_level = SOL_IP ;
cm - > cmsg_type = IP_PKTINFO ;
cm - > cmsg_len = CMSG_LEN ( sizeof ( struct in_pktinfo ) ) ;
pi = ( struct in_pktinfo * ) ( CMSG_DATA ( cm ) ) ;
pi - > ipi_ifindex = ifindex ;
m . msg_controllen = cm - > cmsg_len ;
} else if ( version = = AF_INET6 ) {
struct in6_pktinfo * pi6 ;
cm - > cmsg_level = SOL_IPV6 ;
cm - > cmsg_type = IPV6_PKTINFO ;
cm - > cmsg_len = CMSG_LEN ( sizeof ( struct in6_pktinfo ) ) ;
pi6 = ( struct in6_pktinfo * ) ( CMSG_DATA ( cm ) ) ;
pi6 - > ipi6_ifindex = ifindex ;
m . msg_controllen = cm - > cmsg_len ;
}
again :
err = sendmsg ( sd , & m , 0 ) ;
if ( err < 0 ) {
if ( errno = = EACCES & & try_broadcast ) {
try_broadcast = 0 ;
if ( ! set_broadcast ( sd ) )
goto again ;
errno = EACCES ;
}
log_err_errno ( " sendmsg failed " ) ;
return 1 ;
}
return 0 ;
}
static int send_msg ( int sd , void * addr , socklen_t alen , struct sock_args * args )
{
if ( args - > type = = SOCK_STREAM ) {
if ( write ( sd , msg , msglen ) < 0 ) {
log_err_errno ( " write failed sending msg to peer " ) ;
return 1 ;
}
} else if ( args - > ifindex & & args - > use_cmsg ) {
if ( send_msg_cmsg ( sd , addr , alen , args - > ifindex , args - > version ) )
return 1 ;
} else {
if ( send_msg_no_cmsg ( sd , addr , alen ) )
return 1 ;
}
log_msg ( " Sent message: \n " ) ;
log_msg ( " %.24s%s \n " , msg , msglen > 24 ? " ... " : " " ) ;
return 0 ;
}
static int socket_read_dgram ( int sd , struct sock_args * args )
{
unsigned char addr [ sizeof ( struct sockaddr_in6 ) ] ;
struct sockaddr * sa = ( struct sockaddr * ) addr ;
socklen_t alen = sizeof ( addr ) ;
struct iovec iov [ 2 ] ;
struct msghdr m = {
. msg_name = ( caddr_t ) addr ,
. msg_namelen = alen ,
. msg_iov = iov ,
. msg_iovlen = 1 ,
} ;
unsigned char cmsgbuf [ 256 ] ;
struct cmsghdr * cm = ( struct cmsghdr * ) cmsgbuf ;
char buf [ 16 * 1024 ] ;
int ifindex ;
int len ;
iov [ 0 ] . iov_base = ( caddr_t ) buf ;
iov [ 0 ] . iov_len = sizeof ( buf ) ;
memset ( cmsgbuf , 0 , sizeof ( cmsgbuf ) ) ;
m . msg_control = ( caddr_t ) cm ;
m . msg_controllen = sizeof ( cmsgbuf ) ;
len = recvmsg ( sd , & m , 0 ) ;
if ( len = = 0 ) {
log_msg ( " peer closed connection. \n " ) ;
return 0 ;
} else if ( len < 0 ) {
log_msg ( " failed to read message: %d: %s \n " ,
errno , strerror ( errno ) ) ;
return - 1 ;
}
buf [ len ] = ' \0 ' ;
log_address ( " Message from: " , sa ) ;
log_msg ( " %.24s%s \n " , buf , len > 24 ? " ... " : " " ) ;
ifindex = get_index_from_cmsg ( & m ) ;
if ( args - > expected_ifindex ) {
if ( args - > expected_ifindex ! = ifindex ) {
log_error ( " Device index mismatch: expected %d have %d \n " ,
args - > expected_ifindex , ifindex ) ;
return - 1 ;
}
log_msg ( " Device index matches: expected %d have %d \n " ,
args - > expected_ifindex , ifindex ) ;
}
if ( ! interactive & & server_mode ) {
if ( sa - > sa_family = = AF_INET6 ) {
struct sockaddr_in6 * s6 = ( struct sockaddr_in6 * ) sa ;
struct in6_addr * in6 = & s6 - > sin6_addr ;
if ( IN6_IS_ADDR_V4MAPPED ( in6 ) ) {
const uint32_t * pa = ( uint32_t * ) & in6 - > s6_addr ;
struct in_addr in4 ;
struct sockaddr_in * sin ;
sin = ( struct sockaddr_in * ) addr ;
pa + = 3 ;
in4 . s_addr = * pa ;
sin - > sin_addr = in4 ;
sin - > sin_family = AF_INET ;
if ( send_msg_cmsg ( sd , addr , alen ,
ifindex , AF_INET ) < 0 )
goto out_err ;
}
}
again :
iov [ 0 ] . iov_len = len ;
if ( args - > version = = AF_INET6 ) {
struct sockaddr_in6 * s6 = ( struct sockaddr_in6 * ) sa ;
if ( args - > dev ) {
/* avoid PKTINFO conflicts with bindtodev */
if ( sendto ( sd , buf , len , 0 ,
( void * ) addr , alen ) < 0 )
goto out_err ;
} else {
/* kernel is allowing scope_id to be set to VRF
* index for LLA . for sends to global address
* reset scope id
*/
s6 - > sin6_scope_id = ifindex ;
if ( sendmsg ( sd , & m , 0 ) < 0 )
goto out_err ;
}
} else {
int err ;
err = sendmsg ( sd , & m , 0 ) ;
if ( err < 0 ) {
if ( errno = = EACCES & & try_broadcast ) {
try_broadcast = 0 ;
if ( ! set_broadcast ( sd ) )
goto again ;
errno = EACCES ;
}
goto out_err ;
}
}
log_msg ( " Sent message: \n " ) ;
log_msg ( " %.24s%s \n " , buf , len > 24 ? " ... " : " " ) ;
}
return 1 ;
out_err :
log_err_errno ( " failed to send msg to peer " ) ;
return - 1 ;
}
static int socket_read_stream ( int sd )
{
char buf [ 1024 ] ;
int len ;
len = read ( sd , buf , sizeof ( buf ) - 1 ) ;
if ( len = = 0 ) {
log_msg ( " client closed connection. \n " ) ;
return 0 ;
} else if ( len < 0 ) {
log_msg ( " failed to read message \n " ) ;
return - 1 ;
}
buf [ len ] = ' \0 ' ;
log_msg ( " Incoming message: \n " ) ;
log_msg ( " %.24s%s \n " , buf , len > 24 ? " ... " : " " ) ;
if ( ! interactive & & server_mode ) {
if ( write ( sd , buf , len ) < 0 ) {
log_err_errno ( " failed to send buf " ) ;
return - 1 ;
}
log_msg ( " Sent message: \n " ) ;
log_msg ( " %.24s%s \n " , buf , len > 24 ? " ... " : " " ) ;
}
return 1 ;
}
static int socket_read ( int sd , struct sock_args * args )
{
if ( args - > type = = SOCK_STREAM )
return socket_read_stream ( sd ) ;
return socket_read_dgram ( sd , args ) ;
}
static int stdin_to_socket ( int sd , int type , void * addr , socklen_t alen )
{
char buf [ 1024 ] ;
int len ;
if ( fgets ( buf , sizeof ( buf ) , stdin ) = = NULL )
return 0 ;
len = strlen ( buf ) ;
if ( type = = SOCK_STREAM ) {
if ( write ( sd , buf , len ) < 0 ) {
log_err_errno ( " failed to send buf " ) ;
return - 1 ;
}
} else {
int err ;
again :
err = sendto ( sd , buf , len , 0 , addr , alen ) ;
if ( err < 0 ) {
if ( errno = = EACCES & & try_broadcast ) {
try_broadcast = 0 ;
if ( ! set_broadcast ( sd ) )
goto again ;
errno = EACCES ;
}
log_err_errno ( " failed to send msg to peer " ) ;
return - 1 ;
}
}
log_msg ( " Sent message: \n " ) ;
log_msg ( " %.24s%s \n " , buf , len > 24 ? " ... " : " " ) ;
return 1 ;
}
static void set_recv_attr ( int sd , int version )
{
if ( version = = AF_INET6 ) {
set_recvpktinfo_v6 ( sd ) ;
set_recverr_v6 ( sd ) ;
} else {
set_pktinfo_v4 ( sd ) ;
set_recverr_v4 ( sd ) ;
}
}
static int msg_loop ( int client , int sd , void * addr , socklen_t alen ,
struct sock_args * args )
{
struct timeval timeout = { . tv_sec = prog_timeout } , * ptval = NULL ;
fd_set rfds ;
int nfds ;
int rc ;
if ( args - > type ! = SOCK_STREAM )
set_recv_attr ( sd , args - > version ) ;
if ( msg ) {
msglen = strlen ( msg ) ;
/* client sends first message */
if ( client ) {
if ( send_msg ( sd , addr , alen , args ) )
return 1 ;
}
if ( ! interactive ) {
ptval = & timeout ;
if ( ! prog_timeout )
timeout . tv_sec = 5 ;
}
}
nfds = interactive ? MAX ( fileno ( stdin ) , sd ) + 1 : sd + 1 ;
while ( 1 ) {
FD_ZERO ( & rfds ) ;
FD_SET ( sd , & rfds ) ;
if ( interactive )
FD_SET ( fileno ( stdin ) , & rfds ) ;
rc = select ( nfds , & rfds , NULL , NULL , ptval ) ;
if ( rc < 0 ) {
if ( errno = = EINTR )
continue ;
rc = 1 ;
log_err_errno ( " select failed " ) ;
break ;
} else if ( rc = = 0 ) {
log_error ( " Timed out waiting for response \n " ) ;
rc = 2 ;
break ;
}
if ( FD_ISSET ( sd , & rfds ) ) {
rc = socket_read ( sd , args ) ;
if ( rc < 0 ) {
rc = 1 ;
break ;
}
if ( rc = = 0 )
break ;
}
rc = 0 ;
if ( FD_ISSET ( fileno ( stdin ) , & rfds ) ) {
if ( stdin_to_socket ( sd , args - > type , addr , alen ) < = 0 )
break ;
}
if ( interactive )
continue ;
if ( iter ! = - 1 ) {
- - iter ;
if ( iter = = 0 )
break ;
}
log_msg ( " Going into quiet mode \n " ) ;
quiet = 1 ;
if ( client ) {
if ( send_msg ( sd , addr , alen , args ) ) {
rc = 1 ;
break ;
}
}
}
return rc ;
}
static int msock_init ( struct sock_args * args , int server )
{
uint32_t if_addr = htonl ( INADDR_ANY ) ;
struct sockaddr_in laddr = {
. sin_family = AF_INET ,
. sin_port = htons ( args - > port ) ,
} ;
int one = 1 ;
int sd ;
if ( ! server & & args - > has_local_ip )
if_addr = args - > local_addr . in . s_addr ;
sd = socket ( PF_INET , SOCK_DGRAM , 0 ) ;
if ( sd < 0 ) {
log_err_errno ( " socket " ) ;
return - 1 ;
}
if ( setsockopt ( sd , SOL_SOCKET , SO_REUSEADDR ,
( char * ) & one , sizeof ( one ) ) < 0 ) {
log_err_errno ( " Setting SO_REUSEADDR error " ) ;
goto out_err ;
}
if ( setsockopt ( sd , SOL_SOCKET , SO_BROADCAST ,
( char * ) & one , sizeof ( one ) ) < 0 )
log_err_errno ( " Setting SO_BROADCAST error " ) ;
if ( args - > dev & & bind_to_device ( sd , args - > dev ) ! = 0 )
goto out_err ;
else if ( args - > use_setsockopt & &
set_multicast_if ( sd , args - > ifindex ) )
goto out_err ;
laddr . sin_addr . s_addr = if_addr ;
if ( bind ( sd , ( struct sockaddr * ) & laddr , sizeof ( laddr ) ) < 0 ) {
log_err_errno ( " bind failed " ) ;
goto out_err ;
}
if ( server & &
set_membership ( sd , args - > grp . s_addr ,
2019-08-14 20:11:51 +03:00
args - > local_addr . in . s_addr , args - > ifindex ) )
2019-08-01 21:56:34 +03:00
goto out_err ;
return sd ;
out_err :
close ( sd ) ;
return - 1 ;
}
static int msock_server ( struct sock_args * args )
{
return msock_init ( args , 1 ) ;
}
static int msock_client ( struct sock_args * args )
{
return msock_init ( args , 0 ) ;
}
static int bind_socket ( int sd , struct sock_args * args )
{
struct sockaddr_in serv_addr = {
. sin_family = AF_INET ,
} ;
struct sockaddr_in6 serv6_addr = {
. sin6_family = AF_INET6 ,
} ;
void * addr ;
socklen_t alen ;
if ( ! args - > has_local_ip & & args - > type = = SOCK_RAW )
return 0 ;
switch ( args - > version ) {
case AF_INET :
serv_addr . sin_port = htons ( args - > port ) ;
serv_addr . sin_addr = args - > local_addr . in ;
addr = & serv_addr ;
alen = sizeof ( serv_addr ) ;
break ;
case AF_INET6 :
serv6_addr . sin6_port = htons ( args - > port ) ;
serv6_addr . sin6_addr = args - > local_addr . in6 ;
addr = & serv6_addr ;
alen = sizeof ( serv6_addr ) ;
break ;
default :
log_error ( " Invalid address family \n " ) ;
return - 1 ;
}
if ( bind ( sd , addr , alen ) < 0 ) {
log_err_errno ( " error binding socket " ) ;
return - 1 ;
}
return 0 ;
}
2021-07-20 23:35:29 +03:00
static int config_xfrm_policy ( int sd , struct sock_args * args )
{
struct xfrm_userpolicy_info policy = { } ;
int type = UDP_ENCAP_ESPINUDP ;
int xfrm_af = IP_XFRM_POLICY ;
int level = SOL_IP ;
if ( args - > type ! = SOCK_DGRAM ) {
log_error ( " Invalid socket type. Only DGRAM could be used for XFRM \n " ) ;
return 1 ;
}
policy . action = XFRM_POLICY_ALLOW ;
policy . sel . family = args - > version ;
if ( args - > version = = AF_INET6 ) {
xfrm_af = IPV6_XFRM_POLICY ;
level = SOL_IPV6 ;
}
policy . dir = XFRM_POLICY_OUT ;
if ( setsockopt ( sd , level , xfrm_af , & policy , sizeof ( policy ) ) < 0 )
return 1 ;
policy . dir = XFRM_POLICY_IN ;
if ( setsockopt ( sd , level , xfrm_af , & policy , sizeof ( policy ) ) < 0 )
return 1 ;
if ( setsockopt ( sd , IPPROTO_UDP , UDP_ENCAP , & type , sizeof ( type ) ) < 0 ) {
log_err_errno ( " Failed to set xfrm encap " ) ;
return 1 ;
}
return 0 ;
}
2019-08-01 21:56:34 +03:00
static int lsock_init ( struct sock_args * args )
{
long flags ;
int sd ;
sd = socket ( args - > version , args - > type , args - > protocol ) ;
if ( sd < 0 ) {
log_err_errno ( " Error opening socket " ) ;
return - 1 ;
}
if ( set_reuseaddr ( sd ) ! = 0 )
goto err ;
if ( set_reuseport ( sd ) ! = 0 )
goto err ;
if ( args - > dev & & bind_to_device ( sd , args - > dev ) ! = 0 )
goto err ;
else if ( args - > use_setsockopt & &
set_unicast_if ( sd , args - > ifindex , args - > version ) )
goto err ;
if ( bind_socket ( sd , args ) )
goto err ;
if ( args - > bind_test_only )
goto out ;
if ( args - > type = = SOCK_STREAM & & listen ( sd , 1 ) < 0 ) {
log_err_errno ( " listen failed " ) ;
goto err ;
}
flags = fcntl ( sd , F_GETFL ) ;
if ( ( flags < 0 ) | | ( fcntl ( sd , F_SETFL , flags | O_NONBLOCK ) < 0 ) ) {
log_err_errno ( " Failed to set non-blocking option " ) ;
goto err ;
}
if ( fcntl ( sd , F_SETFD , FD_CLOEXEC ) < 0 )
log_err_errno ( " Failed to set close-on-exec flag " ) ;
2021-07-20 23:35:29 +03:00
if ( args - > use_xfrm & & config_xfrm_policy ( sd , args ) ) {
log_err_errno ( " Failed to set xfrm policy " ) ;
goto err ;
}
2019-08-01 21:56:34 +03:00
out :
return sd ;
err :
close ( sd ) ;
return - 1 ;
}
2021-01-14 06:09:41 +03:00
static void ipc_write ( int fd , int message )
2019-08-01 21:56:34 +03:00
{
2021-01-14 06:09:41 +03:00
/* Not in both_mode, so there's no process to signal */
if ( fd < 0 )
return ;
if ( write ( fd , & message , sizeof ( message ) ) < 0 )
log_err_errno ( " Failed to send client status " ) ;
}
static int do_server ( struct sock_args * args , int ipc_fd )
{
/* ipc_fd = -1 if no parent process to signal */
2019-08-01 21:56:34 +03:00
struct timeval timeout = { . tv_sec = prog_timeout } , * ptval = NULL ;
unsigned char addr [ sizeof ( struct sockaddr_in6 ) ] = { } ;
socklen_t alen = sizeof ( addr ) ;
int lsd , csd = - 1 ;
fd_set rfds ;
int rc ;
2021-01-14 06:09:40 +03:00
if ( args - > serverns ) {
if ( switch_ns ( args - > serverns ) ) {
log_error ( " Could not set server netns to %s \n " ,
args - > serverns ) ;
2021-01-14 06:09:41 +03:00
goto err_exit ;
2021-01-14 06:09:40 +03:00
}
log_msg ( " Switched server netns \n " ) ;
}
2021-01-14 06:09:47 +03:00
args - > dev = args - > server_dev ;
args - > expected_dev = args - > expected_server_dev ;
2021-01-14 06:09:39 +03:00
if ( resolve_devices ( args ) | | validate_addresses ( args ) )
2021-01-14 06:09:41 +03:00
goto err_exit ;
2021-01-14 06:09:37 +03:00
2019-08-01 21:56:34 +03:00
if ( prog_timeout )
ptval = & timeout ;
if ( args - > has_grp )
lsd = msock_server ( args ) ;
else
lsd = lsock_init ( args ) ;
if ( lsd < 0 )
2021-01-14 06:09:41 +03:00
goto err_exit ;
2019-08-01 21:56:34 +03:00
if ( args - > bind_test_only ) {
close ( lsd ) ;
2021-01-14 06:09:41 +03:00
ipc_write ( ipc_fd , 1 ) ;
2019-08-01 21:56:34 +03:00
return 0 ;
}
if ( args - > type ! = SOCK_STREAM ) {
2021-01-14 06:09:41 +03:00
ipc_write ( ipc_fd , 1 ) ;
2019-08-01 21:56:34 +03:00
rc = msg_loop ( 0 , lsd , ( void * ) addr , alen , args ) ;
close ( lsd ) ;
return rc ;
}
if ( args - > password & & tcp_md5_remote ( lsd , args ) ) {
close ( lsd ) ;
2021-01-14 06:09:41 +03:00
goto err_exit ;
2019-08-01 21:56:34 +03:00
}
2021-01-14 06:09:41 +03:00
ipc_write ( ipc_fd , 1 ) ;
2019-08-01 21:56:34 +03:00
while ( 1 ) {
log_msg ( " waiting for client connection. \n " ) ;
FD_ZERO ( & rfds ) ;
FD_SET ( lsd , & rfds ) ;
rc = select ( lsd + 1 , & rfds , NULL , NULL , ptval ) ;
if ( rc = = 0 ) {
rc = 2 ;
break ;
}
if ( rc < 0 ) {
if ( errno = = EINTR )
continue ;
log_err_errno ( " select failed " ) ;
break ;
}
if ( FD_ISSET ( lsd , & rfds ) ) {
csd = accept ( lsd , ( void * ) addr , & alen ) ;
if ( csd < 0 ) {
log_err_errno ( " accept failed " ) ;
break ;
}
rc = show_sockstat ( csd , args ) ;
if ( rc )
break ;
rc = check_device ( csd , args ) ;
if ( rc )
break ;
}
rc = msg_loop ( 0 , csd , ( void * ) addr , alen , args ) ;
close ( csd ) ;
if ( ! interactive )
break ;
}
close ( lsd ) ;
return rc ;
2021-01-14 06:09:41 +03:00
err_exit :
ipc_write ( ipc_fd , 0 ) ;
return 1 ;
2019-08-01 21:56:34 +03:00
}
static int wait_for_connect ( int sd )
{
struct timeval _tv = { . tv_sec = prog_timeout } , * tv = NULL ;
fd_set wfd ;
int val = 0 , sz = sizeof ( val ) ;
int rc ;
FD_ZERO ( & wfd ) ;
FD_SET ( sd , & wfd ) ;
if ( prog_timeout )
tv = & _tv ;
rc = select ( FD_SETSIZE , NULL , & wfd , NULL , tv ) ;
if ( rc = = 0 ) {
log_error ( " connect timed out \n " ) ;
return - 2 ;
} else if ( rc < 0 ) {
log_err_errno ( " select failed " ) ;
return - 3 ;
}
if ( getsockopt ( sd , SOL_SOCKET , SO_ERROR , & val , ( socklen_t * ) & sz ) < 0 ) {
log_err_errno ( " getsockopt(SO_ERROR) failed " ) ;
return - 4 ;
}
if ( val ! = 0 ) {
log_error ( " connect failed: %d: %s \n " , val , strerror ( val ) ) ;
return - 1 ;
}
return 0 ;
}
static int connectsock ( void * addr , socklen_t alen , struct sock_args * args )
{
int sd , rc = - 1 ;
long flags ;
sd = socket ( args - > version , args - > type , args - > protocol ) ;
if ( sd < 0 ) {
log_err_errno ( " Failed to create socket " ) ;
return - 1 ;
}
flags = fcntl ( sd , F_GETFL ) ;
if ( ( flags < 0 ) | | ( fcntl ( sd , F_SETFL , flags | O_NONBLOCK ) < 0 ) ) {
log_err_errno ( " Failed to set non-blocking option " ) ;
goto err ;
}
if ( set_reuseport ( sd ) ! = 0 )
goto err ;
if ( args - > dev & & bind_to_device ( sd , args - > dev ) ! = 0 )
goto err ;
else if ( args - > use_setsockopt & &
set_unicast_if ( sd , args - > ifindex , args - > version ) )
goto err ;
if ( args - > has_local_ip & & bind_socket ( sd , args ) )
goto err ;
if ( args - > type ! = SOCK_STREAM )
goto out ;
2019-12-31 01:14:31 +03:00
if ( args - > password & & tcp_md5sig ( sd , addr , alen , args ) )
2019-08-01 21:56:34 +03:00
goto err ;
if ( args - > bind_test_only )
goto out ;
if ( connect ( sd , addr , alen ) < 0 ) {
if ( errno ! = EINPROGRESS ) {
log_err_errno ( " Failed to connect to remote host " ) ;
rc = - 1 ;
goto err ;
}
rc = wait_for_connect ( sd ) ;
if ( rc < 0 )
goto err ;
}
out :
return sd ;
err :
close ( sd ) ;
return rc ;
}
static int do_client ( struct sock_args * args )
{
struct sockaddr_in sin = {
. sin_family = AF_INET ,
} ;
struct sockaddr_in6 sin6 = {
. sin6_family = AF_INET6 ,
} ;
void * addr ;
int alen ;
int rc = 0 ;
int sd ;
if ( ! args - > has_remote_ip & & ! args - > has_grp ) {
fprintf ( stderr , " remote IP or multicast group not given \n " ) ;
return 1 ;
}
2021-01-14 06:09:40 +03:00
if ( args - > clientns ) {
if ( switch_ns ( args - > clientns ) ) {
log_error ( " Could not set client netns to %s \n " ,
args - > clientns ) ;
return 1 ;
}
log_msg ( " Switched client netns \n " ) ;
}
2021-01-14 06:09:49 +03:00
args - > local_addr_str = args - > client_local_addr_str ;
2021-01-14 06:09:39 +03:00
if ( resolve_devices ( args ) | | validate_addresses ( args ) )
2021-01-14 06:09:37 +03:00
return 1 ;
if ( ( args - > use_setsockopt | | args - > use_cmsg ) & & ! args - > ifindex ) {
fprintf ( stderr , " Device binding not specified \n " ) ;
return 1 ;
}
if ( args - > use_setsockopt | | args - > use_cmsg )
args - > dev = NULL ;
2019-08-01 21:56:34 +03:00
switch ( args - > version ) {
case AF_INET :
sin . sin_port = htons ( args - > port ) ;
if ( args - > has_grp )
sin . sin_addr = args - > grp ;
else
sin . sin_addr = args - > remote_addr . in ;
addr = & sin ;
alen = sizeof ( sin ) ;
break ;
case AF_INET6 :
sin6 . sin6_port = htons ( args - > port ) ;
sin6 . sin6_addr = args - > remote_addr . in6 ;
sin6 . sin6_scope_id = args - > scope_id ;
addr = & sin6 ;
alen = sizeof ( sin6 ) ;
break ;
}
2021-01-14 06:09:46 +03:00
args - > password = args - > client_pw ;
2019-08-01 21:56:34 +03:00
if ( args - > has_grp )
sd = msock_client ( args ) ;
else
sd = connectsock ( addr , alen , args ) ;
if ( sd < 0 )
return - sd ;
if ( args - > bind_test_only )
goto out ;
if ( args - > type = = SOCK_STREAM ) {
rc = show_sockstat ( sd , args ) ;
if ( rc ! = 0 )
goto out ;
}
rc = msg_loop ( 1 , sd , addr , alen , args ) ;
out :
close ( sd ) ;
return rc ;
}
static char * random_msg ( int len )
{
int i , n = 0 , olen = len + 1 ;
char * m ;
if ( len < = 0 )
return NULL ;
m = malloc ( olen ) ;
if ( ! m )
return NULL ;
while ( len > 26 ) {
i = snprintf ( m + n , olen - n , " %.26s " ,
" abcdefghijklmnopqrstuvwxyz " ) ;
n + = i ;
len - = i ;
}
i = snprintf ( m + n , olen - n , " %.*s " , len ,
" abcdefghijklmnopqrstuvwxyz " ) ;
return m ;
}
2021-01-14 06:09:41 +03:00
static int ipc_child ( int fd , struct sock_args * args )
{
2021-01-14 06:09:42 +03:00
char * outbuf , * errbuf ;
int rc = 1 ;
outbuf = malloc ( 4096 ) ;
errbuf = malloc ( 4096 ) ;
if ( ! outbuf | | ! errbuf ) {
fprintf ( stderr , " server: Failed to allocate buffers for stdout and stderr \n " ) ;
goto out ;
}
setbuffer ( stdout , outbuf , 4096 ) ;
setbuffer ( stderr , errbuf , 4096 ) ;
2021-01-14 06:09:41 +03:00
server_mode = 1 ; /* to tell log_msg in case we are in both_mode */
2021-01-14 06:09:44 +03:00
/* when running in both mode, address validation applies
* solely to client side
*/
args - > has_expected_laddr = 0 ;
args - > has_expected_raddr = 0 ;
2021-01-14 06:09:42 +03:00
rc = do_server ( args , fd ) ;
out :
free ( outbuf ) ;
free ( errbuf ) ;
return rc ;
2021-01-14 06:09:41 +03:00
}
static int ipc_parent ( int cpid , int fd , struct sock_args * args )
{
int client_status ;
int status ;
int buf ;
/* do the client-side function here in the parent process,
* waiting to be told when to continue
*/
if ( read ( fd , & buf , sizeof ( buf ) ) < = 0 ) {
log_err_errno ( " Failed to read IPC status from status " ) ;
return 1 ;
}
if ( ! buf ) {
log_error ( " Server failed; can not continue \n " ) ;
return 1 ;
}
log_msg ( " Server is ready \n " ) ;
client_status = do_client ( args ) ;
log_msg ( " parent is done! \n " ) ;
if ( kill ( cpid , 0 ) = = 0 )
kill ( cpid , SIGKILL ) ;
wait ( & status ) ;
return client_status ;
}
2021-07-20 23:35:29 +03:00
# define GETOPT_STR "sr:l:c:p:t:g:P:DRn:M:X:m:d:I:BN:O:SCi6xL:0:1:2:3:Fbq"
2019-08-01 21:56:34 +03:00
static void print_usage ( char * prog )
{
printf (
" usage: %s OPTS \n "
" Required: \n "
" -r addr remote address to connect to (client mode only) \n "
" -p port port to connect to (client mode)/listen on (server mode) \n "
" (default: %d) \n "
" -s server mode (default: client mode) \n "
" -t timeout seconds (default: none) \n "
" \n "
" Optional: \n "
2021-01-14 06:09:41 +03:00
" -B do both client and server via fork and IPC \n "
2021-01-14 06:09:40 +03:00
" -N ns set client to network namespace ns (requires root) \n "
" -O ns set server to network namespace ns (requires root) \n "
2019-08-01 21:56:34 +03:00
" -F Restart server loop \n "
" -6 IPv6 (default is IPv4) \n "
" -P proto protocol for socket: icmp, ospf (default: none) \n "
" -D|R datagram (D) / raw (R) socket (default stream) \n "
2021-01-14 06:09:49 +03:00
" -l addr local address to bind to in server mode \n "
" -c addr local address to bind to in client mode \n "
2021-07-20 23:35:29 +03:00
" -x configure XFRM policy on socket \n "
2019-08-01 21:56:34 +03:00
" \n "
" -d dev bind socket to given device name \n "
2021-01-14 06:09:47 +03:00
" -I dev bind socket to given device name - server mode \n "
2019-08-01 21:56:34 +03:00
" -S use setsockopt (IP_UNICAST_IF or IP_MULTICAST_IF) \n "
" to set device binding \n "
" -C use cmsg and IP_PKTINFO to specify device binding \n "
" \n "
" -L len send random message of given length \n "
" -n num number of times to send message \n "
" \n "
" -M password use MD5 sum protection \n "
2021-01-14 06:09:46 +03:00
" -X password MD5 password for client mode \n "
2019-12-31 01:14:31 +03:00
" -m prefix/len prefix and length to use for MD5 key \n "
2019-08-01 21:56:34 +03:00
" -g grp multicast group (e.g., 239.1.1.1) \n "
" -i interactive mode (default is echo and terminate) \n "
" \n "
" -0 addr Expected local address \n "
" -1 addr Expected remote address \n "
" -2 dev Expected device name (or index) to receive packet \n "
2021-01-14 06:09:47 +03:00
" -3 dev Expected device name (or index) to receive packets - server mode \n "
2019-08-01 21:56:34 +03:00
" \n "
" -b Bind test only. \n "
" -q Be quiet. Run test without printing anything. \n "
, prog , DEFAULT_PORT ) ;
}
int main ( int argc , char * argv [ ] )
{
struct sock_args args = {
. version = AF_INET ,
. type = SOCK_STREAM ,
. port = DEFAULT_PORT ,
} ;
struct protoent * pe ;
2021-01-14 06:09:41 +03:00
int both_mode = 0 ;
2019-08-01 21:56:34 +03:00
unsigned int tmp ;
int forever = 0 ;
2021-01-14 06:09:41 +03:00
int fd [ 2 ] ;
int cpid ;
2019-08-01 21:56:34 +03:00
/* process inputs */
extern char * optarg ;
int rc = 0 ;
/*
* process input args
*/
while ( ( rc = getopt ( argc , argv , GETOPT_STR ) ) ! = - 1 ) {
switch ( rc ) {
2021-01-14 06:09:41 +03:00
case ' B ' :
both_mode = 1 ;
break ;
2019-08-01 21:56:34 +03:00
case ' s ' :
server_mode = 1 ;
break ;
case ' F ' :
forever = 1 ;
break ;
case ' l ' :
args . has_local_ip = 1 ;
2021-01-14 06:09:39 +03:00
args . local_addr_str = optarg ;
2019-08-01 21:56:34 +03:00
break ;
case ' r ' :
args . has_remote_ip = 1 ;
2021-01-14 06:09:39 +03:00
args . remote_addr_str = optarg ;
2019-08-01 21:56:34 +03:00
break ;
2021-01-14 06:09:49 +03:00
case ' c ' :
args . has_local_ip = 1 ;
args . client_local_addr_str = optarg ;
break ;
2019-08-01 21:56:34 +03:00
case ' p ' :
if ( str_to_uint ( optarg , 1 , 65535 , & tmp ) ! = 0 ) {
fprintf ( stderr , " Invalid port \n " ) ;
return 1 ;
}
args . port = ( unsigned short ) tmp ;
break ;
case ' t ' :
if ( str_to_uint ( optarg , 0 , INT_MAX ,
& prog_timeout ) ! = 0 ) {
fprintf ( stderr , " Invalid timeout \n " ) ;
return 1 ;
}
break ;
case ' D ' :
args . type = SOCK_DGRAM ;
break ;
case ' R ' :
args . type = SOCK_RAW ;
args . port = 0 ;
2020-09-17 18:13:33 +03:00
if ( ! args . protocol )
args . protocol = IPPROTO_RAW ;
2019-08-01 21:56:34 +03:00
break ;
case ' P ' :
pe = getprotobyname ( optarg ) ;
if ( pe ) {
args . protocol = pe - > p_proto ;
} else {
if ( str_to_uint ( optarg , 0 , 0xffff , & tmp ) ! = 0 ) {
2019-08-05 13:52:11 +03:00
fprintf ( stderr , " Invalid protocol \n " ) ;
2019-08-01 21:56:34 +03:00
return 1 ;
}
args . protocol = tmp ;
}
break ;
case ' n ' :
iter = atoi ( optarg ) ;
break ;
2021-01-14 06:09:40 +03:00
case ' N ' :
args . clientns = optarg ;
break ;
case ' O ' :
args . serverns = optarg ;
break ;
2019-08-01 21:56:34 +03:00
case ' L ' :
msg = random_msg ( atoi ( optarg ) ) ;
break ;
case ' M ' :
args . password = optarg ;
break ;
2021-01-14 06:09:46 +03:00
case ' X ' :
args . client_pw = optarg ;
break ;
2019-12-31 01:14:31 +03:00
case ' m ' :
2021-01-14 06:09:39 +03:00
args . md5_prefix_str = optarg ;
2019-12-31 01:14:31 +03:00
break ;
2019-08-01 21:56:34 +03:00
case ' S ' :
args . use_setsockopt = 1 ;
break ;
case ' C ' :
args . use_cmsg = 1 ;
break ;
case ' d ' :
args . dev = optarg ;
break ;
2021-01-14 06:09:47 +03:00
case ' I ' :
args . server_dev = optarg ;
break ;
2019-08-01 21:56:34 +03:00
case ' i ' :
interactive = 1 ;
break ;
case ' g ' :
args . has_grp = 1 ;
if ( convert_addr ( & args , optarg , ADDR_TYPE_MCAST ) < 0 )
return 1 ;
args . type = SOCK_DGRAM ;
break ;
case ' 6 ' :
args . version = AF_INET6 ;
break ;
case ' b ' :
args . bind_test_only = 1 ;
break ;
case ' 0 ' :
args . has_expected_laddr = 1 ;
2021-01-14 06:09:39 +03:00
args . expected_laddr_str = optarg ;
2019-08-01 21:56:34 +03:00
break ;
case ' 1 ' :
args . has_expected_raddr = 1 ;
2021-01-14 06:09:39 +03:00
args . expected_raddr_str = optarg ;
2019-08-01 21:56:34 +03:00
break ;
case ' 2 ' :
2021-01-14 06:09:37 +03:00
args . expected_dev = optarg ;
2019-08-01 21:56:34 +03:00
break ;
2021-01-14 06:09:47 +03:00
case ' 3 ' :
args . expected_server_dev = optarg ;
break ;
2019-08-01 21:56:34 +03:00
case ' q ' :
quiet = 1 ;
break ;
2021-07-20 23:35:29 +03:00
case ' x ' :
args . use_xfrm = 1 ;
break ;
2019-08-01 21:56:34 +03:00
default :
print_usage ( argv [ 0 ] ) ;
return 1 ;
}
}
if ( args . password & &
2021-01-14 06:09:39 +03:00
( ( ! args . has_remote_ip & & ! args . md5_prefix_str ) | |
args . type ! = SOCK_STREAM ) ) {
2019-08-01 21:56:34 +03:00
log_error ( " MD5 passwords apply to TCP only and require a remote ip for the password \n " ) ;
return 1 ;
}
2021-01-14 06:09:39 +03:00
if ( args . md5_prefix_str & & ! args . password ) {
2019-12-31 01:14:31 +03:00
log_error ( " Prefix range for MD5 protection specified without a password \n " ) ;
return 1 ;
}
2019-08-01 21:56:34 +03:00
if ( iter = = 0 ) {
fprintf ( stderr , " Invalid number of messages to send \n " ) ;
return 1 ;
}
if ( args . type = = SOCK_STREAM & & ! args . protocol )
args . protocol = IPPROTO_TCP ;
if ( args . type = = SOCK_DGRAM & & ! args . protocol )
args . protocol = IPPROTO_UDP ;
if ( ( args . type = = SOCK_STREAM | | args . type = = SOCK_DGRAM ) & &
args . port = = 0 ) {
fprintf ( stderr , " Invalid port number \n " ) ;
return 1 ;
}
2021-01-14 06:09:41 +03:00
if ( ( both_mode | | ! server_mode ) & & ! args . has_grp & &
2019-08-01 21:56:34 +03:00
! args . has_remote_ip & & ! args . has_local_ip ) {
fprintf ( stderr ,
" Local (server mode) or remote IP (client IP) required \n " ) ;
return 1 ;
}
if ( interactive ) {
prog_timeout = 0 ;
msg = NULL ;
}
2021-01-14 06:09:41 +03:00
if ( both_mode ) {
if ( pipe ( fd ) < 0 ) {
perror ( " pipe " ) ;
exit ( 1 ) ;
}
cpid = fork ( ) ;
if ( cpid < 0 ) {
perror ( " fork " ) ;
exit ( 1 ) ;
}
if ( cpid )
return ipc_parent ( cpid , fd [ 0 ] , & args ) ;
return ipc_child ( fd [ 1 ] , & args ) ;
}
2019-08-01 21:56:34 +03:00
if ( server_mode ) {
do {
2021-01-14 06:09:41 +03:00
rc = do_server ( & args , - 1 ) ;
2019-08-01 21:56:34 +03:00
} while ( forever ) ;
return rc ;
}
return do_client ( & args ) ;
}