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