3 /* libtcp-portmon.c: tcp port monitoring library.
5 * Copyright (C) 2005-2007 Philip Kovacs pkovacs@users.sourceforge.net
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
22 #include <glib/gprintf.h>
23 #include "libtcp-portmon.h"
25 /* -------------------------------------------------------------------
26 * IMPLEMENTATION INTERFACE
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 * ------------------------------------------------------------------- */
33 /* -----------------------------------
34 * Copy a tcp_connection_t
36 * Returns 0 on success, -1 otherwise.
37 * ----------------------------------- */
38 int copy_tcp_connection(tcp_connection_t *p_dest_connection,
39 const tcp_connection_t *p_source_connection)
41 if (!p_dest_connection || !p_source_connection) {
45 g_strlcpy(p_dest_connection->key, p_source_connection->key,
46 sizeof(p_dest_connection->key));
47 p_dest_connection->local_addr = p_source_connection->local_addr;
48 p_dest_connection->local_port = p_source_connection->local_port;
49 p_dest_connection->remote_addr = p_source_connection->remote_addr;
50 p_dest_connection->remote_port = p_source_connection->remote_port;
51 p_dest_connection->age = p_source_connection->age;
56 /* -------------------------------------------
57 * Port monitor utility functions implementing
58 * tcp_port_monitor_function_ptr_t
59 * ------------------------------------------- */
60 void destroy_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
62 tcp_connection_node_t *p_node, *p_temp;
64 if (!p_monitor || p_void) { /* p_void should be NULL in this context */
68 /* destroy the monitor's peek array */
69 free(p_monitor->p_peek);
71 /* destroy the monitor's connection list */
72 for (p_node = p_monitor->connection_list.p_head; p_node != NULL; ) {
73 /* p_temp is for the next iteration */
74 p_temp = p_node->p_next;
81 /* destroy the monitor's hash */
82 g_hash_table_destroy(p_monitor->hash);
83 p_monitor->hash = NULL;
85 /* destroy the monitor */
90 void age_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
92 /* Run through the monitor's connections and decrement the age variable.
93 * If the age goes negative, we remove the connection from the monitor.
94 * Function takes O(n) time on the number of connections. */
96 tcp_connection_node_t *p_node, *p_temp;
97 tcp_connection_t *p_conn;
99 if (!p_monitor || p_void) { /* p_void should be NULL in this context */
103 if (!p_monitor->p_peek) {
107 for (p_node = p_monitor->connection_list.p_head; p_node; ) {
108 if (--p_node->connection.age >= 0) {
109 p_node = p_node->p_next;
113 /* connection on p_node is old. remove connection from the hash. */
114 p_conn = &p_node->connection;
116 fprintf(stderr, "monitor hash removal of connection [%s]", p_conn->key);
117 if (!g_hash_table_remove(p_monitor->hash,
118 (gconstpointer) p_conn->key)) {
119 fprintf(stderr, " - ERROR NOT FOUND\n");
122 fprintf(stderr, " - OK\n");
124 if (!g_hash_table_remove(p_monitor->hash,
125 (gconstpointer) p_conn->key)) {
130 /* splice p_node out of the connection_list */
131 if (p_node->p_prev != NULL) {
132 p_node->p_prev->p_next = p_node->p_next;
134 if (p_node->p_next != NULL) {
135 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;
142 if (p_monitor->connection_list.p_tail == p_node) {
143 p_monitor->connection_list.p_tail = p_node->p_prev;
146 /* p_temp is for the next iteration */
147 p_temp = p_node->p_next;
149 /* destroy the node */
156 void rebuild_tcp_port_monitor_peek_table(tcp_port_monitor_t *p_monitor,
159 /* Run through the monitor's connections and rebuild the peek table of
160 * connection pointers. This is done so peeking into the monitor can be
161 * 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 */
170 /* zero out the peek array */
171 memset(p_monitor->p_peek, 0, p_monitor->max_port_monitor_connections *
172 sizeof(tcp_connection_t *));
174 for (p_node = p_monitor->connection_list.p_head; p_node != NULL;
175 p_node = p_node->p_next, i++) {
176 p_monitor->p_peek[i] = &p_node->connection;
180 void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
183 /* The monitor gets to look at each connection to see if it falls within
184 * the monitor's port range of interest. Connections of interest are first
185 * looked up in the hash to see if they are already there. If they are, we
186 * reset the age of the connection so it is not deleted. If the connection
187 * is not in the hash, we add it, but only if we haven't exceeded the
188 * maximum connection limit for the monitor.
189 * 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) {
198 /* This p_connection is on caller's stack and not the heap.
199 * If we are interested, we will create a copy of the connection
200 * (on the heap) and add it to our list. */
201 p_connection = (tcp_connection_t *) p_void;
203 /* inspect the local port number of the connection to see if we're
205 if ((p_monitor->port_range_begin <= p_connection->local_port)
206 && (p_connection->local_port <= p_monitor->port_range_end)) {
207 /* the connection is in the range of the monitor. */
209 /* first check the hash to see if the connection is already there. */
210 if ((p_conn_hash = g_hash_table_lookup(p_monitor->hash,
211 (gconstpointer) p_connection->key))) {
212 /* it's already in the hash. reset the age of the connection. */
213 p_conn_hash->age = TCP_CONNECTION_STARTING_AGE;
218 /* Connection is not yet in the hash.
219 * Add it if max_connections not exceeded. */
220 if (g_hash_table_size(p_monitor->hash)
221 >= p_monitor->max_port_monitor_connections) {
225 /* create a new connection node */
226 if ((p_node = (tcp_connection_node_t *)
227 calloc(1, sizeof(tcp_connection_node_t))) == NULL) {
231 /* copy the connection data */
232 if (copy_tcp_connection(&p_node->connection, p_connection) != 0) {
233 /* error copying the connection data. deallocate p_node to
234 * avoid leaks and return. */
239 p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
240 p_node->p_next = NULL;
242 /* insert it into the monitor's hash table */
244 fprintf(stderr, "monitor hash insert of connection [%s]\n",
245 p_node->connection.key);
247 g_hash_table_insert(p_monitor->hash,
248 (gpointer) p_node->connection.key, (gpointer) &p_node->connection);
250 /* append the node to the monitor's connection list */
251 if (p_monitor->connection_list.p_tail == NULL) {
252 /* assume p_head is NULL too */
253 p_monitor->connection_list.p_head = p_node;
254 p_monitor->connection_list.p_tail = p_node;
255 p_node->p_prev = NULL;
257 p_monitor->connection_list.p_tail->p_next = p_node;
258 p_node->p_prev = p_monitor->connection_list.p_tail;
259 p_monitor->connection_list.p_tail = p_node;
264 /* ------------------------------------------------------------------------
265 * Apply a tcp_port_monitor_function_ptr_t function to each port monitor in
267 * ------------------------------------------------------------------------ */
268 void for_each_tcp_port_monitor_in_collection(
269 tcp_port_monitor_collection_t *p_collection,
270 tcp_port_monitor_function_ptr_t p_function, void *p_function_args)
272 tcp_port_monitor_node_t *p_current_node, *p_next_node;
274 if (!p_collection || !p_function) {
278 /* for each monitor in the collection */
279 for (p_current_node = p_collection->monitor_list.p_head;
280 p_current_node != NULL; p_current_node = p_next_node) {
281 p_next_node = p_current_node->p_next; /* do this first! */
283 if (p_current_node->p_monitor) {
284 /* apply the function with the given arguments */
285 p_function(p_current_node->p_monitor, p_function_args);
290 /* ----------------------------------------------------------------------
293 * Clients should call only those functions below this line.
294 * ---------------------------------------------------------------------- */
296 /* ----------------------------------
297 * Client operations on port monitors
298 * ---------------------------------- */
300 /* Clients should first try to "find_tcp_port_monitor" before creating one
301 * so that there are no redundant monitors. */
302 tcp_port_monitor_t *create_tcp_port_monitor(in_port_t port_range_begin,
303 in_port_t port_range_end, tcp_port_monitor_args_t *p_creation_args)
305 tcp_port_monitor_t *p_monitor;
307 /* create the monitor */
308 p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t));
313 p_monitor->max_port_monitor_connections =
314 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) {
321 /* we failed to create the hash, so destroy the monitor completely
322 * 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 **)
329 calloc(p_monitor->max_port_monitor_connections,
330 sizeof(tcp_connection_t *))) == NULL) {
331 /* we failed to create the peek array,
332 * so destroy the monitor completely, again, so we don't leak */
333 destroy_tcp_port_monitor(p_monitor, NULL);
337 p_monitor->port_range_begin = port_range_begin;
338 p_monitor->port_range_end = port_range_end;
340 p_monitor->connection_list.p_head = NULL;
341 p_monitor->connection_list.p_tail = NULL;
346 /* Clients use this function to get connection data from the indicated
348 * The requested monitor value is copied into a client-supplied char buffer.
349 * Returns 0 on success, -1 otherwise. */
350 int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
351 int connection_index, char *p_buffer, size_t buffer_size)
353 struct hostent *p_hostent;
354 struct servent *p_servent;
357 if (!p_monitor || !p_buffer || connection_index < 0) {
361 memset(p_buffer, 0, buffer_size);
362 memset(&net, 0, sizeof(net));
364 /* if the connection index is out of range, we simply return with no error,
365 * having first cleared the client-supplied buffer. */
366 if ((item != COUNT) && (connection_index
367 > (int) g_hash_table_size(p_monitor->hash) - 1)) {
375 snprintf(p_buffer, buffer_size, "%d",
376 g_hash_table_size(p_monitor->hash));
381 net.s_addr = p_monitor->p_peek[connection_index]->remote_addr;
382 snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
387 p_hostent = gethostbyaddr((const void *)
388 &p_monitor->p_peek[connection_index]->remote_addr,
389 sizeof(in_addr_t), AF_INET);
390 /* if no host name found, just use ip address. */
391 if (!p_hostent || !p_hostent->h_name) {
392 net.s_addr = p_monitor->p_peek[connection_index]->remote_addr;
393 snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
396 snprintf(p_buffer, buffer_size, "%s", p_hostent->h_name);
401 snprintf(p_buffer, buffer_size, "%d",
402 p_monitor->p_peek[connection_index]->remote_port);
407 p_servent = getservbyport(
408 htons(p_monitor->p_peek[connection_index]->remote_port), "tcp");
409 /* if no service name found for the port,
410 * just use the port number. */
411 if (!p_servent || !p_servent->s_name) {
412 snprintf(p_buffer, buffer_size, "%d",
413 p_monitor->p_peek[connection_index]->remote_port);
415 snprintf(p_buffer, buffer_size, "%s", p_servent->s_name);
421 net.s_addr = p_monitor->p_peek[connection_index]->local_addr;
422 snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
427 p_hostent = gethostbyaddr((const void *)
428 &p_monitor->p_peek[connection_index]->local_addr,
429 sizeof(in_addr_t), AF_INET);
430 /* if no host name found, just use ip address. */
431 if (!p_hostent || !p_hostent->h_name) {
432 net.s_addr = p_monitor->p_peek[connection_index]->local_addr;
433 snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
436 snprintf(p_buffer, buffer_size, "%s", p_hostent->h_name);
441 snprintf(p_buffer, buffer_size, "%d",
442 p_monitor->p_peek[connection_index]->local_port);
447 p_servent = getservbyport(
448 htons(p_monitor->p_peek[connection_index]->local_port), "tcp");
449 /* if no service name found for the port,
450 * just use the port number. */
451 if (!p_servent || !p_servent->s_name) {
452 snprintf(p_buffer, buffer_size, "%d",
453 p_monitor->p_peek[connection_index]->local_port);
456 snprintf(p_buffer, buffer_size, "%s", p_servent->s_name);
466 /* --------------------------------
467 * Client operations on collections
468 * -------------------------------- */
470 /* Create a monitor collection. Do this one first. */
471 tcp_port_monitor_collection_t *create_tcp_port_monitor_collection(void)
473 tcp_port_monitor_collection_t *p_collection;
475 p_collection = (tcp_port_monitor_collection_t *)
476 calloc(1, sizeof(tcp_port_monitor_collection_t));
481 /* create the collection's monitor hash */
482 if ((p_collection->hash = g_hash_table_new(g_str_hash, g_str_equal))
484 /* we failed to create the hash,
485 * so destroy the monitor completely so we don't leak */
486 destroy_tcp_port_monitor_collection(p_collection);
490 p_collection->monitor_list.p_head = NULL;
491 p_collection->monitor_list.p_tail = NULL;
496 /* Destroy the monitor collection (and the monitors inside).
497 * Do this one last. */
498 void destroy_tcp_port_monitor_collection(
499 tcp_port_monitor_collection_t *p_collection)
501 tcp_port_monitor_node_t *p_current_node, *p_next_node;
507 /* destroy the monitors */
508 for_each_tcp_port_monitor_in_collection(p_collection,
509 &destroy_tcp_port_monitor, NULL);
511 /* next destroy the empty monitor nodes */
512 for (p_current_node = p_collection->monitor_list.p_head;
513 p_current_node != NULL; p_current_node = p_next_node) {
514 p_next_node = p_current_node->p_next; /* do this first! */
516 free(p_current_node);
519 /* destroy the collection's hash */
520 g_hash_table_destroy(p_collection->hash);
521 p_collection->hash = NULL;
527 /* Updates the tcp statistics for all monitors within a collection */
528 void update_tcp_port_monitor_collection(
529 tcp_port_monitor_collection_t *p_collection)
533 tcp_connection_t conn;
534 unsigned long inode, uid, state;
540 /* age the connections in all port monitors. */
541 for_each_tcp_port_monitor_in_collection(p_collection,
542 &age_tcp_port_monitor, NULL);
544 /* read tcp data from /proc/net/tcp */
545 if ((fp = fopen("/proc/net/tcp", "r")) == NULL) {
549 /* ignore field name line */
552 /* read all tcp connections */
553 while (fgets(buf, sizeof(buf), fp) != NULL) {
556 "%*d: %x:%hx %x:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
557 (unsigned int *) &conn.local_addr, &conn.local_port,
558 (unsigned int *) &conn.remote_addr, &conn.remote_port,
559 (unsigned long *) &state, (unsigned long *) &uid,
560 (unsigned long *) &inode) != 7) {
561 fprintf(stderr, "/proc/net/tcp: bad file format\n");
563 /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
564 //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
565 if((inode == 0) || (state != 1)) {
570 g_sprintf(conn.key, "%08X:%04X %08X:%04X", conn.local_addr,
571 conn.local_port, conn.remote_addr, conn.remote_port);
573 /* show the connection to each port monitor. */
574 for_each_tcp_port_monitor_in_collection(p_collection,
575 &show_connection_to_tcp_port_monitor, (void *) &conn);
580 /* rebuild the connection peek tables of all monitors
581 * so clients can peek in O(1) time */
582 for_each_tcp_port_monitor_in_collection(p_collection,
583 &rebuild_tcp_port_monitor_peek_table, NULL);
586 /* After clients create a monitor, use this to add it to the collection.
587 * Returns 0 on success, -1 otherwise. */
588 int insert_tcp_port_monitor_into_collection(
589 tcp_port_monitor_collection_t *p_collection,
590 tcp_port_monitor_t *p_monitor)
592 tcp_port_monitor_node_t *p_node;
594 if (!p_collection || !p_monitor) {
598 /* create a container node for this monitor */
599 p_node = (tcp_port_monitor_node_t *)
600 calloc(1, sizeof(tcp_port_monitor_node_t));
605 /* populate the node */
606 p_node->p_monitor = p_monitor;
607 p_node->p_next = NULL;
609 /* add a pointer to this monitor to the collection's hash */
611 fprintf(stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
613 g_hash_table_insert(p_collection->hash, (gpointer) p_monitor->key,
614 (gpointer) p_monitor);
616 /* tail of the container gets this node */
617 if (!p_collection->monitor_list.p_tail) {
618 p_collection->monitor_list.p_tail = p_node;
620 /* p_next of the tail better be NULL */
621 if (p_collection->monitor_list.p_tail->p_next != NULL) {
625 /* splice node onto tail */
626 p_collection->monitor_list.p_tail->p_next = p_node;
627 p_collection->monitor_list.p_tail = p_node;
630 /* if this was the first element added */
631 if (!p_collection->monitor_list.p_head) {
632 p_collection->monitor_list.p_head = p_collection->monitor_list.p_tail;
638 /* Clients need a way to find monitors */
639 tcp_port_monitor_t *find_tcp_port_monitor(
640 const tcp_port_monitor_collection_t *p_collection,
641 in_port_t port_range_begin, in_port_t port_range_end)
643 tcp_port_monitor_t *p_monitor;
650 /* is monitor in hash? */
651 g_sprintf(key, ":%04X :%04X", port_range_begin, port_range_end);
652 p_monitor = g_hash_table_lookup(p_collection->hash, (gconstpointer) key);