/* CTDB TCP connection killing utility 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 #include #include "replace.h" #include "system/network.h" #include "lib/util/debug.h" #include "protocol/protocol.h" #include "protocol/protocol_api.h" #include "common/rb_tree.h" #include "common/system.h" #include "common/logging.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; }; static const char *prog; /* 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 connection has been tickled! RST it and remove it * from the list. */ DEBUG(DEBUG_INFO, ("Sending a TCP RST to kill connection (%s:%d) -> %s:%d\n", ctdb_sock_addr_to_string(con, &con->src_addr), ntohs(con->src_addr.ip.sin_port), ctdb_sock_addr_to_string(con, &con->dst_addr), ntohs(con->dst_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++; ctdb_sys_send_tcp(&con->dst_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, 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); } return 0; } 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 [ ]\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; const char *t; enum debug_level debug_level; bool done; int num = 0; int i, ret; /* Set the debug level */ t = getenv("CTDB_DEBUGLEVEL"); if (t != NULL) { if (debug_level_parse(t, &debug_level)) { DEBUGLEVEL = debug_level_to_int(debug_level); } } 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); /* Do the initial processing of connections */ tevent_add_timer(ev, killtcp, tevent_timeval_current_ofs(0, 0), ctdb_tickle_sentenced_connections, killtcp); while (!done) { tevent_loop_once(ev); } talloc_free(mem_ctx); return 0; fail: TALLOC_FREE(mem_ctx); return -1; }