scan ports as short. debian bug# 359974
[monky] / src / libtcp-portmon.c
index 2e4fedd..5881cc9 100644 (file)
@@ -2,6 +2,8 @@
  * libtcp-portmon.c:  tcp port monitoring library.               
  *
  * Copyright (C) 2005  Philip Kovacs kovacsp3@comcast.net
+ * 
+ * $Id$
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * functions.  Use the "Client interface" functions defined at bottom.
  * ------------------------------------------------------------------- */
 
+/* ----------------------------------
+ * Copy a tcp_connection_t
+ *
+ * Returns 0 on success, -1 otherwise.
+ * ----------------------------------*/
+int copy_tcp_connection( 
+       tcp_connection_t *                      p_dest_connection,
+       const tcp_connection_t *                p_source_connection
+       )
+{
+   if ( !p_dest_connection || !p_source_connection )
+       return (-1);
+
+   p_dest_connection->local_addr = p_source_connection->local_addr;
+   p_dest_connection->local_port = p_source_connection->local_port;
+   p_dest_connection->remote_addr = p_source_connection->remote_addr;
+   p_dest_connection->remote_port = p_source_connection->remote_port;
+   p_dest_connection->age = p_source_connection->age;
+
+   return 0;
+}
+
 /* -----------------------------------------------------------------------------
  * Open-addressed hash implementation requires that we supply two hash functions
  * and a match function to compare two hash elements for identity.
@@ -207,8 +231,8 @@ int monitor_match_function( const void *p_data1, const void *p_data2 )
    p_monitor1 = (tcp_port_monitor_t *)p_data1;
    p_monitor2 = (tcp_port_monitor_t *)p_data2;
 
-   return (p_monitor1->port_range_begin == p_monitor1->port_range_begin &&
-          p_monitor2->port_range_end == p_monitor2->port_range_end);
+   return (p_monitor1->port_range_begin == p_monitor2->port_range_begin &&
+          p_monitor1->port_range_end == p_monitor2->port_range_end);
 }
 
 /* ---------------------------------------------------------------------------
@@ -326,7 +350,7 @@ void maintain_tcp_port_monitor_hash(
 #ifdef HASH_DEBUG
     fprintf(stderr,"--- num vacated is %d, vacated factor is %.3f\n", p_monitor->hash.vacated, vacated_load );
 #endif
-    if ( vacated_load <= TCP_CONNECIION_HASH_MAX_VACATED_PCT )
+    if ( vacated_load <= TCP_CONNECTION_HASH_MAX_VACATED_RATIO )
     {
            /* hash is fine and needs no rebalancing */
            return;
@@ -358,7 +382,7 @@ void rebuild_tcp_port_monitor_peek_table(
        void *                                  p_void
        )
 {
-   /* Run through the monitori's connections and rebuild the peek table
+   /* Run through the monitor's connections and rebuild the peek table
     * of connection pointers.  This is done so peeking into the monitor
     * can be done in O(1) time instead of O(n) time for each peek. */
 
@@ -369,7 +393,7 @@ void rebuild_tcp_port_monitor_peek_table(
        return;
 
    /* zero out the peek array */
-   memset( p_monitor->p_peek, 0, TCP_CONNECTION_HASH_SIZE * sizeof(tcp_connection_t *) );
+   memset( p_monitor->p_peek, 0, p_monitor->hash.positions * sizeof(tcp_connection_t *) );
 
    for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; p_node=p_node->p_next, i++ )
    {
@@ -395,6 +419,8 @@ void show_connection_to_tcp_port_monitor(
    if ( !p_monitor || !p_void )
        return;
 
+   /* This p_connection is on caller's stack and not the heap.  If we are interested,
+    * we will create a copy of the connection (on the heap) and add it to our list. */
    tcp_connection_t *p_connection = (tcp_connection_t *)p_void;
    
    /* inspect the local port number of the connection to see if we're interested. */
@@ -411,7 +437,7 @@ void show_connection_to_tcp_port_monitor(
                /* it's already in the hash.  reset the age of the connection. */
                if ( p_connection != NULL )
                {
-                       p_connection->age = TCP_CONNECIION_STARTING_AGE;
+                       p_connection->age = TCP_CONNECTION_STARTING_AGE;
                }
 
                return;
@@ -422,10 +448,9 @@ void show_connection_to_tcp_port_monitor(
         * if our load factor cap is now exceeded.  The benefit of limiting connections in this way
         * is that the hash will continue to function at an average (1) speed by keeping the load
         * load factor down.  Of course the downside is that each port monitor has a strict maximum 
-        * connection limit.  Future versions should probably allow the client to set the hash size
-        * and load limits and/or provide for automatic resizing of hashes. */
+        * connection limit. */
 
-       if ( p_monitor->hash.size / p_monitor->hash.positions > TCP_CONNECTION_HASH_MAX_LOAD_PCT )
+       if ( (double)p_monitor->hash.size / (double)p_monitor->hash.positions >= TCP_CONNECTION_HASH_MAX_LOAD_RATIO )
        {
                /* hash exceeds our load limit is now "full" */
                return;
@@ -435,8 +460,15 @@ void show_connection_to_tcp_port_monitor(
        if ( (p_node = (tcp_connection_node_t *) calloc(1, sizeof(tcp_connection_node_t))) == NULL )
                return;
 
-       p_node->connection = *p_connection;  /* bitwise copy of the struct */
-       p_node->connection.age = TCP_CONNECIION_STARTING_AGE;
+       /* copy the connection data */
+       if ( copy_tcp_connection( &p_node->connection, p_connection ) != 0 )
+       {
+               /* error copying the connection data. deallocate p_node to avoid leaks and return. */
+               free( p_node );
+               return;
+       }
+
+       p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
        p_node->p_next = NULL;
 
        /* insert it into the monitor's hash table */
@@ -496,7 +528,38 @@ void for_each_tcp_port_monitor_in_collection(
   
 }
 
+/* ----------------------------------------------------------------------------------------
+ * Calculate an efficient hash size based on the desired number of elements and load factor.
+ * ---------------------------------------------------------------------------------------- */
+int calc_efficient_hash_size(
+       int                                     min_elements,
+       int                                     max_hash_size,
+       double                                  max_load_factor
+       )
+{
+   double min_size, hash_size, log_base_2;
+   
+   /* the size of the hash will the smallest power of two such that the minimum number
+      of desired elements does not exceed the maximum load factor. */                 
+   
+   min_size = (double)min_elements / max_load_factor;   /* starting point */
+     
+   /* now adjust size up to nearest power of two */
+   log_base_2 = (double) (int) ( log(min_size) / log(2) ) ;  /* lop off fractional portion of log */
+
+   hash_size = pow(2,log_base_2) >= min_size ? min_size : pow(2,(double)++log_base_2);
+
+   /* respect the maximum */
+   hash_size = hash_size <= max_hash_size ? hash_size : max_hash_size;
 
+   /*
+   fprintf(stderr,"hash size is %d, based on %d min_elements and %.02f max load, %d maximum\n",
+                  (int)hash_size, min_elements, max_load_factor, max_hash_size);
+   */
+
+   return hash_size;
+}
+               
 /* ----------------------------------------------------------------------
  * CLIENT INTERFACE 
  *
@@ -511,7 +574,8 @@ void for_each_tcp_port_monitor_in_collection(
    so that there are no redundant monitors. */
 tcp_port_monitor_t * create_tcp_port_monitor(
        in_port_t                               port_range_begin, 
-       in_port_t                               port_range_end
+       in_port_t                               port_range_end,
+       tcp_port_monitor_args_t *               p_creation_args
        )
 {
    tcp_port_monitor_t * p_monitor;
@@ -522,7 +586,12 @@ tcp_port_monitor_t * create_tcp_port_monitor(
        return NULL;
 
    /* create the monitor's connection hash */
-   if ( hash_create( &p_monitor->hash, TCP_CONNECTION_HASH_SIZE, 
+   if ( hash_create( &p_monitor->hash, 
+                       p_creation_args && p_creation_args->min_port_monitor_connections > 0 ?
+                               calc_efficient_hash_size( p_creation_args->min_port_monitor_connections,
+                                                         TCP_CONNECTION_HASH_SIZE_MAX,
+                                                         TCP_CONNECTION_HASH_MAX_LOAD_RATIO ) :
+                               TCP_CONNECTION_HASH_SIZE_DEFAULT,
                        &connection_hash_function_1, &connection_hash_function_2,
                        &connection_match_function, NULL ) != 0 ) 
    {
@@ -532,7 +601,7 @@ tcp_port_monitor_t * create_tcp_port_monitor(
    }
 
    /* create the monitor's peek array */
-   if ( (p_monitor->p_peek = (tcp_connection_t **) calloc( TCP_CONNECTION_HASH_SIZE, sizeof(tcp_connection_t *))) == NULL )
+   if ( (p_monitor->p_peek = (tcp_connection_t **) calloc( p_monitor->hash.positions, sizeof(tcp_connection_t *))) == NULL )
    {
        /* we failed to create the peek array, so destroy the monitor completely, again, so we don't leak */
        destroy_tcp_port_monitor(p_monitor,NULL);
@@ -552,7 +621,7 @@ tcp_port_monitor_t * create_tcp_port_monitor(
    The requested monitor value is copied into a client-supplied char buffer.
    Returns 0 on success, -1 otherwise. */
 int peek_tcp_port_monitor(
-        tcp_port_monitor_t *                    p_monitor,
+        const tcp_port_monitor_t *              p_monitor,
         int                                     item,
         int                                     connection_index,
         char *                                  p_buffer,
@@ -653,7 +722,9 @@ int peek_tcp_port_monitor(
  * -------------------------------- */
 
 /* Create a monitor collection.  Do this one first. */
-tcp_port_monitor_collection_t * create_tcp_port_monitor_collection( void )
+tcp_port_monitor_collection_t * create_tcp_port_monitor_collection(
+       tcp_port_monitor_collection_args_t *    p_creation_args
+       )
 {
    tcp_port_monitor_collection_t * p_collection;
 
@@ -662,7 +733,12 @@ tcp_port_monitor_collection_t * create_tcp_port_monitor_collection( void )
           return NULL;
 
    /* create the collection's monitor hash */
-   if ( hash_create( &p_collection->hash, TCP_MONITOR_HASH_SIZE,
+   if ( hash_create( &p_collection->hash, 
+                       p_creation_args && p_creation_args->min_port_monitors > 0 ?
+                               calc_efficient_hash_size( p_creation_args->min_port_monitors,
+                                                         TCP_MONITOR_HASH_SIZE_MAX,
+                                                         TCP_MONITOR_HASH_MAX_LOAD_RATIO ) :
+                               TCP_MONITOR_HASH_SIZE_DEFAULT,
                        &monitor_hash_function_1, &monitor_hash_function_2,
                        &monitor_match_function, NULL ) != 0 )
    {
@@ -718,7 +794,7 @@ void update_tcp_port_monitor_collection(
        FILE *fp;
         char buf[256];
         tcp_connection_t conn;
-        unsigned long state;
+        unsigned long inode,uid,state;
 
        if ( !p_collection )
                return;
@@ -740,14 +816,14 @@ void update_tcp_port_monitor_collection(
         /* read all tcp connections */
         while (fgets (buf, sizeof (buf), fp) != NULL) {
 
-                if ( sscanf (buf, "%*d: %lx:%lx %lx:%lx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
-                        (unsigned long *)&conn.local_addr, (unsigned long *)&conn.local_port,
-                        (unsigned long *)&conn.remote_addr, (unsigned long *)&conn.remote_port,
-                        (unsigned long *)&state, (unsigned long *)&conn.uid, (unsigned long *)&conn.inode) != 7 )
+                if ( sscanf (buf, "%*d: %lx:%hx %lx:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
+                        (unsigned long *)&conn.local_addr, &conn.local_port,
+                        (unsigned long *)&conn.remote_addr, &conn.remote_port,
+                        (unsigned long *)&state, (unsigned long *)&uid, (unsigned long *)&inode) != 7 )
 
                         fprintf( stderr, "/proc/net/tcp: bad file format\n" );
 
-                if ((conn.inode == 0) || (state != TCP_ESTABLISHED)) continue;
+                if ((inode == 0) || (state != TCP_ESTABLISHED)) continue;
 
                /* show the connection to each port monitor. */
                for_each_tcp_port_monitor_in_collection(
@@ -826,7 +902,7 @@ int insert_tcp_port_monitor_into_collection(
 
 /* Clients need a way to find monitors */
 tcp_port_monitor_t * find_tcp_port_monitor( 
-       tcp_port_monitor_collection_t *         p_collection, 
+       const tcp_port_monitor_collection_t *   p_collection, 
        in_port_t                               port_range_begin,
        in_port_t                               port_range_end
        )