1 /* -------------------------------------------------------------------------
2 * libtcp-portmon.c: tcp port monitoring library.
4 * Copyright (C) 2005 Philip Kovacs kovacsp3@comcast.net
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.
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.
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 * --------------------------------------------------------------------------- */
23 #include <glib/gprintf.h>
24 #include "libtcp-portmon.h"
26 /* -------------------------------------------------------------------
27 * IMPLEMENTATION INTERFACE
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 * ------------------------------------------------------------------- */
34 /* ----------------------------------
35 * Copy a tcp_connection_t
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
44 if ( !p_dest_connection || !p_source_connection )
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;
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,
65 tcp_connection_node_t *p_node, *p_temp;
67 if ( !p_monitor || p_void ) /* p_void should be NULL in this context */
70 /* destroy the monitor's peek array */
71 free( p_monitor->p_peek );
73 /* destroy the monitor's connection list */
74 for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; )
76 /* p_temp is for the next iteration */
77 p_temp = p_node->p_next;
84 /* destroy the monitor's hash */
85 g_hash_table_destroy (p_monitor->hash);
88 /* destroy the monitor */
93 void age_tcp_port_monitor(
94 tcp_port_monitor_t * p_monitor,
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. */
102 tcp_connection_node_t *p_node, *p_temp;
103 tcp_connection_t *p_conn;
105 if ( !p_monitor || p_void ) /* p_void should be NULL in this context */
108 if ( !p_monitor->p_peek )
111 for ( p_node = p_monitor->connection_list.p_head; p_node; )
113 if ( --p_node->connection.age >= 0 ) {
114 p_node = p_node->p_next;
118 /* connection on p_node is old. remove connection from the hash. */
119 p_conn = &p_node->connection;
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");
126 fprintf (stderr, " - OK\n");
128 if ( !g_hash_table_remove (p_monitor->hash, (gconstpointer)p_conn->key) )
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;
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;
144 /* p_temp is for the next iteration */
145 p_temp = p_node->p_next;
147 /* destroy the node */
154 void rebuild_tcp_port_monitor_peek_table(
155 tcp_port_monitor_t * p_monitor,
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. */
163 tcp_connection_node_t *p_node;
166 if ( !p_monitor || p_void ) /* p_void should be NULL in this context */
169 /* zero out the peek array */
170 memset( p_monitor->p_peek, 0, p_monitor->max_port_monitor_connections * sizeof(tcp_connection_t *) );
172 for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; p_node=p_node->p_next, i++ )
174 p_monitor->p_peek[i] = &p_node->connection;
179 void show_connection_to_tcp_port_monitor(
180 tcp_port_monitor_t * p_monitor,
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. */
191 tcp_connection_node_t *p_node;
192 tcp_connection_t *p_connection, *p_conn_hash;
194 if ( !p_monitor || !p_void )
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;
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) )
205 /* the connection is in the range of the monitor. */
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)) )
210 /* it's already in the hash. reset the age of the connection. */
211 p_conn_hash->age = TCP_CONNECTION_STARTING_AGE;
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)
220 /* create a new connection node */
221 if ( (p_node = (tcp_connection_node_t *) calloc(1, sizeof(tcp_connection_node_t))) == NULL )
224 /* copy the connection data */
225 if ( copy_tcp_connection( &p_node->connection, p_connection ) != 0 )
227 /* error copying the connection data. deallocate p_node to avoid leaks and return. */
232 p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
233 p_node->p_next = NULL;
235 /* insert it into the monitor's hash table */
237 fprintf (stderr, "monitor hash insert of connection [%s]\n", p_node->connection.key);
239 g_hash_table_insert( p_monitor->hash,
240 (gpointer)p_node->connection.key,
241 (gpointer)&p_node->connection);
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 */
246 p_monitor->connection_list.p_head = p_node;
247 p_monitor->connection_list.p_tail = p_node;
248 p_node->p_prev = NULL;
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;
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
268 tcp_port_monitor_node_t * p_current_node, * p_next_node;
270 if ( !p_collection || !p_function )
273 /* for each monitor in the collection */
274 for ( p_current_node = p_collection->monitor_list.p_head; p_current_node != NULL; )
276 p_next_node = p_current_node->p_next; /* do this first! */
278 if ( p_current_node->p_monitor )
280 /* apply the function with the given arguments */
281 (*p_function)( p_current_node->p_monitor, p_function_args );
284 p_current_node = p_next_node;
289 /* ----------------------------------------------------------------------
292 * Clients should call only those functions below this line.
293 * ---------------------------------------------------------------------- */
295 /* ----------------------------------
296 * Client operations on port monitors
297 * ---------------------------------- */
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
307 tcp_port_monitor_t * p_monitor;
309 /* create the monitor */
310 p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t) );
314 p_monitor->max_port_monitor_connections = p_creation_args->max_port_monitor_connections;
316 /* build the monitor key for the collection hash */
317 g_sprintf (p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
319 /* create the monitor's connection hash */
320 if ( (p_monitor->hash = g_hash_table_new (g_str_hash, g_str_equal)) == NULL)
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);
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 )
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);
336 p_monitor->port_range_begin = port_range_begin;
337 p_monitor->port_range_end = port_range_end;
339 p_monitor->connection_list.p_head = NULL;
340 p_monitor->connection_list.p_tail = NULL;
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,
351 int connection_index,
356 struct hostent *p_hostent;
357 struct servent *p_servent;
360 if ( !p_monitor || !p_buffer || connection_index < 0 )
363 memset(p_buffer, 0, buffer_size);
364 memset(&net, 0, sizeof(net));
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) )
375 snprintf( p_buffer, buffer_size, "%d" , g_hash_table_size (p_monitor->hash) );
380 net.s_addr = p_monitor->p_peek[ connection_index ]->remote_addr;
381 snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
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 )
391 net.s_addr = p_monitor->p_peek[ connection_index ]->remote_addr;
392 snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
395 snprintf( p_buffer, buffer_size, "%s", p_hostent->h_name );
400 snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->remote_port );
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 );
410 snprintf( p_buffer, buffer_size, "%s", p_servent->s_name );
416 net.s_addr = p_monitor->p_peek[ connection_index ]->local_addr;
417 snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
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 )
427 net.s_addr = p_monitor->p_peek[ connection_index ]->local_addr;
428 snprintf( p_buffer, buffer_size, "%s", inet_ntoa( net ) );
431 snprintf( p_buffer, buffer_size, "%s", p_hostent->h_name );
436 snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->local_port );
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 )
445 snprintf( p_buffer, buffer_size, "%d", p_monitor->p_peek[ connection_index ]->local_port );
448 snprintf( p_buffer, buffer_size, "%s", p_servent->s_name );
458 /* --------------------------------
459 * Client operations on collections
460 * -------------------------------- */
462 /* Create a monitor collection. Do this one first. */
463 tcp_port_monitor_collection_t * create_tcp_port_monitor_collection (void)
465 tcp_port_monitor_collection_t * p_collection;
467 p_collection = (tcp_port_monitor_collection_t *) calloc( 1, sizeof( tcp_port_monitor_collection_t ) );
471 /* create the collection's monitor hash */
472 if ( (p_collection->hash = g_hash_table_new (g_str_hash, g_str_equal)) == NULL)
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);
479 p_collection->monitor_list.p_head = NULL;
480 p_collection->monitor_list.p_tail = NULL;
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
490 tcp_port_monitor_node_t * p_current_node, * p_next_node;
495 /* destroy the monitors */
496 for_each_tcp_port_monitor_in_collection(
498 &destroy_tcp_port_monitor,
502 /* next destroy the empty monitor nodes */
503 for ( p_current_node = p_collection->monitor_list.p_head; p_current_node != NULL; )
505 p_next_node = p_current_node->p_next; /* do this first! */
507 free( p_current_node );
508 p_current_node = p_next_node;
511 /* destroy the collection's hash */
512 g_hash_table_destroy (p_collection->hash);
513 p_collection->hash = NULL;
515 free( p_collection );
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
526 tcp_connection_t conn;
527 unsigned long inode,uid,state;
532 /* age the connections in all port monitors. */
533 for_each_tcp_port_monitor_in_collection(
535 &age_tcp_port_monitor,
539 /* read tcp data from /proc/net/tcp */
540 if ( ( fp = fopen("/proc/net/tcp", "r" ) ) == NULL )
543 /* ignore field name line */
546 /* read all tcp connections */
547 while (fgets (buf, sizeof (buf), fp) != NULL) {
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 )
554 fprintf( stderr, "/proc/net/tcp: bad file format\n" );
556 if ((inode == 0) || (state != TCP_ESTABLISHED)) continue;
559 g_sprintf (conn.key, "%08X:%04X %08X:%04X",
560 conn.local_addr, conn.local_port,
561 conn.remote_addr, conn.remote_port);
563 /* show the connection to each port monitor. */
564 for_each_tcp_port_monitor_in_collection(
566 &show_connection_to_tcp_port_monitor,
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(
576 &rebuild_tcp_port_monitor_peek_table,
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
588 tcp_port_monitor_node_t * p_node;
590 if ( !p_collection || !p_monitor )
593 /* create a container node for this monitor */
594 p_node = (tcp_port_monitor_node_t *) calloc( 1, sizeof(tcp_port_monitor_node_t) );
598 /* populate the node */
599 p_node->p_monitor = p_monitor;
600 p_node->p_next = NULL;
602 /* add a pointer to this monitor to the collection's hash */
604 fprintf (stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
606 g_hash_table_insert (p_collection->hash, (gpointer)p_monitor->key, (gpointer)p_monitor);
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;
613 /* p_next of the tail better be NULL */
614 if ( p_collection->monitor_list.p_tail->p_next != NULL )
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;
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;
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
636 tcp_port_monitor_t *p_monitor;
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);