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