1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2 * vim: ts=4 sw=4 noet ai cindent syntax=c
4 * libtcp-portmon.c: tcp port monitoring library.
6 * Copyright (C) 2005-2007 Philip Kovacs pkovacs@users.sourceforge.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
29 #include "libtcp-portmon.h"
30 #include <glib/gprintf.h>
32 /* -------------------------------------------------------------------
33 * IMPLEMENTATION INTERFACE
35 * Implementation-specific interface begins here. Clients should not
36 * manipulate these structures directly, nor call the defined helper
37 * functions. Use the "Client interface" functions defined at bottom.
38 * ------------------------------------------------------------------- */
40 /* -----------------------------------
41 * Copy a tcp_connection_t
43 * Returns 0 on success, -1 otherwise.
44 * ----------------------------------- */
45 int copy_tcp_connection(tcp_connection_t *p_dest_connection,
46 const tcp_connection_t *p_source_connection)
48 if (!p_dest_connection || !p_source_connection) {
52 p_dest_connection->local_addr = p_source_connection->local_addr;
53 p_dest_connection->local_port = p_source_connection->local_port;
54 p_dest_connection->remote_addr = p_source_connection->remote_addr;
55 p_dest_connection->remote_port = p_source_connection->remote_port;
56 p_dest_connection->age = p_source_connection->age;
61 /* -------------------------------------------
62 * Port monitor utility functions implementing
63 * tcp_port_monitor_function_ptr_t
64 * ------------------------------------------- */
65 void destroy_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
67 tcp_connection_node_t *p_node, *p_temp;
69 if (!p_monitor || p_void) { /* p_void should be NULL in this context */
73 /* destroy the monitor's peek array */
74 free(p_monitor->p_peek);
76 /* destroy the monitor's connection list */
77 for (p_node = p_monitor->connection_list.p_head; p_node != NULL; ) {
78 /* p_temp is for the next iteration */
79 p_temp = p_node->p_next;
86 /* destroy the monitor's hash */
87 g_hash_table_destroy(p_monitor->hash);
88 p_monitor->hash = NULL;
90 /* destroy the monitor */
95 void age_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
97 /* Run through the monitor's connections and decrement the age variable.
98 * If the age goes negative, we remove the connection from the monitor.
99 * Function takes O(n) time on the number of connections. */
101 tcp_connection_node_t *p_node, *p_temp;
102 tcp_connection_t *p_conn;
104 if (!p_monitor || p_void) { /* p_void should be NULL in this context */
108 if (!p_monitor->p_peek) {
112 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,
123 (gconstpointer) p_conn->key)) {
124 fprintf(stderr, " - ERROR NOT FOUND\n");
127 fprintf(stderr, " - OK\n");
129 if (!g_hash_table_remove(p_monitor->hash,
130 (gconstpointer) p_conn)) {
135 /* splice p_node out of the connection_list */
136 if (p_node->p_prev != NULL) {
137 p_node->p_prev->p_next = p_node->p_next;
139 if (p_node->p_next != NULL) {
140 p_node->p_next->p_prev = p_node->p_prev;
143 /* correct the list head and tail if necessary */
144 if (p_monitor->connection_list.p_head == p_node) {
145 p_monitor->connection_list.p_head = p_node->p_next;
147 if (p_monitor->connection_list.p_tail == p_node) {
148 p_monitor->connection_list.p_tail = p_node->p_prev;
151 /* p_temp is for the next iteration */
152 p_temp = p_node->p_next;
154 /* destroy the node */
161 void rebuild_tcp_port_monitor_peek_table(tcp_port_monitor_t *p_monitor,
164 /* Run through the monitor's connections and rebuild the peek table of
165 * connection pointers. This is done so peeking into the monitor can be
166 * done in O(1) time instead of O(n) time for each peek. */
168 tcp_connection_node_t *p_node;
171 if (!p_monitor || p_void) { /* p_void should be NULL in this context */
175 /* zero out the peek array */
176 memset(p_monitor->p_peek, 0, p_monitor->max_port_monitor_connections *
177 sizeof(tcp_connection_t *));
179 for (p_node = p_monitor->connection_list.p_head; p_node != NULL;
180 p_node = p_node->p_next, i++) {
181 p_monitor->p_peek[i] = &p_node->connection;
185 void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
188 /* The monitor gets to look at each connection to see if it falls within
189 * the monitor's port range of interest. Connections of interest are first
190 * looked up in the hash to see if they are already there. If they are, we
191 * reset the age of the connection so it is not deleted. If the connection
192 * is not in the hash, we add it, but only if we haven't exceeded the
193 * maximum connection limit for the monitor.
194 * The function takes O(1) time. */
196 tcp_connection_node_t *p_node;
197 tcp_connection_t *p_connection, *p_conn_hash;
199 if (!p_monitor || !p_void) {
203 /* This p_connection is on caller's stack and not the heap.
204 * If we are interested, we will create a copy of the connection
205 * (on the heap) and add it to our list. */
206 p_connection = (tcp_connection_t *) p_void;
208 /* inspect the local port number of the connection to see if we're
210 if ((p_monitor->port_range_begin <= p_connection->local_port)
211 && (p_connection->local_port <= p_monitor->port_range_end)) {
212 /* the connection is in the range of the monitor. */
214 /* first check the hash to see if the connection is already there. */
215 if ((p_conn_hash = g_hash_table_lookup(p_monitor->hash,
216 (gconstpointer) p_connection))) {
217 /* it's already in the hash. reset the age of the connection. */
218 p_conn_hash->age = TCP_CONNECTION_STARTING_AGE;
223 /* Connection is not yet in the hash.
224 * Add it if max_connections not exceeded. */
225 if (g_hash_table_size(p_monitor->hash)
226 >= p_monitor->max_port_monitor_connections) {
230 /* create a new connection node */
231 if ((p_node = (tcp_connection_node_t *)
232 calloc(1, sizeof(tcp_connection_node_t))) == NULL) {
236 /* copy the connection data */
237 if (copy_tcp_connection(&p_node->connection, p_connection) != 0) {
238 /* error copying the connection data. deallocate p_node to
239 * avoid leaks and return. */
244 p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
245 p_node->p_next = NULL;
247 /* insert it into the monitor's hash table */
249 fprintf(stderr, "monitor hash insert of connection [%s]\n",
250 p_node->connection.key);
252 g_hash_table_insert(p_monitor->hash,
253 (gpointer) &p_node->connection, (gpointer) &p_node->connection);
255 /* append the node to the monitor's connection list */
256 if (p_monitor->connection_list.p_tail == NULL) {
257 /* assume p_head is NULL too */
258 p_monitor->connection_list.p_head = p_node;
259 p_monitor->connection_list.p_tail = p_node;
260 p_node->p_prev = NULL;
262 p_monitor->connection_list.p_tail->p_next = p_node;
263 p_node->p_prev = p_monitor->connection_list.p_tail;
264 p_monitor->connection_list.p_tail = p_node;
269 /* ------------------------------------------------------------------------
270 * Apply a tcp_port_monitor_function_ptr_t function to each port monitor in
272 * ------------------------------------------------------------------------ */
273 void for_each_tcp_port_monitor_in_collection(
274 tcp_port_monitor_collection_t *p_collection,
275 tcp_port_monitor_function_ptr_t p_function, void *p_function_args)
277 tcp_port_monitor_node_t *p_current_node, *p_next_node;
279 if (!p_collection || !p_function) {
283 /* for each monitor in the collection */
284 for (p_current_node = p_collection->monitor_list.p_head;
285 p_current_node != NULL; p_current_node = p_next_node) {
286 p_next_node = p_current_node->p_next; /* do this first! */
288 if (p_current_node->p_monitor) {
289 /* apply the function with the given arguments */
290 p_function(p_current_node->p_monitor, p_function_args);
295 static const unsigned char prefix_4on6[] = {
296 0x00, 0x00, 0x00, 0x00,
297 0x00, 0x00, 0x00, 0x00,
298 0x00, 0x00, 0xff, 0xff
301 union sockaddr_in46 {
302 struct sockaddr_in sa4;
303 struct sockaddr_in6 sa6;
307 /* checks whether the address is a IPv4-mapped IPv6 address */
308 static int is_4on6(const struct in6_addr *addr)
310 return ! memcmp(&addr->s6_addr, prefix_4on6, sizeof(prefix_4on6));
314 /* converts the address to appropriate textual representation (IPv6, IPv4 or fqdn) */
315 static void print_host(char *p_buffer, size_t buffer_size, const struct in6_addr *addr, int fqdn)
317 union sockaddr_in46 sa;
320 memset(&sa, 0, sizeof(sa));
323 sa.sa4.sin_family = AF_INET;
324 memcpy(&sa.sa4.sin_addr.s_addr, &addr->s6_addr[12], 4);
325 slen = sizeof(sa.sa4);
327 sa.sa6.sin6_family = AF_INET6;
328 memcpy(&sa.sa6.sin6_addr, addr, sizeof(struct in6_addr));
329 slen = sizeof(sa.sa6);
332 getnameinfo(&sa.sa, slen, p_buffer, buffer_size, NULL, 0, fqdn?0:NI_NUMERICHOST);
335 /* converts the textual representation of an IPv4 or IPv6 address to struct in6_addr */
336 static void string_to_addr(struct in6_addr *addr, const char *p_buffer)
340 if(strlen(p_buffer) < 32) { //IPv4 address
341 i = sizeof(prefix_4on6);
342 memcpy(addr->s6_addr, prefix_4on6, i);
347 for( ; i < sizeof(addr->s6_addr); i+=4, p_buffer+=8) {
348 sscanf(p_buffer, "%8x", (unsigned *)&addr->s6_addr[i]);
352 /* hash function for tcp_connections */
353 static guint tcp_connection_hash(gconstpointer A)
355 const tcp_connection_t *a = (const tcp_connection_t *) A;
359 hash = hash*47 + a->local_port;
360 hash = hash*47 + a->remote_port;
361 for(i = 0; i < sizeof(a->local_addr.s6_addr); ++i)
362 hash = hash*47 + a->local_addr.s6_addr[i];
363 for(i = 0; i < sizeof(a->remote_addr.s6_addr); ++i)
364 hash = hash*47 + a->remote_addr.s6_addr[i];
369 /* comparison function for tcp_connections */
370 static gboolean tcp_connection_equal(gconstpointer A, gconstpointer B)
372 const tcp_connection_t *a = (const tcp_connection_t *) A;
373 const tcp_connection_t *b = (const tcp_connection_t *) B;
375 return a->local_port == b->local_port && a->remote_port == b->remote_port &&
376 ! memcmp(&a->local_addr, &b->local_addr, sizeof(a->local_addr)) &&
377 ! memcmp(&a->remote_addr.s6_addr, &b->remote_addr, sizeof(a->remote_addr));
380 /* adds connections from file to the collection */
381 static void process_file(tcp_port_monitor_collection_t *p_collection, const char *file)
386 char remote_addr[40];
387 tcp_connection_t conn;
388 unsigned long inode, uid, state;
390 if ((fp = fopen(file, "r")) == NULL) {
394 /* ignore field name line */
397 /* read all tcp connections */
398 while (fgets(buf, sizeof(buf), fp) != NULL) {
401 "%*d: %39[0-9a-fA-F]:%hx %39[0-9a-fA-F]:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
402 local_addr, &conn.local_port,
403 remote_addr, &conn.remote_port,
404 (unsigned long *) &state, (unsigned long *) &uid,
405 (unsigned long *) &inode) != 7) {
406 fprintf(stderr, "/proc/net/tcp: bad file format\n");
408 /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
409 //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
410 if((inode == 0) || (state != 1)) {
414 string_to_addr(&conn.local_addr, local_addr);
415 string_to_addr(&conn.remote_addr, remote_addr);
417 /* show the connection to each port monitor. */
418 for_each_tcp_port_monitor_in_collection(p_collection,
419 &show_connection_to_tcp_port_monitor, (void *) &conn);
425 /* ----------------------------------------------------------------------
428 * Clients should call only those functions below this line.
429 * ---------------------------------------------------------------------- */
431 /* ----------------------------------
432 * Client operations on port monitors
433 * ---------------------------------- */
435 /* Clients should first try to "find_tcp_port_monitor" before creating one
436 * so that there are no redundant monitors. */
437 tcp_port_monitor_t *create_tcp_port_monitor(in_port_t port_range_begin,
438 in_port_t port_range_end, tcp_port_monitor_args_t *p_creation_args)
440 tcp_port_monitor_t *p_monitor;
442 /* create the monitor */
443 p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t));
448 p_monitor->max_port_monitor_connections =
449 p_creation_args->max_port_monitor_connections;
451 /* build the monitor key for the collection hash */
452 g_sprintf(p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
454 /* create the monitor's connection hash */
455 if ((p_monitor->hash = g_hash_table_new(tcp_connection_hash, tcp_connection_equal)) == NULL) {
456 /* we failed to create the hash, so destroy the monitor completely
457 * so we don't leak */
458 destroy_tcp_port_monitor(p_monitor, NULL);
462 /* create the monitor's peek array */
463 if ((p_monitor->p_peek = (tcp_connection_t **)
464 calloc(p_monitor->max_port_monitor_connections,
465 sizeof(tcp_connection_t *))) == NULL) {
466 /* we failed to create the peek array,
467 * so destroy the monitor completely, again, so we don't leak */
468 destroy_tcp_port_monitor(p_monitor, NULL);
472 p_monitor->port_range_begin = port_range_begin;
473 p_monitor->port_range_end = port_range_end;
475 p_monitor->connection_list.p_head = NULL;
476 p_monitor->connection_list.p_tail = NULL;
481 /* Clients use this function to get connection data from the indicated
483 * The requested monitor value is copied into a client-supplied char buffer.
484 * Returns 0 on success, -1 otherwise. */
485 int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
486 int connection_index, char *p_buffer, size_t buffer_size)
488 struct sockaddr_in sa;
490 if (!p_monitor || !p_buffer || connection_index < 0) {
494 memset(p_buffer, 0, buffer_size);
495 memset(&sa, 0, sizeof(sa));
497 sa.sin_family = AF_INET;
499 /* if the connection index is out of range, we simply return with no error,
500 * having first cleared the client-supplied buffer. */
501 if ((item != COUNT) && (connection_index
502 > (int) g_hash_table_size(p_monitor->hash) - 1)) {
510 snprintf(p_buffer, buffer_size, "%d",
511 g_hash_table_size(p_monitor->hash));
516 print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->remote_addr, 0);
521 print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->remote_addr, 1);
526 snprintf(p_buffer, buffer_size, "%d",
527 p_monitor->p_peek[connection_index]->remote_port);
532 sa.sin_port=htons(p_monitor->p_peek[connection_index]->remote_port);
533 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
538 print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->local_addr, 0);
543 print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->local_addr, 1);
548 snprintf(p_buffer, buffer_size, "%d",
549 p_monitor->p_peek[connection_index]->local_port);
554 sa.sin_port=htons(p_monitor->p_peek[connection_index]->local_port);
555 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
565 /* --------------------------------
566 * Client operations on collections
567 * -------------------------------- */
569 /* Create a monitor collection. Do this one first. */
570 tcp_port_monitor_collection_t *create_tcp_port_monitor_collection(void)
572 tcp_port_monitor_collection_t *p_collection;
574 p_collection = (tcp_port_monitor_collection_t *)
575 calloc(1, sizeof(tcp_port_monitor_collection_t));
580 /* create the collection's monitor hash */
581 if ((p_collection->hash = g_hash_table_new(g_str_hash, g_str_equal))
583 /* we failed to create the hash,
584 * so destroy the monitor completely so we don't leak */
585 destroy_tcp_port_monitor_collection(p_collection);
589 p_collection->monitor_list.p_head = NULL;
590 p_collection->monitor_list.p_tail = NULL;
595 /* Destroy the monitor collection (and the monitors inside).
596 * Do this one last. */
597 void destroy_tcp_port_monitor_collection(
598 tcp_port_monitor_collection_t *p_collection)
600 tcp_port_monitor_node_t *p_current_node, *p_next_node;
606 /* destroy the monitors */
607 for_each_tcp_port_monitor_in_collection(p_collection,
608 &destroy_tcp_port_monitor, NULL);
610 /* next destroy the empty monitor nodes */
611 for (p_current_node = p_collection->monitor_list.p_head;
612 p_current_node != NULL; p_current_node = p_next_node) {
613 p_next_node = p_current_node->p_next; /* do this first! */
615 free(p_current_node);
618 /* destroy the collection's hash */
619 g_hash_table_destroy(p_collection->hash);
620 p_collection->hash = NULL;
626 /* Updates the tcp statistics for all monitors within a collection */
627 void update_tcp_port_monitor_collection(
628 tcp_port_monitor_collection_t *p_collection)
634 process_file(p_collection, "/proc/net/tcp");
635 process_file(p_collection, "/proc/net/tcp6");
637 /* age the connections in all port monitors. */
638 for_each_tcp_port_monitor_in_collection(p_collection,
639 &age_tcp_port_monitor, NULL);
641 /* rebuild the connection peek tables of all monitors
642 * so clients can peek in O(1) time */
643 for_each_tcp_port_monitor_in_collection(p_collection,
644 &rebuild_tcp_port_monitor_peek_table, NULL);
647 /* After clients create a monitor, use this to add it to the collection.
648 * Returns 0 on success, -1 otherwise. */
649 int insert_tcp_port_monitor_into_collection(
650 tcp_port_monitor_collection_t *p_collection,
651 tcp_port_monitor_t *p_monitor)
653 tcp_port_monitor_node_t *p_node;
655 if (!p_collection || !p_monitor) {
659 /* create a container node for this monitor */
660 p_node = (tcp_port_monitor_node_t *)
661 calloc(1, sizeof(tcp_port_monitor_node_t));
666 /* populate the node */
667 p_node->p_monitor = p_monitor;
668 p_node->p_next = NULL;
670 /* add a pointer to this monitor to the collection's hash */
672 fprintf(stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
674 g_hash_table_insert(p_collection->hash, (gpointer) p_monitor->key,
675 (gpointer) p_monitor);
677 /* tail of the container gets this node */
678 if (!p_collection->monitor_list.p_tail) {
679 p_collection->monitor_list.p_tail = p_node;
681 /* p_next of the tail better be NULL */
682 if (p_collection->monitor_list.p_tail->p_next != NULL) {
686 /* splice node onto tail */
687 p_collection->monitor_list.p_tail->p_next = p_node;
688 p_collection->monitor_list.p_tail = p_node;
691 /* if this was the first element added */
692 if (!p_collection->monitor_list.p_head) {
693 p_collection->monitor_list.p_head = p_collection->monitor_list.p_tail;
699 /* Clients need a way to find monitors */
700 tcp_port_monitor_t *find_tcp_port_monitor(
701 const tcp_port_monitor_collection_t *p_collection,
702 in_port_t port_range_begin, in_port_t port_range_end)
704 tcp_port_monitor_t *p_monitor;
711 /* is monitor in hash? */
712 g_sprintf(key, ":%04X :%04X", port_range_begin, port_range_end);
713 p_monitor = g_hash_table_lookup(p_collection->hash, (gconstpointer) key);