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
26 #include "getaddrinfo.h"
27 #include "libtcp-portmon.h"
28 #include <glib/gprintf.h>
30 /* -------------------------------------------------------------------
31 * IMPLEMENTATION INTERFACE
33 * Implementation-specific interface begins here. Clients should not
34 * manipulate these structures directly, nor call the defined helper
35 * functions. Use the "Client interface" functions defined at bottom.
36 * ------------------------------------------------------------------- */
38 /* -----------------------------------
39 * Copy a tcp_connection_t
41 * Returns 0 on success, -1 otherwise.
42 * ----------------------------------- */
43 int copy_tcp_connection(tcp_connection_t *p_dest_connection,
44 const tcp_connection_t *p_source_connection)
46 if (!p_dest_connection || !p_source_connection) {
50 g_strlcpy(p_dest_connection->key, p_source_connection->key,
51 sizeof(p_dest_connection->key));
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->key)) {
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->key))) {
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.key, (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 /* ----------------------------------------------------------------------
298 * Clients should call only those functions below this line.
299 * ---------------------------------------------------------------------- */
301 /* ----------------------------------
302 * Client operations on port monitors
303 * ---------------------------------- */
305 /* Clients should first try to "find_tcp_port_monitor" before creating one
306 * so that there are no redundant monitors. */
307 tcp_port_monitor_t *create_tcp_port_monitor(in_port_t port_range_begin,
308 in_port_t port_range_end, tcp_port_monitor_args_t *p_creation_args)
310 tcp_port_monitor_t *p_monitor;
312 /* create the monitor */
313 p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t));
318 p_monitor->max_port_monitor_connections =
319 p_creation_args->max_port_monitor_connections;
321 /* build the monitor key for the collection hash */
322 g_sprintf(p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
324 /* create the monitor's connection hash */
325 if ((p_monitor->hash = g_hash_table_new(g_str_hash, g_str_equal)) == NULL) {
326 /* we failed to create the hash, so destroy the monitor completely
327 * so we don't leak */
328 destroy_tcp_port_monitor(p_monitor, NULL);
332 /* create the monitor's peek array */
333 if ((p_monitor->p_peek = (tcp_connection_t **)
334 calloc(p_monitor->max_port_monitor_connections,
335 sizeof(tcp_connection_t *))) == NULL) {
336 /* we failed to create the peek array,
337 * so destroy the monitor completely, again, so we don't leak */
338 destroy_tcp_port_monitor(p_monitor, NULL);
342 p_monitor->port_range_begin = port_range_begin;
343 p_monitor->port_range_end = port_range_end;
345 p_monitor->connection_list.p_head = NULL;
346 p_monitor->connection_list.p_tail = NULL;
351 /* Clients use this function to get connection data from the indicated
353 * The requested monitor value is copied into a client-supplied char buffer.
354 * Returns 0 on success, -1 otherwise. */
355 int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
356 int connection_index, char *p_buffer, size_t buffer_size)
359 struct sockaddr_in sa;
361 sa.sin_family = AF_INET;
363 if (!p_monitor || !p_buffer || connection_index < 0) {
367 memset(p_buffer, 0, buffer_size);
368 memset(&net, 0, sizeof(net));
370 /* if the connection index is out of range, we simply return with no error,
371 * having first cleared the client-supplied buffer. */
372 if ((item != COUNT) && (connection_index
373 > (int) g_hash_table_size(p_monitor->hash) - 1)) {
381 snprintf(p_buffer, buffer_size, "%d",
382 g_hash_table_size(p_monitor->hash));
387 net.s_addr = p_monitor->p_peek[connection_index]->remote_addr;
388 snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
393 memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->remote_addr, sizeof(sa.sin_addr.s_addr));
394 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
399 snprintf(p_buffer, buffer_size, "%d",
400 p_monitor->p_peek[connection_index]->remote_port);
405 sa.sin_port=htons(p_monitor->p_peek[connection_index]->remote_port);
406 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
411 net.s_addr = p_monitor->p_peek[connection_index]->local_addr;
412 snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
417 memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->local_addr, sizeof(sa.sin_addr.s_addr));
418 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
423 snprintf(p_buffer, buffer_size, "%d",
424 p_monitor->p_peek[connection_index]->local_port);
429 sa.sin_port=htons(p_monitor->p_peek[connection_index]->local_port);
430 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
440 /* --------------------------------
441 * Client operations on collections
442 * -------------------------------- */
444 /* Create a monitor collection. Do this one first. */
445 tcp_port_monitor_collection_t *create_tcp_port_monitor_collection(void)
447 tcp_port_monitor_collection_t *p_collection;
449 p_collection = (tcp_port_monitor_collection_t *)
450 calloc(1, sizeof(tcp_port_monitor_collection_t));
455 /* create the collection's monitor hash */
456 if ((p_collection->hash = g_hash_table_new(g_str_hash, g_str_equal))
458 /* we failed to create the hash,
459 * so destroy the monitor completely so we don't leak */
460 destroy_tcp_port_monitor_collection(p_collection);
464 p_collection->monitor_list.p_head = NULL;
465 p_collection->monitor_list.p_tail = NULL;
470 /* Destroy the monitor collection (and the monitors inside).
471 * Do this one last. */
472 void destroy_tcp_port_monitor_collection(
473 tcp_port_monitor_collection_t *p_collection)
475 tcp_port_monitor_node_t *p_current_node, *p_next_node;
481 /* destroy the monitors */
482 for_each_tcp_port_monitor_in_collection(p_collection,
483 &destroy_tcp_port_monitor, NULL);
485 /* next destroy the empty monitor nodes */
486 for (p_current_node = p_collection->monitor_list.p_head;
487 p_current_node != NULL; p_current_node = p_next_node) {
488 p_next_node = p_current_node->p_next; /* do this first! */
490 free(p_current_node);
493 /* destroy the collection's hash */
494 g_hash_table_destroy(p_collection->hash);
495 p_collection->hash = NULL;
501 /* Updates the tcp statistics for all monitors within a collection */
502 void update_tcp_port_monitor_collection(
503 tcp_port_monitor_collection_t *p_collection)
507 tcp_connection_t conn;
508 unsigned long inode, uid, state;
514 /* age the connections in all port monitors. */
515 for_each_tcp_port_monitor_in_collection(p_collection,
516 &age_tcp_port_monitor, NULL);
518 /* read tcp data from /proc/net/tcp */
519 if ((fp = fopen("/proc/net/tcp", "r")) == NULL) {
523 /* ignore field name line */
526 /* read all tcp connections */
527 while (fgets(buf, sizeof(buf), fp) != NULL) {
530 "%*d: %x:%hx %x:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
531 (unsigned int *) &conn.local_addr, &conn.local_port,
532 (unsigned int *) &conn.remote_addr, &conn.remote_port,
533 (unsigned long *) &state, (unsigned long *) &uid,
534 (unsigned long *) &inode) != 7) {
535 fprintf(stderr, "/proc/net/tcp: bad file format\n");
537 /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
538 //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
539 if((inode == 0) || (state != 1)) {
544 g_sprintf(conn.key, "%08X:%04X %08X:%04X", conn.local_addr,
545 conn.local_port, conn.remote_addr, conn.remote_port);
547 /* show the connection to each port monitor. */
548 for_each_tcp_port_monitor_in_collection(p_collection,
549 &show_connection_to_tcp_port_monitor, (void *) &conn);
554 /* rebuild the connection peek tables of all monitors
555 * so clients can peek in O(1) time */
556 for_each_tcp_port_monitor_in_collection(p_collection,
557 &rebuild_tcp_port_monitor_peek_table, NULL);
560 /* After clients create a monitor, use this to add it to the collection.
561 * Returns 0 on success, -1 otherwise. */
562 int insert_tcp_port_monitor_into_collection(
563 tcp_port_monitor_collection_t *p_collection,
564 tcp_port_monitor_t *p_monitor)
566 tcp_port_monitor_node_t *p_node;
568 if (!p_collection || !p_monitor) {
572 /* create a container node for this monitor */
573 p_node = (tcp_port_monitor_node_t *)
574 calloc(1, sizeof(tcp_port_monitor_node_t));
579 /* populate the node */
580 p_node->p_monitor = p_monitor;
581 p_node->p_next = NULL;
583 /* add a pointer to this monitor to the collection's hash */
585 fprintf(stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
587 g_hash_table_insert(p_collection->hash, (gpointer) p_monitor->key,
588 (gpointer) p_monitor);
590 /* tail of the container gets this node */
591 if (!p_collection->monitor_list.p_tail) {
592 p_collection->monitor_list.p_tail = p_node;
594 /* p_next of the tail better be NULL */
595 if (p_collection->monitor_list.p_tail->p_next != NULL) {
599 /* splice node onto tail */
600 p_collection->monitor_list.p_tail->p_next = p_node;
601 p_collection->monitor_list.p_tail = p_node;
604 /* if this was the first element added */
605 if (!p_collection->monitor_list.p_head) {
606 p_collection->monitor_list.p_head = p_collection->monitor_list.p_tail;
612 /* Clients need a way to find monitors */
613 tcp_port_monitor_t *find_tcp_port_monitor(
614 const tcp_port_monitor_collection_t *p_collection,
615 in_port_t port_range_begin, in_port_t port_range_end)
617 tcp_port_monitor_t *p_monitor;
624 /* is monitor in hash? */
625 g_sprintf(key, ":%04X :%04X", port_range_begin, port_range_end);
626 p_monitor = g_hash_table_lookup(p_collection->hash, (gconstpointer) key);