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