2005-10-31 05:17:06 +00:00
|
|
|
/* -------------------------------------------------------------------------
|
|
|
|
* libtcp-portmon.c: tcp port monitoring library.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2005 Philip Kovacs kovacsp3@comcast.net
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
* --------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
#include "libtcp-portmon.h"
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------
|
|
|
|
* IMPLEMENTATION INTERFACE
|
|
|
|
*
|
|
|
|
* Implementation-specific interface begins here. Clients should not
|
|
|
|
* manipulate these structures directly, nor call the defined helper
|
|
|
|
* functions. Use the "Client interface" functions defined at bottom.
|
|
|
|
* ------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
|
|
* Open-addressed hash implementation requires that we supply two hash functions
|
|
|
|
* and a match function to compare two hash elements for identity.
|
|
|
|
* ----------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/* --------------------------------------------------
|
|
|
|
* Functions to hash the connections within a monitor
|
|
|
|
* --------------------------------------------------*/
|
|
|
|
|
|
|
|
#define CONNECTION_HASH_KEY_LEN 17
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------------
|
|
|
|
* First connection hash function: DJB with a 65521 prime modulus to govern the range.
|
|
|
|
* ----------------------------------------------------------------------------------*/
|
|
|
|
int connection_hash_function_1( const void *p_data )
|
|
|
|
{
|
|
|
|
tcp_connection_t *p_conn;
|
|
|
|
char key[CONNECTION_HASH_KEY_LEN];
|
|
|
|
unsigned int hash = 5381;
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
|
|
|
if ( !p_data )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
memset(key,0,sizeof(key));
|
|
|
|
|
|
|
|
/* p_data is a pointer to tcp_connection_t */
|
|
|
|
p_conn = (tcp_connection_t *)p_data;
|
|
|
|
|
|
|
|
/* key is a hex representation of the connection */
|
|
|
|
snprintf(key, CONNECTION_HASH_KEY_LEN, "%08X%04X%04X",
|
|
|
|
p_conn->remote_addr, p_conn->remote_port, p_conn->local_port);
|
|
|
|
#ifdef HASH_DEBUG
|
|
|
|
fprintf(stderr,"--- key=[%s]\n",key);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
for(i = 0; i < CONNECTION_HASH_KEY_LEN-1; i++)
|
|
|
|
{
|
|
|
|
hash = ((hash << 5) + hash) + (key[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (hash & 0x7FFFFFFF) % 65521;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------
|
|
|
|
* Second connection hash function: DEK, modified to return odd numbers only,
|
|
|
|
* as required for open-address hashing using double-hash probing.
|
|
|
|
* Also uses a 65521 prime modulus to govern the range.
|
|
|
|
* -------------------------------------------------------------------------*/
|
|
|
|
int connection_hash_function_2( const void *p_data )
|
|
|
|
{
|
|
|
|
tcp_connection_t *p_conn;
|
|
|
|
char key[CONNECTION_HASH_KEY_LEN];
|
|
|
|
unsigned int hash = CONNECTION_HASH_KEY_LEN-1;
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
|
|
|
if ( !p_data )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
memset(key,0,sizeof(key));
|
|
|
|
|
|
|
|
/* p_data is a pointer to a tcp_connection_t */
|
|
|
|
p_conn = (tcp_connection_t *)p_data;
|
|
|
|
|
|
|
|
/* key is a hex representation of the connection */
|
|
|
|
snprintf(key, CONNECTION_HASH_KEY_LEN, "%08X%04X%04X",
|
|
|
|
p_conn->remote_addr, p_conn->remote_port, p_conn->local_port);
|
|
|
|
|
|
|
|
for(i = 0; i < CONNECTION_HASH_KEY_LEN-1; i++)
|
|
|
|
{
|
|
|
|
hash = ((hash << 5) ^ (hash >> 27)) ^ (key[i]);
|
|
|
|
}
|
|
|
|
return (( hash & 0x7FFFFFFF ) % 65521 ) | 0x00000001;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------
|
|
|
|
* Connection Match function returns non-zero if hash elements are identical.
|
|
|
|
* -------------------------------------------------------------------------*/
|
|
|
|
int connection_match_function( const void *p_data1, const void *p_data2 )
|
|
|
|
{
|
|
|
|
tcp_connection_t *p_conn1, *p_conn2;
|
|
|
|
|
|
|
|
if ( !p_data1 || !p_data2 )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* p_data1, p_data2 are pointers to tcp_connection_t */
|
|
|
|
p_conn1 = (tcp_connection_t *)p_data1;
|
|
|
|
p_conn2 = (tcp_connection_t *)p_data2;
|
|
|
|
|
|
|
|
return (p_conn1->local_addr == p_conn2->local_addr &&
|
|
|
|
p_conn1->local_port == p_conn2->local_port &&
|
|
|
|
p_conn1->remote_addr == p_conn2->remote_addr &&
|
|
|
|
p_conn1->remote_port == p_conn2->remote_port );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------
|
|
|
|
* Functions to hash the monitors within a collection
|
|
|
|
* --------------------------------------------------*/
|
|
|
|
|
|
|
|
#define MONITOR_HASH_KEY_LEN 9
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------------
|
|
|
|
* First monitor hash function: DJB with a 65521 prime modulus to govern the range.
|
|
|
|
* -------------------------------------------------------------------------------*/
|
|
|
|
int monitor_hash_function_1( const void *p_data )
|
|
|
|
{
|
|
|
|
tcp_port_monitor_t *p_monitor;
|
|
|
|
char key[MONITOR_HASH_KEY_LEN];
|
|
|
|
unsigned int hash = 5381;
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
|
|
|
if ( !p_data )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
memset(key,0,sizeof(key));
|
|
|
|
|
|
|
|
/* p_data is a pointer to tcp_port_monitor_t */
|
|
|
|
p_monitor = (tcp_port_monitor_t *)p_data;
|
|
|
|
|
|
|
|
/* key is a hex representation of the starting port concatenated to the ending port */
|
|
|
|
snprintf(key, MONITOR_HASH_KEY_LEN, "%04X%04X",
|
|
|
|
p_monitor->port_range_begin, p_monitor->port_range_end );
|
|
|
|
#ifdef HASH_DEBUG
|
|
|
|
fprintf(stderr,"--- key=[%s]\n",key);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
for(i = 0; i < MONITOR_HASH_KEY_LEN-1; i++)
|
|
|
|
{
|
|
|
|
hash = ((hash << 5) + hash) + (key[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (hash & 0x7FFFFFFF) % 65521;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -----------------------------------------------------------------------
|
|
|
|
* Second monitor hash function: DEK, modified to return odd numbers only,
|
|
|
|
* as required for open-address hashing using double-hash probing.
|
|
|
|
* Also uses a 65521 prime modulus to govern the range.
|
|
|
|
* -----------------------------------------------------------------------*/
|
|
|
|
int monitor_hash_function_2( const void *p_data )
|
|
|
|
{
|
|
|
|
tcp_port_monitor_t *p_monitor;
|
|
|
|
char key[MONITOR_HASH_KEY_LEN];
|
|
|
|
unsigned int hash = MONITOR_HASH_KEY_LEN-1;
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
|
|
|
if ( !p_data )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
memset(key,0,sizeof(key));
|
|
|
|
|
|
|
|
/* p_data is a pointer to a tcp_port_monitor_t */
|
|
|
|
p_monitor = (tcp_port_monitor_t *)p_data;
|
|
|
|
|
|
|
|
/* key is a hex representation of the starting port concatenated to the ending port */
|
|
|
|
snprintf(key, MONITOR_HASH_KEY_LEN, "%04X%04X",
|
|
|
|
p_monitor->port_range_begin, p_monitor->port_range_end );
|
|
|
|
|
|
|
|
for(i = 0; i < MONITOR_HASH_KEY_LEN-1; i++)
|
|
|
|
{
|
|
|
|
hash = ((hash << 5) ^ (hash >> 27)) ^ (key[i]);
|
|
|
|
}
|
|
|
|
return (( hash & 0x7FFFFFFF ) % 65521 ) | 0x00000001;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* Monitor match function returns non-zero if hash elements are identical.
|
|
|
|
* ----------------------------------------------------------------------*/
|
|
|
|
int monitor_match_function( const void *p_data1, const void *p_data2 )
|
|
|
|
{
|
|
|
|
tcp_port_monitor_t *p_monitor1, *p_monitor2;
|
|
|
|
|
|
|
|
if ( !p_data1 || !p_data2 )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* p_data1, p_data2 are pointers to tcp_connection_t */
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
|
|
* Port monitor utility functions implementing tcp_port_monitor_function_ptr_t
|
|
|
|
* ---------------------------------------------------------------------------*/
|
|
|
|
void destroy_tcp_port_monitor(
|
|
|
|
tcp_port_monitor_t * p_monitor,
|
|
|
|
void * p_void
|
|
|
|
)
|
|
|
|
{
|
|
|
|
tcp_connection_node_t *p_node, *p_temp;
|
|
|
|
|
|
|
|
if ( !p_monitor || p_void ) /* p_void should be NULL in this context */
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* destroy the monitor's hash */
|
|
|
|
hash_destroy(&p_monitor->hash);
|
|
|
|
|
|
|
|
/* destroy the monitor's peek array */
|
|
|
|
free( p_monitor->p_peek );
|
|
|
|
|
|
|
|
/* destroy the monitor's connection list */
|
|
|
|
for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; )
|
|
|
|
{
|
|
|
|
/* p_temp is for the next iteration */
|
|
|
|
p_temp = p_node->p_next;
|
|
|
|
|
|
|
|
free( p_node );
|
|
|
|
|
|
|
|
p_node = p_temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* destroy the monitor */
|
|
|
|
free( p_monitor );
|
|
|
|
}
|
|
|
|
|
|
|
|
void age_tcp_port_monitor(
|
|
|
|
tcp_port_monitor_t * p_monitor,
|
|
|
|
void * p_void
|
|
|
|
)
|
|
|
|
{
|
|
|
|
/* Run through the monitor's connections and decrement the age variable.
|
|
|
|
* If the age goes negative, we remove the connection from the monitor.
|
|
|
|
* Function takes O(n) time on the number of connections. */
|
|
|
|
|
|
|
|
tcp_connection_node_t *p_node, *p_temp;
|
|
|
|
tcp_connection_t *p_conn;
|
|
|
|
void *p_cast;
|
|
|
|
|
|
|
|
if ( !p_monitor || p_void ) /* p_void should be NULL in this context */
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( !p_monitor->p_peek )
|
|
|
|
return;
|
|
|
|
|
|
|
|
for ( p_node = p_monitor->connection_list.p_head; p_node != NULL; )
|
|
|
|
{
|
|
|
|
if ( --p_node->connection.age >= 0 ) {
|
|
|
|
p_node = p_node->p_next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* connection on p_node is old. remove connection from the hash. */
|
|
|
|
p_conn = &p_node->connection;
|
|
|
|
p_cast = (void *)p_conn;
|
|
|
|
if ( hash_remove( &p_monitor->hash, &p_cast ) != 0 ) {
|
|
|
|
#ifdef HASH_DEBUG
|
|
|
|
fprintf(stderr, "--- hash_remove error\n");
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* splice p_node out of the connection_list */
|
|
|
|
if ( p_node->p_prev != NULL )
|
|
|
|
p_node->p_prev->p_next = p_node->p_next;
|
|
|
|
if ( p_node->p_next != NULL )
|
|
|
|
p_node->p_next->p_prev = p_node->p_prev;
|
|
|
|
|
|
|
|
/* correct the list head and tail if necessary */
|
|
|
|
if ( p_monitor->connection_list.p_head == p_node )
|
|
|
|
p_monitor->connection_list.p_head = p_node->p_next;
|
|
|
|
if ( p_monitor->connection_list.p_tail == p_node )
|
|
|
|
p_monitor->connection_list.p_tail = p_node->p_prev;
|
|
|
|
|
|
|
|
/* p_temp is for the next iteration */
|
|
|
|
p_temp = p_node->p_next;
|
|
|
|
|
|
|
|
/* destroy the node */
|
|
|
|
free( p_node );
|
|
|
|
|
|
|
|
p_node = p_temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void maintain_tcp_port_monitor_hash(
|
|
|
|
tcp_port_monitor_t * p_monitor,
|
|
|
|
void * p_void
|
|
|
|
)
|
|
|
|
{
|
|
|
|
/* Check the number of vacated slots in the hash. If it exceeds our maximum
|
|
|
|
* threshold (should be about 1/4 of the hash table), then the hash table
|
|
|
|
* performance degrades from O(1) toward O(n) as the number of vacated slots
|
|
|
|
* climbs. This is avoided by clearing the hash and reinserting the entries.
|
|
|
|
* The benefit of open-addressing hashing does come with this price --
|
|
|
|
* you must rebalance it occasionally. */
|
|
|
|
|
|
|
|
tcp_connection_node_t *p_node;
|
|
|
|
double vacated_load;
|
|
|
|
|
|
|
|
if ( !p_monitor || p_void ) /* p_void should be NULL in this context */
|
|
|
|
return;
|
|
|
|
|
|
|
|
vacated_load = (double)p_monitor->hash.vacated / (double)p_monitor->hash.positions;
|
|
|
|
#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 )
|
|
|
|
{
|
|
|
|
/* hash is fine and needs no rebalancing */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HASH_DEBUG
|
|
|
|
fprintf(stderr,"--- rebuilding hash\n");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* rebuild the hash */
|
|
|
|
memset( p_monitor->hash.pp_table, 0, p_monitor->hash.positions * sizeof(void **));
|
|
|
|
p_monitor->hash.size = 0;
|
|
|
|
p_monitor->hash.vacated = 0;
|
|
|
|
|
|
|
|
for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; p_node=p_node->p_next )
|
|
|
|
{
|
|
|
|
if ( hash_insert( &p_monitor->hash, (void *)&p_node->connection ) != 0 )
|
|
|
|
{
|
|
|
|
#ifdef HASH_DEBUG
|
|
|
|
fprintf(stderr,"--- hash_insert error\n");
|
|
|
|
#endif
|
|
|
|
;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void rebuild_tcp_port_monitor_peek_table(
|
|
|
|
tcp_port_monitor_t * p_monitor,
|
|
|
|
void * p_void
|
|
|
|
)
|
|
|
|
{
|
|
|
|
/* Run through the monitori'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. */
|
|
|
|
|
|
|
|
tcp_connection_node_t *p_node;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
if ( !p_monitor || p_void ) /* p_void should be NULL in this context */
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* zero out the peek array */
|
|
|
|
memset( p_monitor->p_peek, 0, TCP_CONNECTION_HASH_SIZE * sizeof(tcp_connection_t *) );
|
|
|
|
|
|
|
|
for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; p_node=p_node->p_next, i++ )
|
|
|
|
{
|
|
|
|
p_monitor->p_peek[i] = &p_node->connection;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void show_connection_to_tcp_port_monitor(
|
|
|
|
tcp_port_monitor_t * p_monitor,
|
|
|
|
void * p_void
|
|
|
|
)
|
|
|
|
{
|
|
|
|
/* The monitor gets to look at each connection to see if it falls within
|
|
|
|
* the monitor's port range of interest. Connections of interest are first
|
|
|
|
* looked up in the hash to see if they are already there. If they are, we
|
|
|
|
* reset the age of the connection so it is not deleted. If the connection
|
|
|
|
* is not in the hash, we add it, but only if the hash is not saturated.
|
|
|
|
* The function takes O(1) time. */
|
|
|
|
|
|
|
|
tcp_connection_node_t *p_node;
|
|
|
|
void *p_cast;
|
|
|
|
|
|
|
|
if ( !p_monitor || !p_void )
|
|
|
|
return;
|
|
|
|
|
|
|
|
tcp_connection_t *p_connection = (tcp_connection_t *)p_void;
|
|
|
|
|
|
|
|
/* inspect the local port number of the connection to see if we're interested. */
|
|
|
|
if ( (p_monitor->port_range_begin <= p_connection->local_port) &&
|
|
|
|
(p_connection->local_port <= p_monitor->port_range_end) )
|
|
|
|
{
|
|
|
|
/* the connection is in the range of the monitor. */
|
|
|
|
|
|
|
|
/* first check the hash to see if the connection is already there. */
|
|
|
|
p_cast = (void *)p_connection;
|
|
|
|
if ( hash_lookup( &p_monitor->hash, &p_cast ) == 0 )
|
|
|
|
{
|
|
|
|
p_connection = (tcp_connection_t *)p_cast;
|
|
|
|
/* it's already in the hash. reset the age of the connection. */
|
|
|
|
if ( p_connection != NULL )
|
|
|
|
{
|
|
|
|
p_connection->age = TCP_CONNECIION_STARTING_AGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Connection is not yet in the hash. We will try to add it, but only if the hash is not
|
|
|
|
* yet saturated. We assume the hash is saturated (and therefore ignore this connection)
|
|
|
|
* 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. */
|
|
|
|
|
|
|
|
if ( p_monitor->hash.size / p_monitor->hash.positions > TCP_CONNECTION_HASH_MAX_LOAD_PCT )
|
|
|
|
{
|
|
|
|
/* hash exceeds our load limit is now "full" */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create a new connection node */
|
|
|
|
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;
|
|
|
|
p_node->p_next = NULL;
|
|
|
|
|
|
|
|
/* insert it into the monitor's hash table */
|
|
|
|
if ( hash_insert( &p_monitor->hash, (void *)&p_node->connection ) != 0 )
|
|
|
|
{
|
|
|
|
/* error inserting into hash. delete the connection node we just created, so no leaks. */
|
|
|
|
#ifdef HASH_DEBUG
|
|
|
|
fprintf(stderr, "--- hash_insert error\n");
|
|
|
|
#endif
|
|
|
|
free(p_node);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* append the node to the monitor's connection list */
|
|
|
|
if ( p_monitor->connection_list.p_tail == NULL ) /* assume p_head is NULL too */
|
|
|
|
{
|
|
|
|
p_monitor->connection_list.p_head = p_node;
|
|
|
|
p_monitor->connection_list.p_tail = p_node;
|
|
|
|
p_node->p_prev = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
p_monitor->connection_list.p_tail->p_next = p_node;
|
|
|
|
p_node->p_prev = p_monitor->connection_list.p_tail;
|
|
|
|
p_monitor->connection_list.p_tail = p_node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------------------------
|
|
|
|
* Apply a tcp_port_monitor_function_ptr_t function to each port monitor in the collection.
|
|
|
|
* ---------------------------------------------------------------------------------------*/
|
|
|
|
void for_each_tcp_port_monitor_in_collection(
|
|
|
|
tcp_port_monitor_collection_t * p_collection,
|
|
|
|
tcp_port_monitor_function_ptr_t p_function,
|
|
|
|
void * p_function_args
|
|
|
|
)
|
|
|
|
{
|
|
|
|
tcp_port_monitor_node_t * p_current_node, * p_next_node;
|
|
|
|
|
|
|
|
if ( !p_collection || !p_function )
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* for each monitor in the collection */
|
|
|
|
for ( p_current_node = p_collection->monitor_list.p_head; p_current_node != NULL; )
|
|
|
|
{
|
|
|
|
p_next_node = p_current_node->p_next; /* do this first! */
|
|
|
|
|
|
|
|
if ( p_current_node->p_monitor )
|
|
|
|
{
|
|
|
|
/* apply the function with the given arguments */
|
|
|
|
(*p_function)( p_current_node->p_monitor, p_function_args );
|
|
|
|
}
|
|
|
|
|
|
|
|
p_current_node = p_next_node;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* CLIENT INTERFACE
|
|
|
|
*
|
|
|
|
* Clients should call only those functions below this line.
|
|
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/* ----------------------------------
|
|
|
|
* Client operations on port monitors
|
|
|
|
* ---------------------------------- */
|
|
|
|
|
|
|
|
/* Clients should first try to "find_tcp_port_monitor" before creating one
|
|
|
|
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
|
|
|
|
)
|
|
|
|
{
|
|
|
|
tcp_port_monitor_t * p_monitor;
|
|
|
|
|
|
|
|
/* create the monitor */
|
|
|
|
p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t) );
|
|
|
|
if ( !p_monitor )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* create the monitor's connection hash */
|
|
|
|
if ( hash_create( &p_monitor->hash, TCP_CONNECTION_HASH_SIZE,
|
|
|
|
&connection_hash_function_1, &connection_hash_function_2,
|
|
|
|
&connection_match_function, NULL ) != 0 )
|
|
|
|
{
|
|
|
|
/* we failed to create the hash, so destroy the monitor completely so we don't leak */
|
|
|
|
destroy_tcp_port_monitor(p_monitor,NULL);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create the monitor's peek array */
|
|
|
|
if ( (p_monitor->p_peek = (tcp_connection_t **) calloc( TCP_CONNECTION_HASH_SIZE, 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);
|
|
|
|
return NULL ;
|
|
|
|
}
|
|
|
|
|
|
|
|
p_monitor->port_range_begin = port_range_begin;
|
|
|
|
p_monitor->port_range_end = port_range_end;
|
|
|
|
|
|
|
|
p_monitor->connection_list.p_head = NULL;
|
|
|
|
p_monitor->connection_list.p_tail = NULL;
|
|
|
|
|
|
|
|
return p_monitor;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clients use this function to get connection data from the indicated 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,
|
|
|
|
int item,
|
|
|
|
int connection_index,
|
|
|
|
char * p_buffer,
|
|
|
|
size_t buffer_size
|
|
|
|
)
|
|
|
|
{
|
|
|
|
struct hostent *p_hostent;
|
|
|
|
struct servent *p_servent;
|
|
|
|
struct in_addr net;
|
|
|
|
|
|
|
|
if ( !p_monitor || !p_buffer || connection_index < 0 )
|
|
|
|
return(-1);
|
|
|
|
|
|
|
|
memset(p_buffer, 0, buffer_size);
|
|
|
|
memset(&net, 0, sizeof(net));
|
|
|
|
|
|
|
|
/* if the connection index is out of range, we simply return with no error
|
|
|
|
* having first cleared the client-supplied buffer. */
|
|
|
|
if ( (item!=COUNT) && (connection_index > p_monitor->hash.size - 1) )
|
|
|
|
return(0);
|
|
|
|
|
|
|
|
switch (item) {
|
|
|
|
|
|
|
|
case COUNT:
|
|
|
|
|
|
|
|
snprintf( p_buffer, buffer_size, "%d" , p_monitor->hash.size );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REMOTEIP:
|
|
|
|
|
|
|
|
net.s_addr = p_monitor->p_peek[ connection_index ]->remote_addr;
|
|
|
|
snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REMOTEHOST:
|
|
|
|
|
|
|
|
p_hostent = gethostbyaddr( &p_monitor->p_peek[ connection_index ]->remote_addr, sizeof(in_addr_t), AF_INET);
|
|
|
|
/* if no host name found, just use ip address. */
|
|
|
|
if ( !p_hostent || !p_hostent->h_name )
|
|
|
|
{
|
|
|
|
net.s_addr = p_monitor->p_peek[ connection_index ]->remote_addr;
|
|
|
|
snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
snprintf( p_buffer, buffer_size, "%s", p_hostent->h_name );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REMOTEPORT:
|
|
|
|
|
|
|
|
snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->remote_port );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LOCALIP:
|
|
|
|
|
|
|
|
net.s_addr = p_monitor->p_peek[ connection_index ]->local_addr;
|
|
|
|
snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LOCALHOST:
|
|
|
|
|
|
|
|
p_hostent = gethostbyaddr( &p_monitor->p_peek[ connection_index ]->local_addr, sizeof(in_addr_t), AF_INET);
|
|
|
|
/* if no host name found, just use ip address. */
|
|
|
|
if ( !p_hostent || !p_hostent->h_name )
|
|
|
|
{
|
|
|
|
net.s_addr = p_monitor->p_peek[ connection_index ]->local_addr;
|
|
|
|
snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
snprintf( p_buffer, buffer_size, "%s", p_hostent->h_name );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LOCALPORT:
|
|
|
|
|
|
|
|
snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->local_port );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LOCALSERVICE:
|
|
|
|
|
|
|
|
p_servent = getservbyport( htons(p_monitor->p_peek[ connection_index ]->local_port ), "tcp" );
|
|
|
|
/* if no service name found for the port, just use the port number. */
|
|
|
|
if ( !p_servent || !p_servent->s_name )
|
|
|
|
{
|
|
|
|
snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->local_port );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
snprintf( p_buffer, buffer_size, "%s", p_servent->s_name );
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------
|
|
|
|
* Client operations on collections
|
|
|
|
* -------------------------------- */
|
|
|
|
|
|
|
|
/* Create a monitor collection. Do this one first. */
|
|
|
|
tcp_port_monitor_collection_t * create_tcp_port_monitor_collection( void )
|
|
|
|
{
|
|
|
|
tcp_port_monitor_collection_t * p_collection;
|
|
|
|
|
|
|
|
p_collection = (tcp_port_monitor_collection_t *) calloc( 1, sizeof( tcp_port_monitor_collection_t ) );
|
|
|
|
if ( !p_collection )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* create the collection's monitor hash */
|
|
|
|
if ( hash_create( &p_collection->hash, TCP_MONITOR_HASH_SIZE,
|
|
|
|
&monitor_hash_function_1, &monitor_hash_function_2,
|
|
|
|
&monitor_match_function, NULL ) != 0 )
|
|
|
|
{
|
|
|
|
/* we failed to create the hash, so destroy the monitor completely so we don't leak */
|
|
|
|
destroy_tcp_port_monitor_collection(p_collection);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
p_collection->monitor_list.p_head = NULL;
|
|
|
|
p_collection->monitor_list.p_tail = NULL;
|
|
|
|
|
|
|
|
return p_collection;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Destroy the monitor collection (and the monitors inside). Do this one last. */
|
|
|
|
void destroy_tcp_port_monitor_collection(
|
|
|
|
tcp_port_monitor_collection_t * p_collection
|
|
|
|
)
|
|
|
|
{
|
|
|
|
tcp_port_monitor_node_t * p_current_node, * p_next_node;
|
|
|
|
|
|
|
|
if ( !p_collection )
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* destroy the collection's hash */
|
|
|
|
hash_destroy( &p_collection->hash );
|
|
|
|
|
|
|
|
/* destroy the monitors */
|
|
|
|
for_each_tcp_port_monitor_in_collection(
|
|
|
|
p_collection,
|
|
|
|
&destroy_tcp_port_monitor,
|
|
|
|
NULL
|
|
|
|
);
|
|
|
|
|
|
|
|
/* next destroy the empty monitor nodes */
|
|
|
|
for ( p_current_node = p_collection->monitor_list.p_head; p_current_node != NULL; )
|
|
|
|
{
|
|
|
|
p_next_node = p_current_node->p_next; /* do this first! */
|
|
|
|
|
|
|
|
free( p_current_node );
|
|
|
|
p_current_node = p_next_node;
|
|
|
|
}
|
|
|
|
|
|
|
|
free( p_collection );
|
|
|
|
}
|
|
|
|
|
2005-11-01 00:58:34 +00:00
|
|
|
/* Updates the tcp statistics for all monitors within a collection */
|
2005-10-31 05:17:06 +00:00
|
|
|
void update_tcp_port_monitor_collection(
|
|
|
|
tcp_port_monitor_collection_t * p_collection
|
|
|
|
)
|
|
|
|
{
|
|
|
|
FILE *fp;
|
|
|
|
char buf[256];
|
|
|
|
tcp_connection_t conn;
|
|
|
|
unsigned long state;
|
|
|
|
|
|
|
|
if ( !p_collection )
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* age the connections in all port monitors. */
|
|
|
|
for_each_tcp_port_monitor_in_collection(
|
|
|
|
p_collection,
|
|
|
|
&age_tcp_port_monitor,
|
|
|
|
NULL
|
|
|
|
);
|
|
|
|
|
|
|
|
/* read tcp data from /proc/net/tcp */
|
|
|
|
if ( ( fp = fopen("/proc/net/tcp", "r" ) ) == NULL )
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* ignore field name line */
|
|
|
|
fgets(buf, 255, fp);
|
|
|
|
|
|
|
|
/* 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 )
|
|
|
|
|
|
|
|
fprintf( stderr, "/proc/net/tcp: bad file format\n" );
|
|
|
|
|
|
|
|
if ((conn.inode == 0) || (state != TCP_ESTABLISHED)) continue;
|
|
|
|
|
|
|
|
/* show the connection to each port monitor. */
|
|
|
|
for_each_tcp_port_monitor_in_collection(
|
|
|
|
p_collection,
|
|
|
|
&show_connection_to_tcp_port_monitor,
|
|
|
|
(void *) &conn
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
/* check the health of the monitor hashes and rebuild them if nedded */
|
|
|
|
for_each_tcp_port_monitor_in_collection(
|
|
|
|
p_collection,
|
|
|
|
&maintain_tcp_port_monitor_hash,
|
|
|
|
NULL
|
|
|
|
);
|
|
|
|
|
|
|
|
/* rebuild the connection peek tables of all monitors so clients can peek in O(1) time */
|
|
|
|
for_each_tcp_port_monitor_in_collection(
|
|
|
|
p_collection,
|
|
|
|
&rebuild_tcp_port_monitor_peek_table,
|
|
|
|
NULL
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* After clients create a monitor, use this to add it to the collection.
|
|
|
|
Returns 0 on success, -1 otherwise. */
|
|
|
|
int insert_tcp_port_monitor_into_collection(
|
|
|
|
tcp_port_monitor_collection_t * p_collection,
|
|
|
|
tcp_port_monitor_t * p_monitor
|
|
|
|
)
|
|
|
|
{
|
|
|
|
tcp_port_monitor_node_t * p_node;
|
|
|
|
|
|
|
|
if ( !p_collection || !p_monitor )
|
|
|
|
return (-1);
|
|
|
|
|
|
|
|
/* create a container node for this monitor */
|
|
|
|
p_node = (tcp_port_monitor_node_t *) calloc( 1, sizeof(tcp_port_monitor_node_t) );
|
|
|
|
if ( !p_node )
|
|
|
|
return (-1);
|
|
|
|
|
|
|
|
/* populate the node */
|
|
|
|
p_node->p_monitor = p_monitor;
|
|
|
|
p_node->p_next = NULL;
|
|
|
|
|
|
|
|
/* add a pointer to this monitor to the collection's hash */
|
|
|
|
if ( hash_insert( &p_collection->hash, (void *)p_monitor ) != 0 )
|
|
|
|
{
|
|
|
|
/* error inserting into hash. destroy the monitor's container node so no leaks */
|
|
|
|
free( p_node );
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* tail of the container gets this node */
|
|
|
|
if ( !p_collection->monitor_list.p_tail )
|
|
|
|
p_collection->monitor_list.p_tail = p_node;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* p_next of the tail better be NULL */
|
|
|
|
if ( p_collection->monitor_list.p_tail->p_next != NULL )
|
|
|
|
return (-1);
|
|
|
|
|
|
|
|
/* splice node onto tail */
|
|
|
|
p_collection->monitor_list.p_tail->p_next = p_node;
|
|
|
|
p_collection->monitor_list.p_tail = p_node;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if this was the first element added */
|
|
|
|
if ( !p_collection->monitor_list.p_head )
|
|
|
|
p_collection->monitor_list.p_head = p_collection->monitor_list.p_tail;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clients need a way to find monitors */
|
|
|
|
tcp_port_monitor_t * find_tcp_port_monitor(
|
|
|
|
tcp_port_monitor_collection_t * p_collection,
|
|
|
|
in_port_t port_range_begin,
|
|
|
|
in_port_t port_range_end
|
|
|
|
)
|
|
|
|
{
|
|
|
|
tcp_port_monitor_t monitor,*p_monitor;
|
|
|
|
void *p_cast;
|
|
|
|
|
|
|
|
if ( !p_collection )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* need a monitor object to use for searching the hash */
|
|
|
|
monitor.port_range_begin = port_range_begin;
|
|
|
|
monitor.port_range_end = port_range_end;
|
|
|
|
p_monitor = &monitor;
|
|
|
|
p_cast = (void *)p_monitor;
|
|
|
|
|
|
|
|
/* simple hash table lookup */
|
|
|
|
if ( hash_lookup( &p_collection->hash, &p_cast ) == 0 )
|
|
|
|
{
|
|
|
|
/* found the monitor and p_cast now points to it */
|
|
|
|
p_monitor = (tcp_port_monitor_t *)p_cast;
|
|
|
|
return( p_monitor );
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL; /* monitor not found */
|
|
|
|
}
|