typo
[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 "libtcp-portmon.h"
24
25 /* -------------------------------------------------------------------
26  * IMPLEMENTATION INTERFACE
27  *
28  * Implementation-specific interface begins here.  Clients should not
29  * manipulate these structures directly, nor call the defined helper
30  * functions.  Use the "Client interface" functions defined at bottom.
31  * ------------------------------------------------------------------- */
32
33 /* -----------------------------------------------------------------------------
34  * Open-addressed hash implementation requires that we supply two hash functions
35  * and a match function to compare two hash elements for identity.
36  * ----------------------------------------------------------------------------- */
37
38 /* --------------------------------------------------
39  * Functions to hash the connections within a monitor
40  * --------------------------------------------------*/
41
42 #define CONNECTION_HASH_KEY_LEN 17
43
44 /* ----------------------------------------------------------------------------------
45  * First connection hash function: DJB with a 65521 prime modulus to govern the range. 
46  * ----------------------------------------------------------------------------------*/
47 int connection_hash_function_1( const void *p_data )
48 {
49    tcp_connection_t *p_conn;
50    char key[CONNECTION_HASH_KEY_LEN];
51    unsigned int hash = 5381;
52    unsigned int i    = 0;
53
54    if ( !p_data )
55            return -1;
56    
57    memset(key,0,sizeof(key));
58
59    /* p_data is a pointer to tcp_connection_t */
60    p_conn = (tcp_connection_t *)p_data; 
61
62    /* key is a hex representation of the connection */
63    snprintf(key, CONNECTION_HASH_KEY_LEN, "%08X%04X%04X", 
64                    p_conn->remote_addr, p_conn->remote_port, p_conn->local_port);
65 #ifdef HASH_DEBUG
66    fprintf(stderr,"--- key=[%s]\n",key);
67 #endif
68
69    for(i = 0; i < CONNECTION_HASH_KEY_LEN-1; i++)
70    {
71       hash = ((hash << 5) + hash) + (key[i]);
72    }
73
74    return (hash & 0x7FFFFFFF) % 65521;
75 }
76
77 /* -------------------------------------------------------------------------
78  * Second connection hash function: DEK, modified to return odd numbers only,
79  * as required for open-address hashing using double-hash probing.
80  * Also uses a 65521 prime modulus to govern the range. 
81  * -------------------------------------------------------------------------*/
82 int connection_hash_function_2( const void *p_data )
83 {
84    tcp_connection_t *p_conn;
85    char key[CONNECTION_HASH_KEY_LEN];
86    unsigned int hash = CONNECTION_HASH_KEY_LEN-1;
87    unsigned int i    = 0;
88
89    if ( !p_data )
90            return -1;
91
92    memset(key,0,sizeof(key));
93
94    /* p_data is a pointer to a tcp_connection_t */
95    p_conn = (tcp_connection_t *)p_data;
96
97    /* key is a hex representation of the connection */
98    snprintf(key, CONNECTION_HASH_KEY_LEN, "%08X%04X%04X", 
99                    p_conn->remote_addr, p_conn->remote_port, p_conn->local_port);
100
101    for(i = 0; i < CONNECTION_HASH_KEY_LEN-1; i++)
102    {
103       hash = ((hash << 5) ^ (hash >> 27)) ^ (key[i]);
104    }
105    return (( hash & 0x7FFFFFFF ) % 65521 ) | 0x00000001;
106 }
107
108 /* -------------------------------------------------------------------------
109  * Connection Match function returns non-zero if hash elements are identical. 
110  * -------------------------------------------------------------------------*/
111 int connection_match_function( const void *p_data1, const void *p_data2 )
112 {
113    tcp_connection_t *p_conn1, *p_conn2;
114    
115    if ( !p_data1 || !p_data2 )
116            return 0;
117
118    /* p_data1, p_data2 are pointers to tcp_connection_t */
119    p_conn1 = (tcp_connection_t *)p_data1;
120    p_conn2 = (tcp_connection_t *)p_data2;
121
122    return (p_conn1->local_addr == p_conn2->local_addr &&
123            p_conn1->local_port == p_conn2->local_port &&
124            p_conn1->remote_addr ==  p_conn2->remote_addr &&
125            p_conn1->remote_port == p_conn2->remote_port );
126 }
127
128 /* --------------------------------------------------
129  * Functions to hash the monitors within a collection
130  * --------------------------------------------------*/
131
132 #define MONITOR_HASH_KEY_LEN 9
133
134 /* -------------------------------------------------------------------------------
135  * First monitor hash function: DJB with a 65521 prime modulus to govern the range.
136  * -------------------------------------------------------------------------------*/
137 int monitor_hash_function_1( const void *p_data )
138 {
139    tcp_port_monitor_t *p_monitor;
140    char key[MONITOR_HASH_KEY_LEN];
141    unsigned int hash = 5381;
142    unsigned int i    = 0;
143
144    if ( !p_data )
145            return -1;
146    
147    memset(key,0,sizeof(key));
148
149    /* p_data is a pointer to tcp_port_monitor_t */
150    p_monitor = (tcp_port_monitor_t *)p_data;
151
152    /* key is a hex representation of the starting port concatenated to the ending port */
153    snprintf(key, MONITOR_HASH_KEY_LEN, "%04X%04X",
154                    p_monitor->port_range_begin, p_monitor->port_range_end );
155 #ifdef HASH_DEBUG
156    fprintf(stderr,"--- key=[%s]\n",key);
157 #endif
158
159    for(i = 0; i < MONITOR_HASH_KEY_LEN-1; i++)
160    {
161       hash = ((hash << 5) + hash) + (key[i]);
162    }
163
164    return (hash & 0x7FFFFFFF) % 65521;
165 }
166
167 /* -----------------------------------------------------------------------
168  * Second monitor hash function: DEK, modified to return odd numbers only,
169  * as required for open-address hashing using double-hash probing.
170  * Also uses a 65521 prime modulus to govern the range.
171  * -----------------------------------------------------------------------*/
172 int monitor_hash_function_2( const void *p_data )
173 {
174    tcp_port_monitor_t *p_monitor;
175    char key[MONITOR_HASH_KEY_LEN];
176    unsigned int hash = MONITOR_HASH_KEY_LEN-1;
177    unsigned int i    = 0;
178
179    if ( !p_data )
180            return -1;
181
182    memset(key,0,sizeof(key));
183
184    /* p_data is a pointer to a tcp_port_monitor_t */
185    p_monitor = (tcp_port_monitor_t *)p_data;
186
187    /* key is a hex representation of the starting port concatenated to the ending port */
188    snprintf(key, MONITOR_HASH_KEY_LEN, "%04X%04X",
189                    p_monitor->port_range_begin, p_monitor->port_range_end );
190
191    for(i = 0; i < MONITOR_HASH_KEY_LEN-1; i++)
192    {
193       hash = ((hash << 5) ^ (hash >> 27)) ^ (key[i]);
194    }
195    return (( hash & 0x7FFFFFFF ) % 65521 ) | 0x00000001;
196 }
197
198 /* ----------------------------------------------------------------------
199  * Monitor match function returns non-zero if hash elements are identical.
200  * ----------------------------------------------------------------------*/
201 int monitor_match_function( const void *p_data1, const void *p_data2 )
202 {
203    tcp_port_monitor_t *p_monitor1, *p_monitor2;
204
205    if ( !p_data1 || !p_data2 )
206            return 0;
207
208    /* p_data1, p_data2 are pointers to tcp_connection_t */
209    p_monitor1 = (tcp_port_monitor_t *)p_data1;
210    p_monitor2 = (tcp_port_monitor_t *)p_data2;
211
212    return (p_monitor1->port_range_begin == p_monitor1->port_range_begin &&
213            p_monitor2->port_range_end == p_monitor2->port_range_end);
214 }
215
216 /* ---------------------------------------------------------------------------
217  * Port monitor utility functions implementing tcp_port_monitor_function_ptr_t
218  * ---------------------------------------------------------------------------*/
219 void destroy_tcp_port_monitor(
220         tcp_port_monitor_t *                    p_monitor,
221         void *                                  p_void 
222         )
223 {
224    tcp_connection_node_t *p_node, *p_temp;
225
226    if ( !p_monitor || p_void )  /* p_void should be NULL in this context */
227       return;
228
229    /* destroy the monitor's hash */
230    hash_destroy(&p_monitor->hash);
231
232    /* destroy the monitor's peek array */
233    free( p_monitor->p_peek );
234
235    /* destroy the monitor's connection list */
236    for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; )
237    {
238            /* p_temp is for the next iteration */
239            p_temp = p_node->p_next;
240            
241            free( p_node );
242
243            p_node = p_temp;
244    }
245
246    /* destroy the monitor */
247    free( p_monitor );
248    p_monitor=NULL;
249 }
250
251 void age_tcp_port_monitor(
252         tcp_port_monitor_t *                    p_monitor,
253         void *                                  p_void
254         )
255 {
256    /* Run through the monitor's connections and decrement the age variable. 
257     * If the age goes negative, we remove the connection from the monitor. 
258     * Function takes O(n) time on the number of connections. */
259
260    tcp_connection_node_t *p_node, *p_temp;
261    tcp_connection_t *p_conn;
262    void *p_cast;
263
264    if ( !p_monitor || p_void )  /* p_void should be NULL in this context */
265            return;
266
267    if ( !p_monitor->p_peek )
268            return;
269
270    for ( p_node = p_monitor->connection_list.p_head; p_node != NULL; )
271    {
272            if ( --p_node->connection.age >= 0 ) {
273                    p_node = p_node->p_next;
274                    continue;
275            }
276
277            /* connection on p_node is old.  remove connection from the hash. */
278            p_conn = &p_node->connection;
279            p_cast = (void *)p_conn;
280            if ( hash_remove( &p_monitor->hash, &p_cast ) != 0 ) {
281 #ifdef HASH_DEBUG
282                    fprintf(stderr, "--- hash_remove error\n");
283 #endif
284                    return;
285            }
286
287            /* splice p_node out of the connection_list */
288            if ( p_node->p_prev != NULL )
289                    p_node->p_prev->p_next = p_node->p_next;
290            if ( p_node->p_next != NULL )
291                    p_node->p_next->p_prev = p_node->p_prev;
292
293            /* correct the list head and tail if necessary */
294            if ( p_monitor->connection_list.p_head == p_node )
295                    p_monitor->connection_list.p_head = p_node->p_next;
296            if ( p_monitor->connection_list.p_tail == p_node )
297                    p_monitor->connection_list.p_tail = p_node->p_prev;
298
299            /* p_temp is for the next iteration */
300            p_temp = p_node->p_next;
301
302            /* destroy the node */
303            free( p_node );
304
305            p_node = p_temp;
306    }
307 }
308
309 void maintain_tcp_port_monitor_hash(
310         tcp_port_monitor_t *                    p_monitor,
311         void *                                  p_void
312         )
313 {
314    /* Check the number of vacated slots in the hash.  If it exceeds our maximum
315     * threshold (should be about 1/4 of the hash table), then the hash table
316     * performance degrades from O(1) toward O(n) as the number of vacated slots
317     * climbs.  This is avoided by clearing the hash and reinserting the entries.
318     * The benefit of open-addressing hashing does come with this price --
319     * you must rebalance it occasionally. */
320
321     tcp_connection_node_t *p_node;
322     double vacated_load;
323
324     if ( !p_monitor || p_void )  /* p_void should be NULL in this context */
325         return;
326
327     vacated_load = (double)p_monitor->hash.vacated / (double)p_monitor->hash.positions;
328 #ifdef HASH_DEBUG
329     fprintf(stderr,"--- num vacated is %d, vacated factor is %.3f\n", p_monitor->hash.vacated, vacated_load );
330 #endif
331     if ( vacated_load <= TCP_CONNECIION_HASH_MAX_VACATED_RATIO )
332     {
333             /* hash is fine and needs no rebalancing */
334             return;
335     }
336
337 #ifdef HASH_DEBUG
338     fprintf(stderr,"--- rebuilding hash\n");
339 #endif
340
341     /* rebuild the hash  */
342     memset( p_monitor->hash.pp_table, 0, p_monitor->hash.positions * sizeof(void **));
343     p_monitor->hash.size = 0;
344     p_monitor->hash.vacated = 0;
345
346     for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; p_node=p_node->p_next )
347     {
348             if ( hash_insert( &p_monitor->hash, (void *)&p_node->connection ) != 0 )
349             {
350 #ifdef HASH_DEBUG
351                 fprintf(stderr,"--- hash_insert error\n");
352 #endif
353                 ;       
354             }
355     }
356 }
357
358 void rebuild_tcp_port_monitor_peek_table(
359         tcp_port_monitor_t *                    p_monitor,
360         void *                                  p_void
361         )
362 {
363    /* Run through the monitor's connections and rebuild the peek table
364     * of connection pointers.  This is done so peeking into the monitor
365     * can be done in O(1) time instead of O(n) time for each peek. */
366
367    tcp_connection_node_t *p_node;
368    int i = 0;
369
370    if ( !p_monitor || p_void )  /* p_void should be NULL in this context */
371         return;
372
373    /* zero out the peek array */
374    memset( p_monitor->p_peek, 0, p_monitor->hash.positions * sizeof(tcp_connection_t *) );
375
376    for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; p_node=p_node->p_next, i++ )
377    {
378            p_monitor->p_peek[i] = &p_node->connection;
379    }
380 }
381
382 void show_connection_to_tcp_port_monitor(
383         tcp_port_monitor_t *                    p_monitor,
384         void *                                  p_void
385         )
386 {
387    /* The monitor gets to look at each connection to see if it falls within
388     * the monitor's port range of interest.  Connections of interest are first
389     * looked up in the hash to see if they are already there.  If they are, we
390     * reset the age of the connection so it is not deleted.  If the connection 
391     * is not in the hash, we add it, but only if the hash is not saturated.  
392     * The function takes O(1) time. */
393
394    tcp_connection_node_t *p_node;
395    void *p_cast;
396
397    if ( !p_monitor || !p_void )
398         return;
399
400    tcp_connection_t *p_connection = (tcp_connection_t *)p_void;
401    
402    /* inspect the local port number of the connection to see if we're interested. */
403    if ( (p_monitor->port_range_begin <= p_connection->local_port) &&
404         (p_connection->local_port <= p_monitor->port_range_end) )
405    {
406         /* the connection is in the range of the monitor. */
407
408         /* first check the hash to see if the connection is already there. */
409         p_cast = (void *)p_connection;
410         if ( hash_lookup( &p_monitor->hash, &p_cast ) == 0 )
411         {
412                 p_connection = (tcp_connection_t *)p_cast;
413                 /* it's already in the hash.  reset the age of the connection. */
414                 if ( p_connection != NULL )
415                 {
416                         p_connection->age = TCP_CONNECIION_STARTING_AGE;
417                 }
418
419                 return;
420         }
421
422         /* Connection is not yet in the hash.  We will try to add it, but only if the hash is not
423          * yet saturated.  We assume the hash is saturated (and therefore ignore this connection)
424          * if our load factor cap is now exceeded.  The benefit of limiting connections in this way
425          * is that the hash will continue to function at an average (1) speed by keeping the load
426          * load factor down.  Of course the downside is that each port monitor has a strict maximum 
427          * connection limit. */
428
429         if ( (double)p_monitor->hash.size / (double)p_monitor->hash.positions >= TCP_CONNECTION_HASH_MAX_LOAD_RATIO )
430         {
431                 /* hash exceeds our load limit is now "full" */
432                 return;
433         }
434
435         /* create a new connection node */
436         if ( (p_node = (tcp_connection_node_t *) calloc(1, sizeof(tcp_connection_node_t))) == NULL )
437                 return;
438
439         p_node->connection = *p_connection;  /* bitwise copy of the struct */
440         p_node->connection.age = TCP_CONNECIION_STARTING_AGE;
441         p_node->p_next = NULL;
442
443         /* insert it into the monitor's hash table */
444         if ( hash_insert( &p_monitor->hash, (void *)&p_node->connection ) != 0 )
445         {
446                 /* error inserting into hash.  delete the connection node we just created, so no leaks. */
447 #ifdef HASH_DEBUG
448                 fprintf(stderr, "--- hash_insert error\n");
449 #endif
450                 free(p_node);
451                 return;
452         }
453
454         /* append the node to the monitor's connection list */
455         if ( p_monitor->connection_list.p_tail == NULL )  /* assume p_head is NULL too */
456         {
457                 p_monitor->connection_list.p_head = p_node;
458                 p_monitor->connection_list.p_tail = p_node;
459                 p_node->p_prev = NULL;
460         }
461         else
462         {
463                 p_monitor->connection_list.p_tail->p_next = p_node;
464                 p_node->p_prev = p_monitor->connection_list.p_tail;
465                 p_monitor->connection_list.p_tail = p_node;
466         }
467    }
468 }
469
470 /* ---------------------------------------------------------------------------------------
471  * Apply a tcp_port_monitor_function_ptr_t function to each port monitor in the collection. 
472  * ---------------------------------------------------------------------------------------*/
473 void for_each_tcp_port_monitor_in_collection(
474         tcp_port_monitor_collection_t *         p_collection,
475         tcp_port_monitor_function_ptr_t         p_function,
476         void *                                  p_function_args
477         )
478 {
479    tcp_port_monitor_node_t * p_current_node, * p_next_node;
480    
481    if ( !p_collection || !p_function )
482         return;
483
484    /* for each monitor in the collection */
485    for ( p_current_node = p_collection->monitor_list.p_head; p_current_node != NULL; )
486    {
487         p_next_node = p_current_node->p_next;  /* do this first! */
488
489         if ( p_current_node->p_monitor )
490         {
491             /* apply the function with the given arguments */
492             (*p_function)( p_current_node->p_monitor, p_function_args );
493         }
494
495         p_current_node = p_next_node;
496    }
497   
498 }
499
500 /* ----------------------------------------------------------------------------------------
501  * Calculate an efficient hash size based on the desired number of elements and load factor.
502  * ---------------------------------------------------------------------------------------- */
503 int calc_efficient_hash_size(
504         int                                     min_elements,
505         int                                     max_hash_size,
506         double                                  max_load_factor
507         )
508 {
509    double min_size, hash_size, log_base_2;
510    
511    /* the size of the hash will the smallest power of two such that the minimum number
512       of desired elements does not exceed the maximum load factor. */                 
513    
514    min_size = (double)min_elements / max_load_factor;   /* starting point */
515      
516    /* now adjust size up to nearest power of two */
517    log_base_2 = (double) (int) ( log(min_size) / log(2) ) ;  /* lop off fractional portion of log */
518
519    hash_size = pow(2,log_base_2) >= min_size ? min_size : pow(2,(double)++log_base_2);
520
521    /* respect the maximum */
522    hash_size = hash_size <= max_hash_size ? hash_size : max_hash_size;
523
524    /*
525    fprintf(stderr,"hash size is %d, based on %d min_elements and %.02f max load, %d maximum\n",
526                    (int)hash_size, min_elements, max_load_factor, max_hash_size);
527    */
528
529    return hash_size;
530 }
531                 
532 /* ----------------------------------------------------------------------
533  * CLIENT INTERFACE 
534  *
535  * Clients should call only those functions below this line.
536  * ---------------------------------------------------------------------- */
537
538 /* ----------------------------------
539  * Client operations on port monitors
540  * ---------------------------------- */
541
542 /* Clients should first try to "find_tcp_port_monitor" before creating one
543    so that there are no redundant monitors. */
544 tcp_port_monitor_t * create_tcp_port_monitor(
545         in_port_t                               port_range_begin, 
546         in_port_t                               port_range_end,
547         tcp_port_monitor_args_t *               p_creation_args
548         )
549 {
550    tcp_port_monitor_t * p_monitor;
551
552    /* create the monitor */
553    p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t) );
554    if ( !p_monitor )
555         return NULL;
556
557    /* create the monitor's connection hash */
558    if ( hash_create( &p_monitor->hash, 
559                         p_creation_args && p_creation_args->min_port_monitor_connections > 0 ?
560                                 calc_efficient_hash_size( p_creation_args->min_port_monitor_connections,
561                                                           TCP_CONNECTION_HASH_SIZE_MAX,
562                                                           TCP_CONNECTION_HASH_MAX_LOAD_RATIO ) :
563                                 TCP_CONNECTION_HASH_SIZE_DEFAULT,
564                         &connection_hash_function_1, &connection_hash_function_2,
565                         &connection_match_function, NULL ) != 0 ) 
566    {
567         /* we failed to create the hash, so destroy the monitor completely so we don't leak */
568         destroy_tcp_port_monitor(p_monitor,NULL);
569         return NULL;
570    }
571
572    /* create the monitor's peek array */
573    if ( (p_monitor->p_peek = (tcp_connection_t **) calloc( p_monitor->hash.positions, sizeof(tcp_connection_t *))) == NULL )
574    {
575         /* we failed to create the peek array, so destroy the monitor completely, again, so we don't leak */
576         destroy_tcp_port_monitor(p_monitor,NULL);
577         return NULL ;
578    }
579
580    p_monitor->port_range_begin = port_range_begin;
581    p_monitor->port_range_end = port_range_end;
582
583    p_monitor->connection_list.p_head = NULL;
584    p_monitor->connection_list.p_tail = NULL;
585
586    return p_monitor;
587 }
588
589 /* Clients use this function to get connection data from the indicated port monitor.
590    The requested monitor value is copied into a client-supplied char buffer.
591    Returns 0 on success, -1 otherwise. */
592 int peek_tcp_port_monitor(
593         tcp_port_monitor_t *                    p_monitor,
594         int                                     item,
595         int                                     connection_index,
596         char *                                  p_buffer,
597         size_t                                  buffer_size
598         )
599 {
600    struct hostent *p_hostent;
601    struct servent *p_servent;
602    struct in_addr net;
603
604    if ( !p_monitor || !p_buffer || connection_index < 0 )
605         return(-1);
606
607    memset(p_buffer, 0, buffer_size);
608    memset(&net, 0, sizeof(net));
609
610    /* if the connection index is out of range, we simply return with no error
611     * having first cleared the client-supplied buffer. */
612    if ( (item!=COUNT) && (connection_index > p_monitor->hash.size - 1) )
613            return(0);
614                    
615    switch (item) {
616
617    case COUNT:
618    
619         snprintf( p_buffer, buffer_size, "%d" , p_monitor->hash.size );
620         break;
621
622    case REMOTEIP:
623
624         net.s_addr = p_monitor->p_peek[ connection_index ]->remote_addr;
625         snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
626         break;
627
628    case REMOTEHOST:
629
630         p_hostent = gethostbyaddr( &p_monitor->p_peek[ connection_index ]->remote_addr, sizeof(in_addr_t), AF_INET);
631         /* if no host name found, just use ip address. */
632         if ( !p_hostent || !p_hostent->h_name )
633         {
634                 net.s_addr = p_monitor->p_peek[ connection_index ]->remote_addr;
635                 snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
636                 break;
637         }
638         snprintf( p_buffer, buffer_size, "%s", p_hostent->h_name );
639         break;
640
641    case REMOTEPORT:
642
643         snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->remote_port );                          
644         break;
645         
646    case LOCALIP:
647
648         net.s_addr = p_monitor->p_peek[ connection_index ]->local_addr;
649         snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
650         break;
651
652    case LOCALHOST:
653
654         p_hostent = gethostbyaddr( &p_monitor->p_peek[ connection_index ]->local_addr, sizeof(in_addr_t), AF_INET);
655         /* if no host name found, just use ip address. */
656         if ( !p_hostent || !p_hostent->h_name )
657         {
658                 net.s_addr = p_monitor->p_peek[ connection_index ]->local_addr;
659                 snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
660                 break;
661         }
662         snprintf( p_buffer, buffer_size, "%s", p_hostent->h_name );
663         break;
664
665    case LOCALPORT: 
666
667         snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->local_port );
668         break;        
669
670    case LOCALSERVICE:
671
672         p_servent = getservbyport( htons(p_monitor->p_peek[ connection_index ]->local_port ), "tcp" );
673         /* if no service name found for the port, just use the port number. */
674         if ( !p_servent || !p_servent->s_name )
675         {
676                 snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->local_port );
677                 break;
678         }
679         snprintf( p_buffer, buffer_size, "%s", p_servent->s_name );
680         break;
681
682    default:
683         return(-1);
684    }
685
686    return(0);
687 }
688
689 /* --------------------------------
690  * Client operations on collections
691  * -------------------------------- */
692
693 /* Create a monitor collection.  Do this one first. */
694 tcp_port_monitor_collection_t * create_tcp_port_monitor_collection(
695         tcp_port_monitor_collection_args_t *    p_creation_args
696         )
697 {
698    tcp_port_monitor_collection_t * p_collection;
699
700    p_collection = (tcp_port_monitor_collection_t *) calloc( 1, sizeof( tcp_port_monitor_collection_t ) );
701    if ( !p_collection )
702            return NULL;
703
704    /* create the collection's monitor hash */
705    if ( hash_create( &p_collection->hash, 
706                         p_creation_args && p_creation_args->min_port_monitors > 0 ?
707                                 calc_efficient_hash_size( p_creation_args->min_port_monitors,
708                                                           TCP_MONITOR_HASH_SIZE_MAX,
709                                                           TCP_MONITOR_HASH_MAX_LOAD_RATIO ) :
710                                 TCP_MONITOR_HASH_SIZE_DEFAULT,
711                         &monitor_hash_function_1, &monitor_hash_function_2,
712                         &monitor_match_function, NULL ) != 0 )
713    {
714          /* we failed to create the hash, so destroy the monitor completely so we don't leak */
715          destroy_tcp_port_monitor_collection(p_collection);
716          return NULL;
717    }
718
719    p_collection->monitor_list.p_head = NULL;
720    p_collection->monitor_list.p_tail = NULL;
721
722    return p_collection;
723 }
724
725 /* Destroy the monitor collection (and the monitors inside).  Do this one last. */
726 void destroy_tcp_port_monitor_collection( 
727         tcp_port_monitor_collection_t *         p_collection
728         )
729 {
730    tcp_port_monitor_node_t * p_current_node, * p_next_node;
731
732    if ( !p_collection )
733            return;
734
735    /* destroy the collection's hash */
736    hash_destroy( &p_collection->hash );
737
738    /* destroy the monitors */
739    for_each_tcp_port_monitor_in_collection(
740         p_collection,
741         &destroy_tcp_port_monitor,
742         NULL
743         );
744
745    /* next destroy the empty monitor nodes */
746    for ( p_current_node = p_collection->monitor_list.p_head; p_current_node != NULL; )
747    {
748         p_next_node = p_current_node->p_next;  /* do this first! */
749
750         free( p_current_node );
751         p_current_node = p_next_node;
752    }
753    
754    free( p_collection );
755    p_collection=NULL;
756 }
757
758 /* Updates the tcp statistics for all monitors within a collection */
759 void update_tcp_port_monitor_collection(
760         tcp_port_monitor_collection_t *         p_collection
761         )
762 {
763         FILE *fp;
764         char buf[256];
765         tcp_connection_t conn;
766         unsigned long state;
767
768         if ( !p_collection )
769                 return;
770
771         /* age the connections in all port monitors. */
772         for_each_tcp_port_monitor_in_collection(
773                 p_collection,
774                 &age_tcp_port_monitor,
775                 NULL
776                 );
777
778         /* read tcp data from /proc/net/tcp */
779         if ( ( fp = fopen("/proc/net/tcp", "r" ) ) == NULL )
780                 return;
781
782         /* ignore field name line */
783         fgets(buf, 255, fp);
784
785         /* read all tcp connections */
786         while (fgets (buf, sizeof (buf), fp) != NULL) {
787
788                 if ( sscanf (buf, "%*d: %lx:%lx %lx:%lx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
789                         (unsigned long *)&conn.local_addr, (unsigned long *)&conn.local_port,
790                         (unsigned long *)&conn.remote_addr, (unsigned long *)&conn.remote_port,
791                         (unsigned long *)&state, (unsigned long *)&conn.uid, (unsigned long *)&conn.inode) != 7 )
792
793                         fprintf( stderr, "/proc/net/tcp: bad file format\n" );
794
795                 if ((conn.inode == 0) || (state != TCP_ESTABLISHED)) continue;
796
797                 /* show the connection to each port monitor. */
798                 for_each_tcp_port_monitor_in_collection(
799                         p_collection,
800                         &show_connection_to_tcp_port_monitor,
801                         (void *) &conn
802                         );
803         }
804
805         fclose(fp);
806
807         /* check the health of the monitor hashes and rebuild them if nedded */
808         for_each_tcp_port_monitor_in_collection(
809                 p_collection,
810                 &maintain_tcp_port_monitor_hash,
811                 NULL
812                 );
813
814         /* rebuild the connection peek tables of all monitors so clients can peek in O(1) time */
815         for_each_tcp_port_monitor_in_collection(
816                 p_collection,
817                 &rebuild_tcp_port_monitor_peek_table,
818                 NULL
819                 );
820 }
821
822 /* After clients create a monitor, use this to add it to the collection.
823    Returns 0 on success, -1 otherwise. */
824 int insert_tcp_port_monitor_into_collection( 
825         tcp_port_monitor_collection_t *         p_collection,
826         tcp_port_monitor_t *                    p_monitor
827         )
828 {
829    tcp_port_monitor_node_t * p_node;
830
831    if ( !p_collection || !p_monitor )
832         return (-1);
833
834    /* create a container node for this monitor */
835    p_node = (tcp_port_monitor_node_t *) calloc( 1, sizeof(tcp_port_monitor_node_t) );
836    if ( !p_node )
837         return (-1);
838
839    /* populate the node */
840    p_node->p_monitor = p_monitor;
841    p_node->p_next = NULL;
842            
843    /* add a pointer to this monitor to the collection's hash */
844    if ( hash_insert( &p_collection->hash, (void *)p_monitor ) != 0 )
845    {
846         /* error inserting into hash.  destroy the monitor's container node so no leaks */
847         free( p_node );
848         return (-1);
849    }
850
851    /* tail of the container gets this node */
852    if ( !p_collection->monitor_list.p_tail )
853         p_collection->monitor_list.p_tail = p_node;
854    else
855    {
856         /* p_next of the tail better be NULL */
857         if ( p_collection->monitor_list.p_tail->p_next != NULL )
858            return (-1);
859
860         /* splice node onto tail */
861         p_collection->monitor_list.p_tail->p_next = p_node;
862         p_collection->monitor_list.p_tail = p_node;
863    }
864
865    /* if this was the first element added */
866    if ( !p_collection->monitor_list.p_head )
867         p_collection->monitor_list.p_head = p_collection->monitor_list.p_tail;
868
869    return 0;
870 }
871
872 /* Clients need a way to find monitors */
873 tcp_port_monitor_t * find_tcp_port_monitor( 
874         tcp_port_monitor_collection_t *         p_collection, 
875         in_port_t                               port_range_begin,
876         in_port_t                               port_range_end
877         )
878 {
879    tcp_port_monitor_t monitor,*p_monitor;
880    void *p_cast;
881
882    if ( !p_collection )
883         return NULL;
884
885    /* need a monitor object to use for searching the hash */
886    monitor.port_range_begin = port_range_begin;
887    monitor.port_range_end = port_range_end;
888    p_monitor = &monitor;
889    p_cast = (void *)p_monitor;
890
891    /* simple hash table lookup */
892    if ( hash_lookup( &p_collection->hash, &p_cast ) == 0 )
893    {
894            /* found the monitor and p_cast now points to it */
895            p_monitor = (tcp_port_monitor_t *)p_cast;
896            return( p_monitor );
897    }
898
899    return NULL;  /* monitor not found */
900 }