2022-03-03 16:53:46 +01:00
/*
* Copyright ( C ) 2010 - 2022 Willy Tarreau < w @ 1 wt . eu >
*
* Permission is hereby granted , free of charge , to any person obtaining
* a copy of this software and associated documentation files ( the
* " Software " ) , to deal in the Software without restriction , including
* without limitation the rights to use , copy , modify , merge , publish ,
* distribute , sublicense , and / or sell copies of the Software , and to
* permit persons to whom the Software is furnished to do so , subject to
* the following conditions :
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND ,
* EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT . IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY ,
* WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING
* FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE .
*/
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <ctype.h>
# include <sys/time.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/tcp.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
# include <fcntl.h>
# include <errno.h>
2022-03-16 14:49:33 +01:00
# include <getopt.h>
2022-03-03 16:53:46 +01:00
# include <signal.h>
# include <stdarg.h>
# include <sys/stat.h>
# include <time.h>
# include <limits.h>
# include <poll.h>
# include <stdio.h>
# include <stdlib.h>
# include <stdarg.h>
# define MAXCONN 1
const int zero = 0 ;
const int one = 1 ;
struct conn {
struct sockaddr_storage cli_addr ;
int fd_bck ;
} ;
struct errmsg {
char * msg ;
int size ;
int len ;
} ;
struct sockaddr_storage frt_addr ; // listen address
struct sockaddr_storage srv_addr ; // server address
# define MAXPKTSIZE 16384
2022-03-03 17:36:53 +01:00
# define MAXREORDER 20
2022-03-03 16:53:46 +01:00
char trash [ MAXPKTSIZE ] ;
2022-03-03 17:36:53 +01:00
/* history buffer, to resend random packets */
struct {
char buf [ MAXPKTSIZE ] ;
size_t len ;
} history [ MAXREORDER ] ;
int history_idx = 0 ;
unsigned int rand_rate = 0 ;
2022-03-16 15:07:51 +01:00
unsigned int corr_rate = 0 ;
unsigned int corr_span = 1 ;
unsigned int corr_base = 0 ;
2022-03-03 17:36:53 +01:00
2022-03-03 16:53:46 +01:00
struct conn conns [ MAXCONN ] ; // sole connection for now
int fd_frt ;
int nbfd = 0 ;
int nbconn = MAXCONN ;
/* display the message and exit with the code */
__attribute__ ( ( noreturn ) ) void die ( int code , const char * format , . . . )
{
va_list args ;
va_start ( args , format ) ;
vfprintf ( stderr , format , args ) ;
va_end ( args ) ;
exit ( code ) ;
}
2022-03-03 17:36:53 +01:00
/* Xorshift RNG */
unsigned int prng_state = ~ 0U / 3 ; // half bits set, but any seed will fit
static inline unsigned int prng ( unsigned int range )
{
unsigned int x = prng_state ;
x ^ = x < < 13 ;
x ^ = x > > 17 ;
x ^ = x < < 5 ;
prng_state = x ;
return ( ( unsigned long long ) x * ( range - 1 ) + x ) > > 32 ;
}
2022-03-03 16:53:46 +01:00
/* converts str in the form [<ipv4>|<ipv6>|<hostname>]:port to struct sockaddr_storage.
* Returns < 0 with err set in case of error .
*/
int addr_to_ss ( char * str , struct sockaddr_storage * ss , struct errmsg * err )
{
char * port_str ;
int port ;
/* look for the addr/port delimiter, it's the last colon. */
if ( ( port_str = strrchr ( str , ' : ' ) ) = = NULL )
port_str = str ;
else
* port_str + + = 0 ;
port = atoi ( port_str ) ;
if ( port < = 0 | | port > 65535 ) {
err - > len = snprintf ( err - > msg , err - > size , " Missing/invalid port number: '%s' \n " , port_str ) ;
return - 1 ;
}
* port_str = 0 ; // present an empty address if none was set
memset ( ss , 0 , sizeof ( * ss ) ) ;
if ( strrchr ( str , ' : ' ) ! = NULL ) {
/* IPv6 address contains ':' */
ss - > ss_family = AF_INET6 ;
( ( struct sockaddr_in6 * ) ss ) - > sin6_port = htons ( port ) ;
if ( ! inet_pton ( ss - > ss_family , str , & ( ( struct sockaddr_in6 * ) ss ) - > sin6_addr ) ) {
err - > len = snprintf ( err - > msg , err - > size , " Invalid IPv6 server address: '%s' " , str ) ;
return - 1 ;
}
}
else {
ss - > ss_family = AF_INET ;
( ( struct sockaddr_in * ) ss ) - > sin_port = htons ( port ) ;
if ( * str = = ' * ' | | * str = = ' \0 ' ) { /* INADDR_ANY */
( ( struct sockaddr_in * ) ss ) - > sin_addr . s_addr = INADDR_ANY ;
return 0 ;
}
if ( ! inet_pton ( ss - > ss_family , str , & ( ( struct sockaddr_in * ) ss ) - > sin_addr ) ) {
struct hostent * he = gethostbyname ( str ) ;
if ( he = = NULL ) {
err - > len = snprintf ( err - > msg , err - > size , " Invalid IPv4 server name: '%s' " , str ) ;
return - 1 ;
}
( ( struct sockaddr_in * ) ss ) - > sin_addr = * ( struct in_addr * ) * ( he - > h_addr_list ) ;
}
}
return 0 ;
}
/* returns <0 with err in case of error or the front FD */
int create_udp_listener ( struct sockaddr_storage * addr , struct errmsg * err )
{
int fd ;
if ( ( fd = socket ( addr - > ss_family , SOCK_DGRAM , 0 ) ) = = - 1 ) {
err - > len = snprintf ( err - > msg , err - > size , " socket(): '%s' " , strerror ( errno ) ) ;
goto fail ;
}
if ( fcntl ( fd , F_SETFL , O_NONBLOCK ) = = - 1 ) {
err - > len = snprintf ( err - > msg , err - > size , " fcntl(O_NONBLOCK): '%s' " , strerror ( errno ) ) ;
goto fail ;
}
if ( setsockopt ( fd , SOL_SOCKET , SO_REUSEADDR , ( char * ) & one , sizeof ( one ) ) = = - 1 ) {
err - > len = snprintf ( err - > msg , err - > size , " setsockopt(SO_REUSEADDR): '%s' " , strerror ( errno ) ) ;
goto fail ;
}
# ifdef SO_REUSEPORT
if ( setsockopt ( fd , SOL_SOCKET , SO_REUSEPORT , ( char * ) & one , sizeof ( one ) ) = = - 1 ) {
err - > len = snprintf ( err - > msg , err - > size , " setsockopt(SO_REUSEPORT): '%s' " , strerror ( errno ) ) ;
goto fail ;
}
# endif
if ( bind ( fd , ( struct sockaddr * ) & frt_addr , addr - > ss_family = = AF_INET6 ?
sizeof ( struct sockaddr_in6 ) : sizeof ( struct sockaddr_in ) ) = = - 1 ) {
err - > len = snprintf ( err - > msg , err - > size , " bind(): '%s' " , strerror ( errno ) ) ;
goto fail ;
}
/* the socket is ready */
return fd ;
fail :
if ( fd > - 1 )
close ( fd ) ;
fd = - 1 ;
return fd ;
}
/* recompute pollfds using frt_fd and scanning nbconn connections.
* Returns the number of FDs in the set .
*/
int update_pfd ( struct pollfd * pfd , int frt_fd , struct conn * conns , int nbconn )
{
int nbfd = 0 ;
int i ;
pfd [ nbfd ] . fd = frt_fd ;
pfd [ nbfd ] . events = POLLIN ;
nbfd + + ;
for ( i = 0 ; i < nbconn ; i + + ) {
if ( conns [ i ] . fd_bck < 0 )
continue ;
pfd [ nbfd ] . fd = conns [ i ] . fd_bck ;
pfd [ nbfd ] . events = POLLIN ;
nbfd + + ;
}
return nbfd ;
}
/* searches a connection using fd <fd> as back connection, returns it if found
* otherwise NULL .
*/
struct conn * conn_bck_lookup ( struct conn * conns , int nbconn , int fd )
{
int i ;
for ( i = 0 ; i < nbconn ; i + + ) {
if ( conns [ i ] . fd_bck < 0 )
continue ;
if ( conns [ i ] . fd_bck = = fd )
return & conns [ i ] ;
}
return NULL ;
}
/* Try to establish a connection to <sa>. Return the fd or -1 in case of error */
int add_connection ( struct sockaddr_storage * ss )
{
int fd ;
fd = socket ( ss - > ss_family , SOCK_DGRAM , 0 ) ;
if ( fd < 0 )
goto fail ;
if ( fcntl ( fd , F_SETFL , O_NONBLOCK ) = = - 1 )
goto fail ;
if ( setsockopt ( fd , SOL_SOCKET , SO_REUSEADDR , & one , sizeof ( one ) ) = = - 1 )
goto fail ;
if ( connect ( fd , ( struct sockaddr * ) ss , ss - > ss_family = = AF_INET6 ?
sizeof ( struct sockaddr_in6 ) : sizeof ( struct sockaddr_in ) ) = = - 1 ) {
if ( errno ! = EINPROGRESS )
goto fail ;
}
return fd ;
fail :
if ( fd > - 1 )
close ( fd ) ;
return - 1 ;
}
/* Handle a read operation on an front FD. Will either reuse the existing
* connection if the source is found , or will allocate a new one , possibly
* replacing the oldest one . Returns < 0 on error or the number of bytes
* transmitted .
*/
int handle_frt ( int fd , struct pollfd * pfd , struct conn * conns , int nbconn )
{
struct sockaddr_storage addr ;
socklen_t addrlen ;
struct conn * conn ;
2022-03-03 17:36:53 +01:00
char * pktbuf = trash ;
2022-03-03 16:53:46 +01:00
int ret ;
int i ;
2022-03-03 17:36:53 +01:00
if ( rand_rate > 0 ) {
/* keep a copy of this packet */
history_idx + + ;
if ( history_idx > = MAXREORDER )
history_idx = 0 ;
pktbuf = history [ history_idx ] . buf ;
}
ret = recvfrom ( fd , pktbuf , MAXPKTSIZE , MSG_DONTWAIT | MSG_NOSIGNAL ,
2022-03-03 16:53:46 +01:00
( struct sockaddr * ) & addr , & addrlen ) ;
2022-03-03 17:36:53 +01:00
if ( rand_rate > 0 ) {
history [ history_idx ] . len = ret ; // note: we may store -1/EAGAIN
if ( prng ( 100 ) < rand_rate ) {
/* return a random buffer or nothing */
int idx = prng ( MAXREORDER + 1 ) - 1 ;
if ( idx < 0 ) {
/* pretend we didn't receive anything */
return 0 ;
}
pktbuf = history [ idx ] . buf ;
ret = history [ idx ] . len ;
if ( ret < 0 )
errno = EAGAIN ;
}
}
2022-03-03 16:53:46 +01:00
if ( ret = = 0 )
return 0 ;
if ( ret < 0 )
return errno = = EAGAIN ? 0 : - 1 ;
2022-03-16 15:07:51 +01:00
if ( corr_rate > 0 & & prng ( 100 ) < corr_rate ) {
unsigned int rnd = prng ( corr_span * 256 ) ; // pos and value
unsigned int pos = corr_base + ( rnd > > 8 ) ;
if ( pos < ret )
pktbuf [ pos ] ^ = rnd ;
}
2022-03-03 16:53:46 +01:00
conn = NULL ;
for ( i = 0 ; i < nbconn ; i + + ) {
if ( addr . ss_family ! = conns [ i ] . cli_addr . ss_family )
continue ;
if ( memcmp ( & conns [ i ] . cli_addr , & addr ,
( addr . ss_family = = AF_INET6 ) ?
sizeof ( struct sockaddr_in6 ) :
sizeof ( struct sockaddr_in ) ) ! = 0 )
continue ;
conn = & conns [ i ] ;
break ;
}
if ( ! conn ) {
/* address not found, create a new conn or replace the oldest
* one . For now we support a single one .
*/
conn = & conns [ 0 ] ;
memcpy ( & conn - > cli_addr , & addr ,
( addr . ss_family = = AF_INET6 ) ?
sizeof ( struct sockaddr_in6 ) :
sizeof ( struct sockaddr_in ) ) ;
if ( conn - > fd_bck < 0 ) {
/* try to create a new connection */
conn - > fd_bck = add_connection ( & srv_addr ) ;
nbfd = update_pfd ( pfd , fd , conns , nbconn ) ; // FIXME: MAXCONN instead ?
}
}
if ( conn - > fd_bck < 0 )
return 0 ;
2022-03-03 17:36:53 +01:00
ret = send ( conn - > fd_bck , pktbuf , ret , MSG_DONTWAIT | MSG_NOSIGNAL ) ;
2022-03-03 16:53:46 +01:00
return ret ;
}
/* Handle a read operation on an FD. Close and return 0 when the read returns zero or an error */
int handle_bck ( int fd , struct pollfd * pfd , struct conn * conns , int nbconn )
{
struct sockaddr_storage addr ;
socklen_t addrlen ;
struct conn * conn ;
2022-03-03 17:36:53 +01:00
char * pktbuf = trash ;
2022-03-03 16:53:46 +01:00
int ret ;
2022-03-03 17:36:53 +01:00
if ( rand_rate > 0 ) {
/* keep a copy of this packet */
history_idx + + ;
if ( history_idx > = MAXREORDER )
history_idx = 0 ;
pktbuf = history [ history_idx ] . buf ;
}
ret = recvfrom ( fd , pktbuf , MAXPKTSIZE , MSG_DONTWAIT | MSG_NOSIGNAL ,
2022-03-03 16:53:46 +01:00
( struct sockaddr * ) & addr , & addrlen ) ;
2022-03-03 17:36:53 +01:00
if ( rand_rate > 0 ) {
history [ history_idx ] . len = ret ; // note: we may store -1/EAGAIN
if ( prng ( 100 ) < rand_rate ) {
/* return a random buffer or nothing */
int idx = prng ( MAXREORDER + 1 ) - 1 ;
if ( idx < 0 ) {
/* pretend we didn't receive anything */
return 0 ;
}
pktbuf = history [ idx ] . buf ;
ret = history [ idx ] . len ;
if ( ret < 0 )
errno = EAGAIN ;
}
}
2022-03-03 16:53:46 +01:00
if ( ret = = 0 )
return 0 ;
if ( ret < 0 )
return errno = = EAGAIN ? 0 : - 1 ;
conn = conn_bck_lookup ( conns , nbconn , fd ) ;
if ( ! conn )
return 0 ;
2022-03-03 17:36:53 +01:00
ret = sendto ( fd_frt , pktbuf , ret , MSG_DONTWAIT | MSG_NOSIGNAL ,
2022-03-03 16:53:46 +01:00
( struct sockaddr * ) & conn - > cli_addr ,
conn - > cli_addr . ss_family = = AF_INET6 ?
sizeof ( struct sockaddr_in6 ) : sizeof ( struct sockaddr_in ) ) ;
return ret ;
}
2022-03-16 14:49:33 +01:00
/* print the usage message for program named <name> and exit with status <status> */
void usage ( int status , const char * name )
{
if ( strchr ( name , ' / ' ) )
name = strrchr ( name , ' / ' ) + 1 ;
die ( status ,
" Usage: %s [-h] [options] [<laddr>:]<lport> [<saddr>:]<sport> \n "
" Options: \n "
" -h display this help \n "
" -r rate reorder/duplicate/lose around <rate>%% of packets \n "
" -s seed force initial random seed (currently %#x) \n "
2022-03-16 15:07:51 +01:00
" -c rate corrupt around <rate>%% of packets \n "
" -o ofs start offset of corrupted area (def: 0) \n "
" -w width width of the corrupted area (def: 1) \n "
2022-03-16 14:49:33 +01:00
" " , name , prng_state ) ;
}
2022-03-03 16:53:46 +01:00
int main ( int argc , char * * argv )
{
struct errmsg err ;
struct pollfd * pfd ;
2022-03-16 14:49:33 +01:00
int opt ;
2022-03-03 16:53:46 +01:00
int i ;
err . len = 0 ;
err . size = 100 ;
err . msg = malloc ( err . size ) ;
2022-03-16 15:07:51 +01:00
while ( ( opt = getopt ( argc , argv , " hr:s:c:o:w: " ) ) ! = - 1 ) {
2022-03-16 14:49:33 +01:00
switch ( opt ) {
case ' r ' : // rand_rate%
rand_rate = atoi ( optarg ) ;
break ;
case ' s ' : // seed
prng_state = atol ( optarg ) ;
break ;
2022-03-16 15:07:51 +01:00
case ' c ' : // corruption rate
corr_rate = atol ( optarg ) ;
break ;
case ' o ' : // corruption offset
corr_base = atol ( optarg ) ;
break ;
case ' w ' : // corruption width
corr_span = atol ( optarg ) ;
break ;
2022-03-16 14:49:33 +01:00
default : // help, anything else
usage ( 0 , argv [ 0 ] ) ;
}
}
if ( argc - optind < 2 )
usage ( 1 , argv [ 0 ] ) ;
2022-03-03 16:53:46 +01:00
2022-03-16 14:49:33 +01:00
if ( addr_to_ss ( argv [ optind ] , & frt_addr , & err ) < 0 )
2022-03-03 16:53:46 +01:00
die ( 1 , " parsing listen address: %s \n " , err . msg ) ;
2022-03-16 14:49:33 +01:00
if ( addr_to_ss ( argv [ optind + 1 ] , & srv_addr , & err ) < 0 )
2022-03-03 16:53:46 +01:00
die ( 1 , " parsing server address: %s \n " , err . msg ) ;
pfd = calloc ( sizeof ( struct pollfd ) , MAXCONN + 1 ) ;
if ( ! pfd )
die ( 1 , " out of memory \n " ) ;
fd_frt = create_udp_listener ( & frt_addr , & err ) ;
if ( fd_frt < 0 )
die ( 1 , " binding listener: %s \n " , err . msg ) ;
for ( i = 0 ; i < MAXCONN ; i + + )
conns [ i ] . fd_bck = - 1 ;
nbfd = update_pfd ( pfd , fd_frt , conns , MAXCONN ) ;
while ( 1 ) {
/* listen for incoming packets */
int ret , i ;
ret = poll ( pfd , nbfd , 1000 ) ;
if ( ret < = 0 )
continue ;
for ( i = 0 ; ret ; i + + ) {
if ( ! pfd [ i ] . revents )
continue ;
ret - - ;
if ( pfd [ i ] . fd = = fd_frt ) {
handle_frt ( pfd [ i ] . fd , pfd , conns , nbconn ) ;
continue ;
}
handle_bck ( pfd [ i ] . fd , pfd , conns , nbconn ) ;
}
}
}