weather_forecast: corrected variables.xml formatting
[monky] / src / libtcp-portmon.c
1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2  *
3  * libtcp-portmon.c:  tcp port monitoring library.
4  *
5  * Copyright (C) 2005-2007 Philip Kovacs pkovacs@users.sourceforge.net
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA.
21  *
22  * vim: ts=4 sw=4 noet ai cindent syntax=c
23  *
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include "libtcp-portmon.h"
31 #include <glib/gprintf.h>
32
33 /* -------------------------------------------------------------------
34  * IMPLEMENTATION INTERFACE
35  *
36  * Implementation-specific interface begins here.  Clients should not
37  * manipulate these structures directly, nor call the defined helper
38  * functions.  Use the "Client interface" functions defined at bottom.
39  * ------------------------------------------------------------------- */
40
41 /* -----------------------------------
42  * Copy a tcp_connection_t
43  *
44  * Returns 0 on success, -1 otherwise.
45  * ----------------------------------- */
46 int copy_tcp_connection(tcp_connection_t *p_dest_connection,
47                 const tcp_connection_t *p_source_connection)
48 {
49         if (!p_dest_connection || !p_source_connection) {
50                 return -1;
51         }
52
53         g_strlcpy(p_dest_connection->key, p_source_connection->key,
54                 sizeof(p_dest_connection->key));
55         p_dest_connection->local_addr = p_source_connection->local_addr;
56         p_dest_connection->local_port = p_source_connection->local_port;
57         p_dest_connection->remote_addr = p_source_connection->remote_addr;
58         p_dest_connection->remote_port = p_source_connection->remote_port;
59         p_dest_connection->age = p_source_connection->age;
60
61         return 0;
62 }
63
64 /* -------------------------------------------
65  * Port monitor utility functions implementing
66  * tcp_port_monitor_function_ptr_t
67  * ------------------------------------------- */
68 void destroy_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
69 {
70         tcp_connection_node_t *p_node, *p_temp;
71
72         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
73                 return;
74         }
75
76         /* destroy the monitor's peek array */
77         free(p_monitor->p_peek);
78
79         /* destroy the monitor's connection list */
80         for (p_node = p_monitor->connection_list.p_head; p_node != NULL; ) {
81                 /* p_temp is for the next iteration */
82                 p_temp = p_node->p_next;
83
84                 free(p_node);
85
86                 p_node = p_temp;
87         }
88
89         /* destroy the monitor's hash */
90         g_hash_table_destroy(p_monitor->hash);
91         p_monitor->hash = NULL;
92
93         /* destroy the monitor */
94         free(p_monitor);
95         p_monitor = NULL;
96 }
97
98 void age_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
99 {
100         /* Run through the monitor's connections and decrement the age variable.
101          * If the age goes negative, we remove the connection from the monitor.
102          * Function takes O(n) time on the number of connections. */
103
104         tcp_connection_node_t *p_node, *p_temp;
105         tcp_connection_t *p_conn;
106
107         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
108                 return;
109         }
110
111         if (!p_monitor->p_peek) {
112                 return;
113         }
114
115         for (p_node = p_monitor->connection_list.p_head; p_node; ) {
116                 if (--p_node->connection.age >= 0) {
117                         p_node = p_node->p_next;
118                         continue;
119                 }
120
121                 /* connection on p_node is old.  remove connection from the hash. */
122                 p_conn = &p_node->connection;
123 #ifdef HASH_DEBUG
124                 fprintf(stderr, "monitor hash removal of connection [%s]", p_conn->key);
125                 if (!g_hash_table_remove(p_monitor->hash,
126                                 (gconstpointer) p_conn->key)) {
127                         fprintf(stderr, " - ERROR NOT FOUND\n");
128                         return;
129                 }
130                 fprintf(stderr, " - OK\n");
131 #else
132                 if (!g_hash_table_remove(p_monitor->hash,
133                                 (gconstpointer) p_conn->key)) {
134                         return;
135                 }
136 #endif
137
138                 /* splice p_node out of the connection_list */
139                 if (p_node->p_prev != NULL) {
140                         p_node->p_prev->p_next = p_node->p_next;
141                 }
142                 if (p_node->p_next != NULL) {
143                         p_node->p_next->p_prev = p_node->p_prev;
144                 }
145
146                 /* correct the list head and tail if necessary */
147                 if (p_monitor->connection_list.p_head == p_node) {
148                         p_monitor->connection_list.p_head = p_node->p_next;
149                 }
150                 if (p_monitor->connection_list.p_tail == p_node) {
151                         p_monitor->connection_list.p_tail = p_node->p_prev;
152                 }
153
154                 /* p_temp is for the next iteration */
155                 p_temp = p_node->p_next;
156
157                 /* destroy the node */
158                 free(p_node);
159
160                 p_node = p_temp;
161         }
162 }
163
164 void rebuild_tcp_port_monitor_peek_table(tcp_port_monitor_t *p_monitor,
165                 void *p_void)
166 {
167         /* Run through the monitor's connections and rebuild the peek table of
168          * connection pointers.  This is done so peeking into the monitor can be
169          * done in O(1) time instead of O(n) time for each peek. */
170
171         tcp_connection_node_t *p_node;
172         int i = 0;
173
174         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
175                 return;
176         }
177
178         /* zero out the peek array */
179         memset(p_monitor->p_peek, 0, p_monitor->max_port_monitor_connections *
180                 sizeof(tcp_connection_t *));
181
182         for (p_node = p_monitor->connection_list.p_head; p_node != NULL;
183                         p_node = p_node->p_next, i++) {
184                 p_monitor->p_peek[i] = &p_node->connection;
185         }
186 }
187
188 void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
189                 void *p_void)
190 {
191         /* The monitor gets to look at each connection to see if it falls within
192          * the monitor's port range of interest.  Connections of interest are first
193          * looked up in the hash to see if they are already there.  If they are, we
194          * reset the age of the connection so it is not deleted.  If the connection
195          * is not in the hash, we add it, but only if we haven't exceeded the
196          * maximum connection limit for the monitor.
197          * The function takes O(1) time. */
198
199         tcp_connection_node_t *p_node;
200         tcp_connection_t *p_connection, *p_conn_hash;
201
202         if (!p_monitor || !p_void) {
203                 return;
204         }
205
206         /* This p_connection is on caller's stack and not the heap.
207          * If we are interested, we will create a copy of the connection
208          * (on the heap) and add it to our list. */
209         p_connection = (tcp_connection_t *) p_void;
210
211         /* inspect the local port number of the connection to see if we're
212          * interested. */
213         if ((p_monitor->port_range_begin <= p_connection->local_port)
214                         && (p_connection->local_port <= p_monitor->port_range_end)) {
215                 /* the connection is in the range of the monitor. */
216
217                 /* first check the hash to see if the connection is already there. */
218                 if ((p_conn_hash = g_hash_table_lookup(p_monitor->hash,
219                                 (gconstpointer) p_connection->key))) {
220                         /* it's already in the hash.  reset the age of the connection. */
221                         p_conn_hash->age = TCP_CONNECTION_STARTING_AGE;
222
223                         return;
224                 }
225
226                 /* Connection is not yet in the hash.
227                  * Add it if max_connections not exceeded. */
228                 if (g_hash_table_size(p_monitor->hash)
229                                 >= p_monitor->max_port_monitor_connections) {
230                         return;
231                 }
232
233                 /* create a new connection node */
234                 if ((p_node = (tcp_connection_node_t *)
235                                 calloc(1, sizeof(tcp_connection_node_t))) == NULL) {
236                         return;
237                 }
238
239                 /* copy the connection data */
240                 if (copy_tcp_connection(&p_node->connection, p_connection) != 0) {
241                         /* error copying the connection data. deallocate p_node to
242                          * avoid leaks and return. */
243                         free(p_node);
244                         return;
245                 }
246
247                 p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
248                 p_node->p_next = NULL;
249
250                 /* insert it into the monitor's hash table */
251 #ifdef HASH_DEBUG
252                 fprintf(stderr, "monitor hash insert of connection [%s]\n",
253                         p_node->connection.key);
254 #endif
255                 g_hash_table_insert(p_monitor->hash,
256                         (gpointer) p_node->connection.key, (gpointer) &p_node->connection);
257
258                 /* append the node to the monitor's connection list */
259                 if (p_monitor->connection_list.p_tail == NULL) {
260                         /* assume p_head is NULL too */
261                         p_monitor->connection_list.p_head = p_node;
262                         p_monitor->connection_list.p_tail = p_node;
263                         p_node->p_prev = NULL;
264                 } else {
265                         p_monitor->connection_list.p_tail->p_next = p_node;
266                         p_node->p_prev = p_monitor->connection_list.p_tail;
267                         p_monitor->connection_list.p_tail = p_node;
268                 }
269         }
270 }
271
272 /* ------------------------------------------------------------------------
273  * Apply a tcp_port_monitor_function_ptr_t function to each port monitor in
274  * the collection.
275  * ------------------------------------------------------------------------ */
276 void for_each_tcp_port_monitor_in_collection(
277                 tcp_port_monitor_collection_t *p_collection,
278                 tcp_port_monitor_function_ptr_t p_function, void *p_function_args)
279 {
280         tcp_port_monitor_node_t *p_current_node, *p_next_node;
281
282         if (!p_collection || !p_function) {
283                 return;
284         }
285
286         /* for each monitor in the collection */
287         for (p_current_node = p_collection->monitor_list.p_head;
288                         p_current_node != NULL; p_current_node = p_next_node) {
289                 p_next_node = p_current_node->p_next;   /* do this first! */
290
291                 if (p_current_node->p_monitor) {
292                         /* apply the function with the given arguments */
293                         p_function(p_current_node->p_monitor, p_function_args);
294                 }
295         }
296 }
297
298 /* ----------------------------------------------------------------------
299  * CLIENT INTERFACE
300  *
301  * Clients should call only those functions below this line.
302  * ---------------------------------------------------------------------- */
303
304 /* ----------------------------------
305  * Client operations on port monitors
306  * ---------------------------------- */
307
308 /* Clients should first try to "find_tcp_port_monitor" before creating one
309  * so that there are no redundant monitors. */
310 tcp_port_monitor_t *create_tcp_port_monitor(in_port_t port_range_begin,
311                 in_port_t port_range_end, tcp_port_monitor_args_t *p_creation_args)
312 {
313         tcp_port_monitor_t *p_monitor;
314
315         /* create the monitor */
316         p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t));
317         if (!p_monitor) {
318                 return NULL;
319         }
320
321         p_monitor->max_port_monitor_connections =
322                 p_creation_args->max_port_monitor_connections;
323
324         /* build the monitor key for the collection hash */
325         g_sprintf(p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
326
327         /* create the monitor's connection hash */
328         if ((p_monitor->hash = g_hash_table_new(g_str_hash, g_str_equal)) == NULL) {
329                 /* we failed to create the hash, so destroy the monitor completely
330                  * so we don't leak */
331                 destroy_tcp_port_monitor(p_monitor, NULL);
332                 return NULL;
333         }
334
335         /* create the monitor's peek array */
336         if ((p_monitor->p_peek = (tcp_connection_t **)
337                         calloc(p_monitor->max_port_monitor_connections,
338                         sizeof(tcp_connection_t *))) == NULL) {
339                 /* we failed to create the peek array,
340                  * so destroy the monitor completely, again, so we don't leak */
341                 destroy_tcp_port_monitor(p_monitor, NULL);
342                 return NULL;
343         }
344
345         p_monitor->port_range_begin = port_range_begin;
346         p_monitor->port_range_end = port_range_end;
347
348         p_monitor->connection_list.p_head = NULL;
349         p_monitor->connection_list.p_tail = NULL;
350
351         return p_monitor;
352 }
353
354 /* Clients use this function to get connection data from the indicated
355  * port monitor.
356  * The requested monitor value is copied into a client-supplied char buffer.
357  * Returns 0 on success, -1 otherwise. */
358 int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
359                 int connection_index, char *p_buffer, size_t buffer_size)
360 {
361         struct in_addr net;
362         struct sockaddr_in sa;
363
364         sa.sin_family = AF_INET;
365         
366         if (!p_monitor || !p_buffer || connection_index < 0) {
367                 return -1;
368         }
369
370         memset(p_buffer, 0, buffer_size);
371         memset(&net, 0, sizeof(net));
372
373         /* if the connection index is out of range, we simply return with no error,
374          * having first cleared the client-supplied buffer. */
375         if ((item != COUNT) && (connection_index
376                         > (int) g_hash_table_size(p_monitor->hash) - 1)) {
377                 return 0;
378         }
379
380         switch (item) {
381
382                 case COUNT:
383
384                         snprintf(p_buffer, buffer_size, "%d",
385                                 g_hash_table_size(p_monitor->hash));
386                         break;
387
388                 case REMOTEIP:
389
390                         net.s_addr = p_monitor->p_peek[connection_index]->remote_addr;
391                         snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
392                         break;
393
394                 case REMOTEHOST:
395
396                         memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->remote_addr, sizeof(sa.sin_addr.s_addr));
397                         getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
398                         break;
399
400                 case REMOTEPORT:
401
402                         snprintf(p_buffer, buffer_size, "%d",
403                                 p_monitor->p_peek[connection_index]->remote_port);
404                         break;
405
406                 case REMOTESERVICE:
407
408                         sa.sin_port=htons(p_monitor->p_peek[connection_index]->remote_port);
409                         getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
410                         break;
411
412                 case LOCALIP:
413
414                         net.s_addr = p_monitor->p_peek[connection_index]->local_addr;
415                         snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
416                         break;
417
418                 case LOCALHOST:
419
420                         memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->local_addr, sizeof(sa.sin_addr.s_addr));
421                         getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
422                         break;
423
424                 case LOCALPORT:
425
426                         snprintf(p_buffer, buffer_size, "%d",
427                                 p_monitor->p_peek[connection_index]->local_port);
428                         break;
429
430                 case LOCALSERVICE:
431
432                         sa.sin_port=htons(p_monitor->p_peek[connection_index]->local_port);
433                         getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
434                         break;
435
436                 default:
437                         return -1;
438         }
439
440         return 0;
441 }
442
443 /* --------------------------------
444  * Client operations on collections
445  * -------------------------------- */
446
447 /* Create a monitor collection.  Do this one first. */
448 tcp_port_monitor_collection_t *create_tcp_port_monitor_collection(void)
449 {
450         tcp_port_monitor_collection_t *p_collection;
451
452         p_collection = (tcp_port_monitor_collection_t *)
453                 calloc(1, sizeof(tcp_port_monitor_collection_t));
454         if (!p_collection) {
455                 return NULL;
456         }
457
458         /* create the collection's monitor hash */
459         if ((p_collection->hash = g_hash_table_new(g_str_hash, g_str_equal))
460                         == NULL) {
461                 /* we failed to create the hash,
462                  * so destroy the monitor completely so we don't leak */
463                 destroy_tcp_port_monitor_collection(p_collection);
464                 return NULL;
465         }
466
467         p_collection->monitor_list.p_head = NULL;
468         p_collection->monitor_list.p_tail = NULL;
469
470         return p_collection;
471 }
472
473 /* Destroy the monitor collection (and the monitors inside).
474  * Do this one last. */
475 void destroy_tcp_port_monitor_collection(
476                 tcp_port_monitor_collection_t *p_collection)
477 {
478         tcp_port_monitor_node_t *p_current_node, *p_next_node;
479
480         if (!p_collection) {
481                 return;
482         }
483
484         /* destroy the monitors */
485         for_each_tcp_port_monitor_in_collection(p_collection,
486                 &destroy_tcp_port_monitor, NULL);
487
488         /* next destroy the empty monitor nodes */
489         for (p_current_node = p_collection->monitor_list.p_head;
490                         p_current_node != NULL; p_current_node = p_next_node) {
491                 p_next_node = p_current_node->p_next;   /* do this first! */
492
493                 free(p_current_node);
494         }
495
496         /* destroy the collection's hash */
497         g_hash_table_destroy(p_collection->hash);
498         p_collection->hash = NULL;
499
500         free(p_collection);
501         p_collection = NULL;
502 }
503
504 /* Updates the tcp statistics for all monitors within a collection */
505 void update_tcp_port_monitor_collection(
506                 tcp_port_monitor_collection_t *p_collection)
507 {
508         FILE *fp;
509         char buf[256];
510         tcp_connection_t conn;
511         unsigned long inode, uid, state;
512
513         if (!p_collection) {
514                 return;
515         }
516
517         /* age the connections in all port monitors. */
518         for_each_tcp_port_monitor_in_collection(p_collection,
519                 &age_tcp_port_monitor, NULL);
520
521         /* read tcp data from /proc/net/tcp */
522         if ((fp = fopen("/proc/net/tcp", "r")) == NULL) {
523                 return;
524         }
525
526         /* ignore field name line */
527         fgets(buf, 255, fp);
528
529         /* read all tcp connections */
530         while (fgets(buf, sizeof(buf), fp) != NULL) {
531
532                 if (sscanf(buf,
533                                 "%*d: %x:%hx %x:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
534                                 (unsigned int *) &conn.local_addr, &conn.local_port,
535                                 (unsigned int *) &conn.remote_addr, &conn.remote_port,
536                                 (unsigned long *) &state, (unsigned long *) &uid,
537                                 (unsigned long *) &inode) != 7) {
538                         fprintf(stderr, "/proc/net/tcp: bad file format\n");
539                 }
540                 /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
541                 //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
542                 if((inode == 0) || (state != 1)) {
543                         continue;
544                 }
545
546                 /* build hash key */
547                 g_sprintf(conn.key, "%08X:%04X %08X:%04X", conn.local_addr,
548                         conn.local_port, conn.remote_addr, conn.remote_port);
549
550                 /* show the connection to each port monitor. */
551                 for_each_tcp_port_monitor_in_collection(p_collection,
552                         &show_connection_to_tcp_port_monitor, (void *) &conn);
553         }
554
555         fclose(fp);
556
557         /* rebuild the connection peek tables of all monitors
558          * so clients can peek in O(1) time */
559         for_each_tcp_port_monitor_in_collection(p_collection,
560                 &rebuild_tcp_port_monitor_peek_table, NULL);
561 }
562
563 /* After clients create a monitor, use this to add it to the collection.
564  * Returns 0 on success, -1 otherwise. */
565 int insert_tcp_port_monitor_into_collection(
566                 tcp_port_monitor_collection_t *p_collection,
567                 tcp_port_monitor_t *p_monitor)
568 {
569         tcp_port_monitor_node_t *p_node;
570
571         if (!p_collection || !p_monitor) {
572                 return -1;
573         }
574
575         /* create a container node for this monitor */
576         p_node = (tcp_port_monitor_node_t *)
577                 calloc(1, sizeof(tcp_port_monitor_node_t));
578         if (!p_node) {
579                 return -1;
580         }
581
582         /* populate the node */
583         p_node->p_monitor = p_monitor;
584         p_node->p_next = NULL;
585
586         /* add a pointer to this monitor to the collection's hash */
587 #ifdef HASH_DEBUG
588         fprintf(stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
589 #endif
590         g_hash_table_insert(p_collection->hash, (gpointer) p_monitor->key,
591                 (gpointer) p_monitor);
592
593         /* tail of the container gets this node */
594         if (!p_collection->monitor_list.p_tail) {
595                 p_collection->monitor_list.p_tail = p_node;
596         } else {
597                 /* p_next of the tail better be NULL */
598                 if (p_collection->monitor_list.p_tail->p_next != NULL) {
599                         return -1;
600                 }
601
602                 /* splice node onto tail */
603                 p_collection->monitor_list.p_tail->p_next = p_node;
604                 p_collection->monitor_list.p_tail = p_node;
605         }
606
607         /* if this was the first element added */
608         if (!p_collection->monitor_list.p_head) {
609                 p_collection->monitor_list.p_head = p_collection->monitor_list.p_tail;
610         }
611
612         return 0;
613 }
614
615 /* Clients need a way to find monitors */
616 tcp_port_monitor_t *find_tcp_port_monitor(
617                 const tcp_port_monitor_collection_t *p_collection,
618                 in_port_t port_range_begin, in_port_t port_range_end)
619 {
620         tcp_port_monitor_t *p_monitor;
621         gchar key[12];
622
623         if (!p_collection) {
624                 return NULL;
625         }
626
627         /* is monitor in hash? */
628         g_sprintf(key, ":%04X :%04X", port_range_begin, port_range_end);
629         p_monitor = g_hash_table_lookup(p_collection->hash, (gconstpointer) key);
630         return p_monitor;
631 }