e88ef210f5bfd52ee51acea1a156ef00cb65935f
[samba.git] / ctdb / tools / ctdb_killtcp.c
1 /*
2    CTDB TCP connection killing utility
3
4    Copyright (C) Martin Schwenke <martin@meltin.net> 2016
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <talloc.h>
21 #include <tevent.h>
22
23 #include "replace.h"
24 #include "system/network.h"
25
26 #include "lib/util/debug.h"
27
28 #include "protocol/protocol.h"
29 #include "protocol/protocol_util.h"
30
31 #include "common/db_hash.h"
32 #include "common/system.h"
33 #include "common/logging.h"
34
35
36 /* Contains the listening socket and the list of TCP connections to
37  * kill */
38 struct ctdb_kill_tcp {
39         int capture_fd;
40         struct tevent_fd *fde;
41         struct db_hash_context *connections;
42         void *private_data;
43         void *destructor_data;
44         unsigned int attempts;
45         unsigned int max_attempts;
46         struct timeval retry_interval;
47         unsigned int batch_count;
48         unsigned int batch_size;
49 };
50
51
52 /*
53   called when we get a read event on the raw socket
54  */
55 static void capture_tcp_handler(struct tevent_context *ev,
56                                 struct tevent_fd *fde,
57                                 uint16_t flags, void *private_data)
58 {
59         struct ctdb_kill_tcp *killtcp = talloc_get_type(private_data, struct ctdb_kill_tcp);
60         /* 0 the parts that don't get set by ctdb_sys_read_tcp_packet */
61         struct ctdb_connection conn;
62         uint32_t ack_seq, seq;
63         int rst;
64         uint16_t window;
65         int ret;
66
67         if (ctdb_sys_read_tcp_packet(killtcp->capture_fd,
68                                      killtcp->private_data,
69                                      &conn.server, &conn.client,
70                                      &ack_seq, &seq, &rst, &window) != 0) {
71                 /* probably a non-tcp ACK packet */
72                 return;
73         }
74
75         if (window == htons(1234) && (rst || seq == 0)) {
76                 /* Ignore packets that we sent! */
77                 D_DEBUG("Ignoring packet: %s, "
78                         "seq=%"PRIu32", ack_seq=%"PRIu32", "
79                         "rst=%d, window=%"PRIu16"\n",
80                         ctdb_connection_to_string(killtcp, &conn, false),
81                         seq, ack_seq, rst, ntohs(window));
82                 return;
83         }
84
85         /* Check if this connection is one being reset, if found then delete */
86         ret = db_hash_delete(killtcp->connections,
87                              (uint8_t*)&conn, sizeof(conn));
88         if (ret == ENOENT) {
89                 /* Packet for some other connection, ignore */
90                 return;
91         }
92         if (ret != 0) {
93                 DBG_WARNING("Internal error (%s)\n", strerror(ret));
94                 return;
95         }
96
97         D_INFO("Sending a TCP RST to kill connection %s\n",
98                ctdb_connection_to_string(killtcp, &conn, true));
99
100         ctdb_sys_send_tcp(&conn.server, &conn.client, ack_seq, seq, 1);
101 }
102
103
104 static int tickle_connection_parser(uint8_t *keybuf, size_t keylen,
105                                     uint8_t *databuf, size_t datalen,
106                                     void *private_data)
107 {
108         struct ctdb_kill_tcp *killtcp = talloc_get_type_abort(
109                 private_data, struct ctdb_kill_tcp);
110         struct ctdb_connection *conn;
111
112         if (keylen != sizeof(*conn)) {
113                 DBG_WARNING("Unexpected data in connection hash\n");
114                 return 0;
115         }
116
117         conn = (struct ctdb_connection *)keybuf;
118
119         killtcp->batch_count++;
120         if (killtcp->batch_count > killtcp->batch_size) {
121                 /* Terminate the traverse */
122                 return 1;
123         }
124
125         ctdb_sys_send_tcp(&conn->server, &conn->client, 0, 0, 0);
126
127         return 0;
128 }
129
130 /*
131  * Called periodically until all sentenced connections have been reset
132  * or enough attempts have been made
133  */
134 static void ctdb_tickle_sentenced_connections(struct tevent_context *ev,
135                                               struct tevent_timer *te,
136                                               struct timeval t, void *private_data)
137 {
138         struct ctdb_kill_tcp *killtcp = talloc_get_type(private_data, struct ctdb_kill_tcp);
139         int count, ret;
140
141         /* loop over up to batch_size connections sending tickle ACKs */
142         killtcp->batch_count = 0;
143         ret = db_hash_traverse(killtcp->connections,
144                                tickle_connection_parser, killtcp, NULL);
145         if (ret != 0) {
146                 DBG_WARNING("Unexpected error traversing connections (%s)\n",
147                             strerror(ret));
148         }
149
150         killtcp->attempts++;
151
152         /*
153          * If there are no more connections to kill or we have tried
154          * too many times we can remove the entire killtcp structure
155          */
156         ret = db_hash_traverse(killtcp->connections, NULL, NULL, &count);
157         if (ret != 0) {
158                 /* What now?  Try again until max_attempts reached */
159                 DBG_WARNING("Unexpected error traversing connections (%s)\n",
160                             strerror(ret));
161                 count = 1;
162         }
163         if (count == 0 ||
164             killtcp->attempts >= killtcp->max_attempts) {
165                 talloc_free(killtcp);
166                 return;
167         }
168
169         /* try tickling them again in a seconds time
170          */
171         tevent_add_timer(ev, killtcp,
172                          tevent_timeval_current_ofs(
173                                  killtcp->retry_interval.tv_sec,
174                                  killtcp->retry_interval.tv_usec),
175                          ctdb_tickle_sentenced_connections, killtcp);
176 }
177
178 /* Add a TCP socket to the list of connections we want to RST.  The
179  * list is attached to *killtcp_arg.  If this is NULL then allocate
180  * the structure.  */
181 static int ctdb_killtcp(struct tevent_context *ev,
182                         TALLOC_CTX *mem_ctx,
183                         const char *iface,
184                         struct ctdb_connection *conn,
185                         struct ctdb_kill_tcp **killtcp_arg)
186 {
187         struct ctdb_kill_tcp *killtcp;
188         int ret;
189
190         if (killtcp_arg == NULL) {
191                 DEBUG(DEBUG_ERR, (__location__ " killtcp_arg is NULL!\n"));
192                 return -1;
193         }
194
195         killtcp = *killtcp_arg;
196
197         /* Allocate a new structure if necessary.  The structure is
198          * only freed when mem_ctx is freed. */
199         if (killtcp == NULL) {
200                 killtcp = talloc_zero(mem_ctx, struct ctdb_kill_tcp);
201                 if (killtcp == NULL) {
202                         DEBUG(DEBUG_ERR, (__location__ " out of memory\n"));
203                         return -1;
204                 }
205
206                 killtcp->capture_fd  = -1;
207                 ret = db_hash_init(killtcp, "connections", 2048, DB_HASH_SIMPLE,
208                                    &killtcp->connections);
209                 if (ret != 0) {
210                         D_ERR("Failed to initialise connection hash (%s)\n",
211                               strerror(ret));
212                         talloc_free(killtcp);
213                         return -1;
214                 }
215
216                 killtcp->attempts = 0;
217                 killtcp->max_attempts = 50;
218
219                 killtcp->retry_interval.tv_sec = 0;
220                 killtcp->retry_interval.tv_usec = 100 * 1000;
221
222                 killtcp->batch_count = 0;
223                 killtcp->batch_size = 300;
224
225                 *killtcp_arg = killtcp;
226         }
227
228         /* Connection is stored as a key in the connections hash */
229         ret = db_hash_add(killtcp->connections,
230                           (uint8_t *)conn, sizeof(*conn),
231                           NULL, 0);
232         if (ret != 0) {
233                 D_ERR("Error adding connection to hash (%s)\n", strerror(ret));
234                 return -1;
235         }
236
237         /*
238            If we don't have a socket to listen on yet we must create it
239          */
240         if (killtcp->capture_fd == -1) {
241                 killtcp->capture_fd =
242                         ctdb_sys_open_capture_socket(iface,
243                                                      &killtcp->private_data);
244                 if (killtcp->capture_fd == -1) {
245                         DEBUG(DEBUG_CRIT,(__location__ " Failed to open capturing "
246                                           "socket on iface '%s' for killtcp (%s)\n",
247                                           iface, strerror(errno)));
248                         return -1;
249                 }
250         }
251
252
253         if (killtcp->fde == NULL) {
254                 killtcp->fde = tevent_add_fd(ev, killtcp,
255                                              killtcp->capture_fd,
256                                              TEVENT_FD_READ,
257                                              capture_tcp_handler, killtcp);
258                 tevent_fd_set_auto_close(killtcp->fde);
259         }
260
261         return 0;
262 }
263
264 static int ctdb_killtcp_destructor(struct ctdb_kill_tcp *killtcp)
265 {
266         bool *done = killtcp->destructor_data;
267         *done = true;
268
269         return 0;
270 }
271
272 static void usage(const char *prog)
273 {
274         printf("usage: %s <interface> [ <srcip:port> <dstip:port> ]\n", prog);
275         exit(1);
276 }
277
278 int main(int argc, char **argv)
279 {
280         struct ctdb_connection conn;
281         struct ctdb_kill_tcp *killtcp = NULL;
282         struct tevent_context *ev = NULL;
283         struct TALLOC_CONTEXT *mem_ctx = NULL;
284         struct ctdb_connection_list *conn_list = NULL;
285         const char *t;
286         int debug_level;
287         bool done;
288         int i, ret;
289
290         /* Set the debug level */
291         t = getenv("CTDB_DEBUGLEVEL");
292         if (t != NULL) {
293                 if (debug_level_parse(t, &debug_level)) {
294                         DEBUGLEVEL = debug_level;
295                 } else {
296                         DEBUGLEVEL = DEBUG_ERR;
297                 }
298         }
299
300         if (argc != 2 && argc != 4) {
301                 usage(argv[0]);
302         }
303
304         if (argc == 4) {
305                 ret = ctdb_sock_addr_from_string(argv[2], &conn.client, true);
306                 if (ret != 0) {
307                         D_ERR("Bad IP:port '%s'\n", argv[2]);
308                         goto fail;
309                 }
310
311                 ret = ctdb_sock_addr_from_string(argv[3], &conn.server, true);
312                 if (ret != 0) {
313                         D_ERR("Bad IP:port '%s'\n", argv[3]);
314                         goto fail;
315                 }
316
317
318                 conn_list = talloc_zero(mem_ctx, struct ctdb_connection_list);
319                 if (conn_list == NULL) {
320                         ret = ENOMEM;
321                         DBG_ERR("Internal error (%s)\n", strerror(ret));
322                         goto fail;
323                 }
324                 ret = ctdb_connection_list_add(conn_list, &conn);
325                 if (ret != 0) {
326                         DBG_ERR("Internal error (%s)\n", strerror(ret));
327                         goto fail;
328                 }
329         } else {
330                 ret = ctdb_connection_list_read(mem_ctx, true, &conn_list);
331                 if (ret != 0) {
332                         D_ERR("Unable to parse connections (%s)\n",
333                               strerror(ret));
334                         goto fail;
335                 }
336         }
337
338         mem_ctx = talloc_new(NULL);
339         if (mem_ctx == NULL) {
340                 DEBUG(DEBUG_ERR, (__location__ " out of memory\n"));
341                 goto fail;
342         }
343
344         ev = tevent_context_init(mem_ctx);
345         if (ev == NULL) {
346                 DEBUG(DEBUG_ERR, ("Failed to initialise tevent\n"));
347                 goto fail;
348         }
349
350         if (conn_list->num == 0) {
351                 /* No connections, done! */
352                 talloc_free(mem_ctx);
353                 return 0;
354         }
355
356         for (i = 0; i < conn_list->num; i++) {
357                 ret = ctdb_killtcp(ev, mem_ctx, argv[1],
358                                    &conn_list->conn[i], &killtcp);
359                 if (ret != 0) {
360                         DEBUG(DEBUG_ERR, ("Unable to killtcp\n"));
361                         goto fail;
362                 }
363         }
364
365         done = false;
366         killtcp->destructor_data = &done;
367         talloc_set_destructor(killtcp, ctdb_killtcp_destructor);
368
369         /* Do the initial processing of connections */
370         tevent_add_timer(ev, killtcp,
371                          tevent_timeval_current_ofs(0, 0),
372                          ctdb_tickle_sentenced_connections, killtcp);
373
374         while (!done) {
375                 tevent_loop_once(ev);
376         }
377
378         talloc_free(mem_ctx);
379
380         return 0;
381
382 fail:
383         TALLOC_FREE(mem_ctx);
384         return -1;
385 }