From: Martin Schwenke Date: Wed, 2 Mar 2016 05:27:02 +0000 (+1100) Subject: ctdb-killtcp: Factor out killtcp code into separate file. X-Git-Tag: tdb-1.3.9~49 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=51f5faf5557bf326a3a45a9851aed99335ddc8c2;p=thirdparty%2Fsamba.git ctdb-killtcp: Factor out killtcp code into separate file. This will be used in a standalone helper. Don't worry that the API isn't clean and opaque. All of the code will eventually move into the helper and will no longer be used by the daemon. Signed-off-by: Martin Schwenke Reviewed-by: Amitay Isaacs --- diff --git a/ctdb/server/ctdb_takeover.c b/ctdb/server/ctdb_takeover.c index 100fb95040f..1256c162e92 100644 --- a/ctdb/server/ctdb_takeover.c +++ b/ctdb/server/ctdb_takeover.c @@ -40,6 +40,7 @@ #include "common/system.h" #include "common/common.h" #include "common/logging.h" +#include "killtcp.h" #include "server/ipalloc.h" @@ -2626,182 +2627,6 @@ int32_t ctdb_control_set_iface_link(struct ctdb_context *ctdb, } -/* - structure containing the listening socket and the list of tcp connections - that the ctdb daemon is to kill -*/ -struct ctdb_kill_tcp { - int capture_fd; - struct tevent_fd *fde; - trbt_tree_t *connections; - void *private_data; - void *destructor_data; -}; - -/* - a tcp connection that is 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 (!(flags & TEVENT_FD_READ)) { - return; - } - - 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_addr_to_str(&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; - } - - /* othervise, try tickling it again */ - con->count++; - ctdb_sys_send_tcp( - (ctdb_sock_addr *)&con->dst_addr, - (ctdb_sock_addr *)&con->src_addr, - 0, 0, 0); - 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, - timeval_current_ofs(1, 0), - ctdb_tickle_sentenced_connections, killtcp); -} - /* destroy the killtcp structure */ @@ -2837,112 +2662,6 @@ static int ctdb_killtcp_destructor(struct ctdb_kill_tcp *killtcp) return 0; } - -/* 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, 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; -} - /* add a tcp socket to the list of connections we want to RST */ diff --git a/ctdb/server/killtcp.c b/ctdb/server/killtcp.c new file mode 100644 index 00000000000..3b5cf1aaf81 --- /dev/null +++ b/ctdb/server/killtcp.c @@ -0,0 +1,302 @@ +/* + CTDB TCP connection killing code + + Copyright (C) Ronnie Sahlberg 2007 + Copyright (C) Andrew Tridgell 2007 + Copyright (C) Martin Schwenke 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 . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/time.h" + +#include "ctdb_private.h" + +#include "common/rb_tree.h" +#include "common/system.h" +#include "common/common.h" +#include "common/logging.h" + +#include "killtcp.h" + +/* 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 (!(flags & TEVENT_FD_READ)) { + return; + } + + 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_addr_to_str(&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; + } + + /* othervise, try tickling it again */ + con->count++; + ctdb_sys_send_tcp( + (ctdb_sock_addr *)&con->dst_addr, + (ctdb_sock_addr *)&con->src_addr, + 0, 0, 0); + 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, + 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. */ +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, 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; +} diff --git a/ctdb/server/killtcp.h b/ctdb/server/killtcp.h new file mode 100644 index 00000000000..24ab35136d3 --- /dev/null +++ b/ctdb/server/killtcp.h @@ -0,0 +1,56 @@ +/* + CTDB TCP connection killing code + + Copyright (C) Ronnie Sahlberg 2007 + Copyright (C) Andrew Tridgell 2007 + Copyright (C) Martin Schwenke 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 . +*/ + +#ifndef _CTDB_KILLTCP_H_ +#define _CTDB_KILLTCP_H_ + +#include +#include + +#include "replace.h" +#include "system/network.h" + +#include "common/rb_tree.h" + +#include "protocol/protocol.h" + +/* 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; +}; + + +/* 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. */ +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); + +#endif /* _CTDB_KILLTCP_H_ */ diff --git a/ctdb/tests/src/ctdbd_test.c b/ctdb/tests/src/ctdbd_test.c index 5fa70144ed4..5a3f6b85c93 100644 --- a/ctdb/tests/src/ctdbd_test.c +++ b/ctdb/tests/src/ctdbd_test.c @@ -75,6 +75,7 @@ bool fast_start; #include "server/ctdb_update_record.c" #include "server/ctdb_lock.c" #include "server/ctdb_fork.c" +#include "server/killtcp.c" /* CTDB_CLIENT_OBJ */ #include "client/ctdb_client.c" diff --git a/ctdb/wscript b/ctdb/wscript index f21b51dd2ea..6d310755f1d 100755 --- a/ctdb/wscript +++ b/ctdb/wscript @@ -401,7 +401,7 @@ def build(bld): ctdb_vacuum.c ctdb_banning.c ctdb_statistics.c ctdb_update_record.c - ctdb_lock.c ctdb_fork.c'''), + ctdb_lock.c ctdb_fork.c killtcp.c'''), includes='include', deps='ctdb-ipalloc replace popt talloc tevent tdb talloc_report')