2016-03-03 02:34:48 +03:00
/*
CTDB TCP connection killing utility
Copyright ( C ) Martin Schwenke < martin @ meltin . net > 2016
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , see < http : //www.gnu.org/licenses/>.
*/
2016-03-11 08:04:30 +03:00
# include <talloc.h>
# include <tevent.h>
2016-03-03 02:34:48 +03:00
# include "replace.h"
# include "system/network.h"
# include "lib/util/debug.h"
# include "protocol/protocol.h"
2016-03-11 08:04:30 +03:00
# include "protocol/protocol_api.h"
2016-03-03 02:34:48 +03:00
2016-03-11 08:04:30 +03:00
# include "common/rb_tree.h"
2016-03-03 02:34:48 +03:00
# include "common/system.h"
# include "common/logging.h"
2016-03-11 08:04:30 +03:00
/* Contains the listening socket and the list of TCP connections to
* kill */
struct ctdb_kill_tcp {
int capture_fd ;
struct tevent_fd * fde ;
trbt_tree_t * connections ;
void * private_data ;
void * destructor_data ;
} ;
2016-03-03 02:34:48 +03:00
static const char * prog ;
2016-03-11 08:04:30 +03:00
/* TCP connection to be killed */
struct ctdb_killtcp_con {
ctdb_sock_addr src_addr ;
ctdb_sock_addr dst_addr ;
int count ;
struct ctdb_kill_tcp * killtcp ;
} ;
/* this function is used to create a key to represent this socketpair
in the killtcp tree .
this key is used to insert and lookup matching socketpairs that are
to be tickled and RST
*/
# define KILLTCP_KEYLEN 10
static uint32_t * killtcp_key ( ctdb_sock_addr * src , ctdb_sock_addr * dst )
{
static uint32_t key [ KILLTCP_KEYLEN ] ;
bzero ( key , sizeof ( key ) ) ;
if ( src - > sa . sa_family ! = dst - > sa . sa_family ) {
DEBUG ( DEBUG_ERR , ( __location__ " ERROR, different families passed :%u vs %u \n " , src - > sa . sa_family , dst - > sa . sa_family ) ) ;
return key ;
}
switch ( src - > sa . sa_family ) {
case AF_INET :
key [ 0 ] = dst - > ip . sin_addr . s_addr ;
key [ 1 ] = src - > ip . sin_addr . s_addr ;
key [ 2 ] = dst - > ip . sin_port ;
key [ 3 ] = src - > ip . sin_port ;
break ;
case AF_INET6 : {
uint32_t * dst6_addr32 =
( uint32_t * ) & ( dst - > ip6 . sin6_addr . s6_addr ) ;
uint32_t * src6_addr32 =
( uint32_t * ) & ( src - > ip6 . sin6_addr . s6_addr ) ;
key [ 0 ] = dst6_addr32 [ 3 ] ;
key [ 1 ] = src6_addr32 [ 3 ] ;
key [ 2 ] = dst6_addr32 [ 2 ] ;
key [ 3 ] = src6_addr32 [ 2 ] ;
key [ 4 ] = dst6_addr32 [ 1 ] ;
key [ 5 ] = src6_addr32 [ 1 ] ;
key [ 6 ] = dst6_addr32 [ 0 ] ;
key [ 7 ] = src6_addr32 [ 0 ] ;
key [ 8 ] = dst - > ip6 . sin6_port ;
key [ 9 ] = src - > ip6 . sin6_port ;
break ;
}
default :
DEBUG ( DEBUG_ERR , ( __location__ " ERROR, unknown family passed :%u \n " , src - > sa . sa_family ) ) ;
return key ;
}
return key ;
}
/*
called when we get a read event on the raw socket
*/
static void capture_tcp_handler ( struct tevent_context * ev ,
struct tevent_fd * fde ,
uint16_t flags , void * private_data )
{
struct ctdb_kill_tcp * killtcp = talloc_get_type ( private_data , struct ctdb_kill_tcp ) ;
struct ctdb_killtcp_con * con ;
ctdb_sock_addr src , dst ;
uint32_t ack_seq , seq ;
if ( ctdb_sys_read_tcp_packet ( killtcp - > capture_fd ,
killtcp - > private_data ,
& src , & dst ,
& ack_seq , & seq ) ! = 0 ) {
/* probably a non-tcp ACK packet */
return ;
}
/* check if we have this guy in our list of connections
to kill
*/
con = trbt_lookuparray32 ( killtcp - > connections ,
KILLTCP_KEYLEN , killtcp_key ( & src , & dst ) ) ;
if ( con = = NULL ) {
/* no this was some other packet we can just ignore */
return ;
}
/* This one has been tickled !
now reset him and remove him from the list .
*/
DEBUG ( DEBUG_INFO ,
( " sending a tcp reset to kill connection :%d -> %s:%d \n " ,
ntohs ( con - > dst_addr . ip . sin_port ) ,
ctdb_sock_addr_to_string ( con , & con - > src_addr ) ,
ntohs ( con - > src_addr . ip . sin_port ) ) ) ;
ctdb_sys_send_tcp ( & con - > dst_addr , & con - > src_addr , ack_seq , seq , 1 ) ;
talloc_free ( con ) ;
}
/* when traversing the list of all tcp connections to send tickle acks to
( so that we can capture the ack coming back and kill the connection
by a RST )
this callback is called for each connection we are currently trying to kill
*/
static int tickle_connection_traverse ( void * param , void * data )
{
struct ctdb_killtcp_con * con = talloc_get_type ( data , struct ctdb_killtcp_con ) ;
/* have tried too many times, just give up */
if ( con - > count > = 5 ) {
/* can't delete in traverse: reparent to delete_cons */
talloc_steal ( param , con ) ;
return 0 ;
}
con - > count + + ;
2016-03-23 06:49:05 +03:00
ctdb_sys_send_tcp ( & con - > dst_addr , & con - > src_addr , 0 , 0 , 0 ) ;
2016-03-11 08:04:30 +03:00
return 0 ;
}
/*
called every second until all sentenced connections have been reset
*/
static void ctdb_tickle_sentenced_connections ( struct tevent_context * ev ,
struct tevent_timer * te ,
struct timeval t , void * private_data )
{
struct ctdb_kill_tcp * killtcp = talloc_get_type ( private_data , struct ctdb_kill_tcp ) ;
void * delete_cons = talloc_new ( NULL ) ;
/* loop over all connections sending tickle ACKs */
trbt_traversearray32 ( killtcp - > connections , KILLTCP_KEYLEN , tickle_connection_traverse , delete_cons ) ;
/* now we've finished traverse, it's safe to do deletion. */
talloc_free ( delete_cons ) ;
/* If there are no more connections to kill we can remove the
entire killtcp structure
*/
if ( ( killtcp - > connections = = NULL ) | |
( killtcp - > connections - > root = = NULL ) ) {
talloc_free ( killtcp ) ;
return ;
}
/* try tickling them again in a seconds time
*/
tevent_add_timer ( ev , killtcp ,
tevent_timeval_current_ofs ( 1 , 0 ) ,
ctdb_tickle_sentenced_connections , killtcp ) ;
}
/* nothing fancy here, just unconditionally replace any existing
connection structure with the new one .
don ' t even free the old one if it did exist , that one is talloc_stolen
by the same node in the tree anyway and will be deleted when the new data
is deleted
*/
static void * add_killtcp_callback ( void * parm , void * data )
{
return parm ;
}
/* Add a TCP socket to the list of connections we want to RST. The
* list is attached to * killtcp_arg . If this is NULL then allocate
* the structure . */
static int ctdb_killtcp ( struct tevent_context * ev ,
TALLOC_CTX * mem_ctx ,
const char * iface ,
const ctdb_sock_addr * src ,
const ctdb_sock_addr * dst ,
struct ctdb_kill_tcp * * killtcp_arg )
{
struct ctdb_kill_tcp * killtcp ;
struct ctdb_killtcp_con * con ;
if ( killtcp_arg = = NULL ) {
DEBUG ( DEBUG_ERR , ( __location__ " killtcp_arg is NULL! \n " ) ) ;
return - 1 ;
}
killtcp = * killtcp_arg ;
/* Allocate a new structure if necessary. The structure is
* only freed when mem_ctx is freed . */
if ( killtcp = = NULL ) {
killtcp = talloc_zero ( mem_ctx , struct ctdb_kill_tcp ) ;
if ( killtcp = = NULL ) {
DEBUG ( DEBUG_ERR , ( __location__ " out of memory \n " ) ) ;
return - 1 ;
}
killtcp - > capture_fd = - 1 ;
killtcp - > connections = trbt_create ( killtcp , 0 ) ;
* killtcp_arg = killtcp ;
}
/* create a structure that describes this connection we want to
RST and store it in killtcp - > connections
*/
con = talloc ( killtcp , struct ctdb_killtcp_con ) ;
if ( con = = NULL ) {
DEBUG ( DEBUG_ERR , ( __location__ " out of memory \n " ) ) ;
return - 1 ;
}
con - > src_addr = * src ;
con - > dst_addr = * dst ;
con - > count = 0 ;
con - > killtcp = killtcp ;
trbt_insertarray32_callback ( killtcp - > connections ,
KILLTCP_KEYLEN ,
killtcp_key ( & con - > dst_addr ,
& con - > src_addr ) ,
add_killtcp_callback , con ) ;
/*
If we don ' t have a socket to listen on yet we must create it
*/
if ( killtcp - > capture_fd = = - 1 ) {
killtcp - > capture_fd =
ctdb_sys_open_capture_socket ( iface ,
& killtcp - > private_data ) ;
if ( killtcp - > capture_fd = = - 1 ) {
DEBUG ( DEBUG_CRIT , ( __location__ " Failed to open capturing "
" socket on iface '%s' for killtcp (%s) \n " ,
iface , strerror ( errno ) ) ) ;
return - 1 ;
}
}
if ( killtcp - > fde = = NULL ) {
killtcp - > fde = tevent_add_fd ( ev , killtcp ,
killtcp - > capture_fd ,
TEVENT_FD_READ ,
capture_tcp_handler , killtcp ) ;
tevent_fd_set_auto_close ( killtcp - > fde ) ;
/* We also need to set up some events to tickle all these connections
until they are all reset
*/
tevent_add_timer ( ev , killtcp , tevent_timeval_current_ofs ( 1 , 0 ) ,
ctdb_tickle_sentenced_connections , killtcp ) ;
}
/* tickle him once now */
ctdb_sys_send_tcp (
& con - > dst_addr ,
& con - > src_addr ,
0 , 0 , 0 ) ;
return 0 ;
}
2016-03-03 02:34:48 +03:00
static int ctdb_killtcp_destructor ( struct ctdb_kill_tcp * killtcp )
{
bool * done = killtcp - > destructor_data ;
* done = true ;
return 0 ;
}
static void usage ( void )
{
printf ( " usage: %s <interface> [ <srcip:port> <dstip:port> ] \n " , prog ) ;
exit ( 1 ) ;
}
int main ( int argc , char * * argv )
{
struct ctdb_connection conn ;
struct ctdb_kill_tcp * killtcp = NULL ;
struct tevent_context * ev = NULL ;
struct TALLOC_CONTEXT * mem_ctx = NULL ;
struct ctdb_connection * conns = NULL ;
bool done ;
int num = 0 ;
int i , ret ;
prog = argv [ 0 ] ;
if ( argc ! = 2 & & argc ! = 4 ) {
usage ( ) ;
}
if ( argc = = 4 ) {
if ( ! parse_ip_port ( argv [ 2 ] , & conn . src ) ) {
DEBUG ( DEBUG_ERR , ( " Bad IP:port '%s' \n " , argv [ 2 ] ) ) ;
goto fail ;
}
if ( ! parse_ip_port ( argv [ 3 ] , & conn . dst ) ) {
DEBUG ( DEBUG_ERR , ( " Bad IP:port '%s' \n " , argv [ 3 ] ) ) ;
goto fail ;
}
conns = & conn ;
num = 1 ;
} else {
ret = ctdb_parse_connections ( stdin , mem_ctx , & num , & conns ) ;
if ( ret ! = 0 ) {
DEBUG ( DEBUG_ERR ,
( " Unable to parse connections [%s] \n " ,
strerror ( ret ) ) ) ;
goto fail ;
}
}
mem_ctx = talloc_new ( NULL ) ;
if ( mem_ctx = = NULL ) {
DEBUG ( DEBUG_ERR , ( __location__ " out of memory \n " ) ) ;
goto fail ;
}
ev = tevent_context_init ( mem_ctx ) ;
if ( ev = = NULL ) {
DEBUG ( DEBUG_ERR , ( " Failed to initialise tevent \n " ) ) ;
goto fail ;
}
if ( num = = 0 ) {
/* No connections, done! */
talloc_free ( mem_ctx ) ;
return 0 ;
}
for ( i = 0 ; i < num ; i + + ) {
ret = ctdb_killtcp ( ev , mem_ctx , argv [ 1 ] ,
& conns [ i ] . src , & conns [ i ] . dst ,
& killtcp ) ;
if ( ret ! = 0 ) {
DEBUG ( DEBUG_ERR , ( " Unable to killtcp \n " ) ) ;
goto fail ;
}
}
done = false ;
killtcp - > destructor_data = & done ;
talloc_set_destructor ( killtcp , ctdb_killtcp_destructor ) ;
while ( ! done ) {
tevent_loop_once ( ev ) ;
}
talloc_free ( mem_ctx ) ;
return 0 ;
fail :
TALLOC_FREE ( mem_ctx ) ;
return - 1 ;
}