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/>.
*/
# include "replace.h"
# include "system/network.h"
2018-06-29 08:55:49 +03:00
# include <talloc.h>
# include <tevent.h>
2016-03-03 02:34:48 +03:00
# include "lib/util/debug.h"
2017-06-30 12:50:43 +03:00
# include "lib/util/tevent_unix.h"
2016-03-03 02:34:48 +03:00
# include "protocol/protocol.h"
2017-09-04 09:00:48 +03:00
# include "protocol/protocol_util.h"
2016-03-03 02:34:48 +03:00
2017-06-29 08:57:19 +03:00
# include "common/db_hash.h"
2018-06-28 14:06:58 +03:00
# include "common/system_socket.h"
2016-03-03 02:34:48 +03:00
# include "common/logging.h"
2016-03-11 08:04:30 +03:00
2017-06-30 12:50:43 +03:00
struct reset_connections_state {
struct tevent_context * ev ;
2016-03-11 08:04:30 +03:00
int capture_fd ;
struct tevent_fd * fde ;
2017-06-29 08:57:19 +03:00
struct db_hash_context * connections ;
2016-03-11 08:04:30 +03:00
void * private_data ;
2016-03-23 00:20:07 +03:00
unsigned int attempts ;
unsigned int max_attempts ;
2016-03-23 03:03:41 +03:00
struct timeval retry_interval ;
2016-03-24 07:11:22 +03:00
unsigned int batch_count ;
unsigned int batch_size ;
2016-03-11 08:04:30 +03:00
} ;
2016-03-03 02:34:48 +03:00
2017-06-30 12:50:43 +03:00
static void reset_connections_capture_tcp_handler ( struct tevent_context * ev ,
struct tevent_fd * fde ,
uint16_t flags ,
void * private_data ) ;
static void reset_connections_batch ( struct tevent_req * subreq ) ;
static int reset_connections_tickle_connection (
uint8_t * keybuf , size_t keylen ,
uint8_t * databuf , size_t datalen ,
void * private_data ) ;
2017-06-29 09:35:06 +03:00
2017-06-30 12:50:43 +03:00
static struct tevent_req * reset_connections_send (
TALLOC_CTX * mem_ctx ,
2017-06-29 09:35:06 +03:00
struct tevent_context * ev ,
const char * iface ,
2017-06-30 12:50:43 +03:00
struct ctdb_connection_list * conn_list )
2017-06-29 09:35:06 +03:00
{
2017-06-30 12:50:43 +03:00
struct tevent_req * req , * subreq ;
struct reset_connections_state * state ;
2019-05-22 14:54:04 +03:00
unsigned int i ;
int ret ;
2017-06-29 09:35:06 +03:00
2017-06-30 12:50:43 +03:00
req = tevent_req_create ( mem_ctx , & state ,
struct reset_connections_state ) ;
if ( req = = NULL ) {
return NULL ;
2017-06-29 09:35:06 +03:00
}
2017-06-30 12:50:43 +03:00
state - > ev = ev ;
2017-07-04 07:02:14 +03:00
if ( conn_list - > num = = 0 ) {
/* No connections, done! */
tevent_req_done ( req ) ;
return tevent_req_post ( req , ev ) ;
}
2017-06-29 09:35:06 +03:00
ret = db_hash_init ( state , " connections " , 2048 , DB_HASH_SIMPLE ,
& state - > connections ) ;
if ( ret ! = 0 ) {
D_ERR ( " Failed to initialise connection hash (%s) \n " ,
strerror ( ret ) ) ;
2017-06-30 12:50:43 +03:00
tevent_req_error ( req , ret ) ;
return tevent_req_post ( req , ev ) ;
2017-06-29 09:35:06 +03:00
}
2017-07-04 05:11:20 +03:00
DBG_DEBUG ( " Adding %u connections to hash \n " , conn_list - > num ) ;
2017-06-29 09:35:06 +03:00
for ( i = 0 ; i < conn_list - > num ; i + + ) {
struct ctdb_connection * c = & conn_list - > conn [ i ] ;
2017-07-04 05:11:20 +03:00
DBG_DEBUG ( " Adding connection to hash: %s \n " ,
ctdb_connection_to_string ( conn_list , c , true ) ) ;
2017-06-29 09:35:06 +03:00
/* Connection is stored as a key in the connections hash */
ret = db_hash_add ( state - > connections ,
( uint8_t * ) discard_const ( c ) , sizeof ( * c ) ,
NULL , 0 ) ;
if ( ret ! = 0 ) {
D_ERR ( " Error adding connection to hash (%s) \n " ,
strerror ( ret ) ) ;
2017-06-30 12:50:43 +03:00
tevent_req_error ( req , ret ) ;
return tevent_req_post ( req , ev ) ;
2017-06-29 09:35:06 +03:00
}
}
state - > attempts = 0 ;
state - > max_attempts = 50 ;
state - > retry_interval . tv_sec = 0 ;
state - > retry_interval . tv_usec = 100 * 1000 ;
state - > batch_count = 0 ;
state - > batch_size = 300 ;
state - > capture_fd =
ctdb_sys_open_capture_socket ( iface , & state - > private_data ) ;
if ( state - > capture_fd = = - 1 ) {
D_ERR ( " Failed to open capture socket on iface '%s' (%s) \n " ,
iface , strerror ( errno ) ) ;
2017-06-30 12:50:43 +03:00
tevent_req_error ( req , EIO ) ;
return tevent_req_post ( req , ev ) ;
2017-06-29 09:35:06 +03:00
}
state - > fde = tevent_add_fd ( ev , state , state - > capture_fd ,
2017-06-30 12:50:43 +03:00
TEVENT_FD_READ ,
reset_connections_capture_tcp_handler ,
2017-06-29 09:35:06 +03:00
state ) ;
2017-06-30 12:50:43 +03:00
if ( tevent_req_nomem ( state - > fde , req ) ) {
return tevent_req_post ( req , ev ) ;
2017-06-29 09:35:06 +03:00
}
tevent_fd_set_auto_close ( state - > fde ) ;
2017-06-30 12:50:43 +03:00
subreq = tevent_wakeup_send ( state , ev , tevent_timeval_current_ofs ( 0 , 0 ) ) ;
if ( tevent_req_nomem ( subreq , req ) ) {
return tevent_req_post ( req , ev ) ;
}
tevent_req_set_callback ( subreq , reset_connections_batch , req ) ;
return req ;
2017-06-29 09:35:06 +03:00
}
2016-03-11 08:04:30 +03:00
/*
called when we get a read event on the raw socket
*/
2017-06-30 12:50:43 +03:00
static void reset_connections_capture_tcp_handler ( struct tevent_context * ev ,
struct tevent_fd * fde ,
uint16_t flags ,
void * private_data )
2016-03-11 08:04:30 +03:00
{
2017-06-30 12:50:43 +03:00
struct reset_connections_state * state = talloc_get_type_abort (
private_data , struct reset_connections_state ) ;
2017-06-29 07:46:31 +03:00
/* 0 the parts that don't get set by ctdb_sys_read_tcp_packet */
struct ctdb_connection conn ;
2016-03-11 08:04:30 +03:00
uint32_t ack_seq , seq ;
2016-03-21 03:07:19 +03:00
int rst ;
uint16_t window ;
2017-06-29 08:57:19 +03:00
int ret ;
2016-03-11 08:04:30 +03:00
2017-06-30 12:50:43 +03:00
ret = ctdb_sys_read_tcp_packet ( state - > capture_fd ,
state - > private_data ,
2017-09-14 08:19:43 +03:00
& conn . server , & conn . client ,
& ack_seq , & seq , & rst , & window ) ;
if ( ret ! = 0 ) {
2016-03-11 08:04:30 +03:00
/* probably a non-tcp ACK packet */
return ;
}
2016-03-21 03:11:19 +03:00
if ( window = = htons ( 1234 ) & & ( rst | | seq = = 0 ) ) {
/* Ignore packets that we sent! */
2017-06-29 07:46:31 +03:00
D_DEBUG ( " Ignoring packet: %s, "
" seq=% " PRIu32 " , ack_seq=% " PRIu32 " , "
" rst=%d, window=% " PRIu16 " \n " ,
2017-06-30 12:50:43 +03:00
ctdb_connection_to_string ( state , & conn , false ) ,
2017-06-29 07:46:31 +03:00
seq , ack_seq , rst , ntohs ( window ) ) ;
2016-03-21 03:11:19 +03:00
return ;
}
2017-06-29 08:57:19 +03:00
/* Check if this connection is one being reset, if found then delete */
2017-06-30 12:50:43 +03:00
ret = db_hash_delete ( state - > connections ,
2017-06-29 08:57:19 +03:00
( uint8_t * ) & conn , sizeof ( conn ) ) ;
if ( ret = = ENOENT ) {
/* Packet for some other connection, ignore */
2017-07-04 05:11:20 +03:00
DBG_DEBUG ( " Ignoring packet for unknown connection: %s \n " ,
ctdb_connection_to_string ( state , & conn , true ) ) ;
2017-06-29 08:57:19 +03:00
return ;
}
if ( ret ! = 0 ) {
DBG_WARNING ( " Internal error (%s) \n " , strerror ( ret ) ) ;
2016-03-11 08:04:30 +03:00
return ;
}
2017-06-30 12:50:43 +03:00
D_INFO ( " Sending a TCP RST to for connection %s \n " ,
ctdb_connection_to_string ( state , & conn , true ) ) ;
2016-03-11 08:04:30 +03:00
2017-09-14 08:19:43 +03:00
ret = ctdb_sys_send_tcp ( & conn . server , & conn . client , ack_seq , seq , 1 ) ;
if ( ret ! = 0 ) {
DBG_ERR ( " Error sending TCP RST for connection \n " ) ;
}
2016-03-11 08:04:30 +03:00
}
/*
2017-06-29 08:57:19 +03:00
* Called periodically until all sentenced connections have been reset
* or enough attempts have been made
2016-03-11 08:04:30 +03:00
*/
2017-06-30 12:50:43 +03:00
static void reset_connections_batch ( struct tevent_req * subreq )
2016-03-11 08:04:30 +03:00
{
2017-06-30 12:50:43 +03:00
struct tevent_req * req = tevent_req_callback_data (
subreq , struct tevent_req ) ;
struct reset_connections_state * state = tevent_req_data (
req , struct reset_connections_state ) ;
bool status ;
2017-06-29 08:57:19 +03:00
int count , ret ;
2016-03-11 08:04:30 +03:00
2017-06-30 12:50:43 +03:00
status = tevent_wakeup_recv ( subreq ) ;
TALLOC_FREE ( subreq ) ;
if ( ! status ) {
DBG_WARNING ( " Unexpected error on timer expiry \n " ) ;
/* Keep going... */
}
2016-03-24 07:11:22 +03:00
/* loop over up to batch_size connections sending tickle ACKs */
2017-06-30 12:50:43 +03:00
state - > batch_count = 0 ;
ret = db_hash_traverse ( state - > connections ,
reset_connections_tickle_connection ,
state , NULL ) ;
2017-06-29 08:57:19 +03:00
if ( ret ! = 0 ) {
DBG_WARNING ( " Unexpected error traversing connections (%s) \n " ,
strerror ( ret ) ) ;
}
2016-03-11 08:04:30 +03:00
2017-06-30 12:50:43 +03:00
state - > attempts + + ;
2016-03-23 00:20:07 +03:00
2017-06-29 08:57:19 +03:00
/*
* If there are no more connections to kill or we have tried
2017-06-30 12:50:43 +03:00
* too many times we ' re finished
2016-03-11 08:04:30 +03:00
*/
2017-06-30 12:50:43 +03:00
ret = db_hash_traverse ( state - > connections , NULL , NULL , & count ) ;
2017-06-29 08:57:19 +03:00
if ( ret ! = 0 ) {
/* What now? Try again until max_attempts reached */
DBG_WARNING ( " Unexpected error traversing connections (%s) \n " ,
strerror ( ret ) ) ;
count = 1 ;
}
if ( count = = 0 | |
2017-06-30 12:50:43 +03:00
state - > attempts > = state - > max_attempts ) {
tevent_req_done ( req ) ;
2016-03-11 08:04:30 +03:00
return ;
}
2017-06-30 12:50:43 +03:00
/* Schedule next attempt */
subreq = tevent_wakeup_send ( state , state - > ev ,
tevent_timeval_current_ofs (
state - > retry_interval . tv_sec ,
state - > retry_interval . tv_usec ) ) ;
if ( tevent_req_nomem ( subreq , req ) ) {
return ;
}
tevent_req_set_callback ( subreq , reset_connections_batch , req ) ;
2016-03-11 08:04:30 +03:00
}
2017-06-30 12:50:43 +03:00
static int reset_connections_tickle_connection (
uint8_t * keybuf , size_t keylen ,
uint8_t * databuf , size_t datalen ,
void * private_data )
2016-03-03 02:34:48 +03:00
{
2017-06-30 12:50:43 +03:00
struct reset_connections_state * state = talloc_get_type_abort (
private_data , struct reset_connections_state ) ;
struct ctdb_connection * conn ;
int ret ;
if ( keylen ! = sizeof ( * conn ) ) {
DBG_WARNING ( " Unexpected data in connection hash \n " ) ;
return 0 ;
}
conn = ( struct ctdb_connection * ) keybuf ;
state - > batch_count + + ;
if ( state - > batch_count > state - > batch_size ) {
/* Terminate the traverse */
return 1 ;
}
2017-07-04 05:11:20 +03:00
DBG_DEBUG ( " Sending tickle ACK for connection '%s' \n " ,
ctdb_connection_to_string ( state , conn , true ) ) ;
2017-06-30 12:50:43 +03:00
ret = ctdb_sys_send_tcp ( & conn - > server , & conn - > client , 0 , 0 , 0 ) ;
if ( ret ! = 0 ) {
DBG_ERR ( " Error sending tickle ACK \n " ) ;
/* continue */
}
2016-03-03 02:34:48 +03:00
return 0 ;
}
2017-06-30 12:50:43 +03:00
static bool reset_connections_recv ( struct tevent_req * req , int * perr )
{
int err ;
if ( tevent_req_is_unix_error ( req , & err ) ) {
if ( perr ! = NULL ) {
* perr = err ;
}
return false ;
}
return true ;
}
2017-06-30 10:12:48 +03:00
static void usage ( const char * prog )
2016-03-03 02:34:48 +03:00
{
printf ( " usage: %s <interface> [ <srcip:port> <dstip:port> ] \n " , prog ) ;
exit ( 1 ) ;
}
int main ( int argc , char * * argv )
{
struct ctdb_connection conn ;
struct tevent_context * ev = NULL ;
2017-09-06 11:11:41 +03:00
TALLOC_CTX * mem_ctx = NULL ;
2017-06-29 07:46:31 +03:00
struct ctdb_connection_list * conn_list = NULL ;
2016-03-21 03:42:40 +03:00
const char * t ;
2017-06-30 12:50:43 +03:00
struct tevent_req * req ;
2016-11-25 05:23:11 +03:00
int debug_level ;
2017-06-30 12:50:43 +03:00
bool status ;
2018-11-07 16:14:05 +03:00
bool ok ;
2017-06-29 09:35:06 +03:00
int ret ;
2016-03-03 02:34:48 +03:00
2016-03-21 03:42:40 +03:00
/* Set the debug level */
t = getenv ( " CTDB_DEBUGLEVEL " ) ;
if ( t ! = NULL ) {
2018-11-07 16:14:05 +03:00
ok = debug_level_parse ( t , & debug_level ) ;
if ( ! ok ) {
debug_level = DEBUG_ERR ;
2016-03-21 03:42:40 +03:00
}
2018-11-07 16:14:05 +03:00
debuglevel_set ( debug_level ) ;
2016-03-21 03:42:40 +03:00
}
2016-03-03 02:34:48 +03:00
if ( argc ! = 2 & & argc ! = 4 ) {
2017-06-30 10:12:48 +03:00
usage ( argv [ 0 ] ) ;
2016-03-03 02:34:48 +03:00
}
if ( argc = = 4 ) {
2017-06-29 07:46:31 +03:00
ret = ctdb_sock_addr_from_string ( argv [ 2 ] , & conn . client , true ) ;
if ( ret ! = 0 ) {
D_ERR ( " Bad IP:port '%s' \n " , argv [ 2 ] ) ;
2016-03-03 02:34:48 +03:00
goto fail ;
}
2017-06-29 07:46:31 +03:00
ret = ctdb_sock_addr_from_string ( argv [ 3 ] , & conn . server , true ) ;
if ( ret ! = 0 ) {
D_ERR ( " Bad IP:port '%s' \n " , argv [ 3 ] ) ;
2016-03-03 02:34:48 +03:00
goto fail ;
}
2017-06-29 07:46:31 +03:00
conn_list = talloc_zero ( mem_ctx , struct ctdb_connection_list ) ;
if ( conn_list = = NULL ) {
ret = ENOMEM ;
DBG_ERR ( " Internal error (%s) \n " , strerror ( ret ) ) ;
goto fail ;
}
ret = ctdb_connection_list_add ( conn_list , & conn ) ;
if ( ret ! = 0 ) {
DBG_ERR ( " Internal error (%s) \n " , strerror ( ret ) ) ;
goto fail ;
}
2016-03-03 02:34:48 +03:00
} else {
2018-07-18 12:00:42 +03:00
ret = ctdb_connection_list_read ( mem_ctx , 0 , true , & conn_list ) ;
2016-03-03 02:34:48 +03:00
if ( ret ! = 0 ) {
2017-06-29 07:46:31 +03:00
D_ERR ( " Unable to parse connections (%s) \n " ,
strerror ( ret ) ) ;
2016-03-03 02:34:48 +03:00
goto fail ;
}
}
mem_ctx = talloc_new ( NULL ) ;
if ( mem_ctx = = NULL ) {
DEBUG ( DEBUG_ERR , ( __location__ " out of memory \n " ) ) ;
goto fail ;
}
2017-06-30 12:50:43 +03:00
ev = tevent_context_init ( mem_ctx ) ;
2016-03-03 02:34:48 +03:00
if ( ev = = NULL ) {
DEBUG ( DEBUG_ERR , ( " Failed to initialise tevent \n " ) ) ;
goto fail ;
}
2017-06-30 12:50:43 +03:00
req = reset_connections_send ( mem_ctx , ev , argv [ 1 ] , conn_list ) ;
if ( req = = NULL ) {
2017-06-29 09:35:06 +03:00
goto fail ;
2016-03-03 02:34:48 +03:00
}
2017-06-30 12:50:43 +03:00
tevent_req_poll ( req , ev ) ;
2016-03-03 02:34:48 +03:00
2017-06-30 12:50:43 +03:00
status = reset_connections_recv ( req , & ret ) ;
if ( ! status ) {
D_ERR ( " Failed to kill connections (%s) \n " , strerror ( ret ) ) ;
goto fail ;
2016-03-03 02:34:48 +03:00
}
talloc_free ( mem_ctx ) ;
return 0 ;
fail :
TALLOC_FREE ( mem_ctx ) ;
return - 1 ;
}