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 g_strlcpy(p_dest_connection->key, p_source_connection->key,
53 sizeof(p_dest_connection->key));
54 p_dest_connection->local_addr = p_source_connection->local_addr;
55 p_dest_connection->local_port = p_source_connection->local_port;
56 p_dest_connection->remote_addr = p_source_connection->remote_addr;
57 p_dest_connection->remote_port = p_source_connection->remote_port;
58 p_dest_connection->age = p_source_connection->age;
63 /* -------------------------------------------
64 * Port monitor utility functions implementing
65 * tcp_port_monitor_function_ptr_t
66 * ------------------------------------------- */
67 void destroy_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
69 tcp_connection_node_t *p_node, *p_temp;
71 if (!p_monitor || p_void) { /* p_void should be NULL in this context */
75 /* destroy the monitor's peek array */
76 free(p_monitor->p_peek);
78 /* destroy the monitor's connection list */
79 for (p_node = p_monitor->connection_list.p_head; p_node != NULL; ) {
80 /* p_temp is for the next iteration */
81 p_temp = p_node->p_next;
88 /* destroy the monitor's hash */
89 g_hash_table_destroy(p_monitor->hash);
90 p_monitor->hash = NULL;
92 /* destroy the monitor */
97 void age_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
99 /* Run through the monitor's connections and decrement the age variable.
100 * If the age goes negative, we remove the connection from the monitor.
101 * Function takes O(n) time on the number of connections. */
103 tcp_connection_node_t *p_node, *p_temp;
104 tcp_connection_t *p_conn;
106 if (!p_monitor || p_void) { /* p_void should be NULL in this context */
110 if (!p_monitor->p_peek) {
114 for (p_node = p_monitor->connection_list.p_head; p_node; ) {
115 if (--p_node->connection.age >= 0) {
116 p_node = p_node->p_next;
120 /* connection on p_node is old. remove connection from the hash. */
121 p_conn = &p_node->connection;
123 fprintf(stderr, "monitor hash removal of connection [%s]", p_conn->key);
124 if (!g_hash_table_remove(p_monitor->hash,
125 (gconstpointer) p_conn->key)) {
126 fprintf(stderr, " - ERROR NOT FOUND\n");
129 fprintf(stderr, " - OK\n");
131 if (!g_hash_table_remove(p_monitor->hash,
132 (gconstpointer) p_conn->key)) {
137 /* splice p_node out of the connection_list */
138 if (p_node->p_prev != NULL) {
139 p_node->p_prev->p_next = p_node->p_next;
141 if (p_node->p_next != NULL) {
142 p_node->p_next->p_prev = p_node->p_prev;
145 /* correct the list head and tail if necessary */
146 if (p_monitor->connection_list.p_head == p_node) {
147 p_monitor->connection_list.p_head = p_node->p_next;
149 if (p_monitor->connection_list.p_tail == p_node) {
150 p_monitor->connection_list.p_tail = p_node->p_prev;
153 /* p_temp is for the next iteration */
154 p_temp = p_node->p_next;
156 /* destroy the node */
163 void rebuild_tcp_port_monitor_peek_table(tcp_port_monitor_t *p_monitor,
166 /* Run through the monitor's connections and rebuild the peek table of
167 * connection pointers. This is done so peeking into the monitor can be
168 * done in O(1) time instead of O(n) time for each peek. */
170 tcp_connection_node_t *p_node;
173 if (!p_monitor || p_void) { /* p_void should be NULL in this context */
177 /* zero out the peek array */
178 memset(p_monitor->p_peek, 0, p_monitor->max_port_monitor_connections *
179 sizeof(tcp_connection_t *));
181 for (p_node = p_monitor->connection_list.p_head; p_node != NULL;
182 p_node = p_node->p_next, i++) {
183 p_monitor->p_peek[i] = &p_node->connection;
187 void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
190 /* The monitor gets to look at each connection to see if it falls within
191 * the monitor's port range of interest. Connections of interest are first
192 * looked up in the hash to see if they are already there. If they are, we
193 * reset the age of the connection so it is not deleted. If the connection
194 * is not in the hash, we add it, but only if we haven't exceeded the
195 * maximum connection limit for the monitor.
196 * The function takes O(1) time. */
198 tcp_connection_node_t *p_node;
199 tcp_connection_t *p_connection, *p_conn_hash;
201 if (!p_monitor || !p_void) {
205 /* This p_connection is on caller's stack and not the heap.
206 * If we are interested, we will create a copy of the connection
207 * (on the heap) and add it to our list. */
208 p_connection = (tcp_connection_t *) p_void;
210 /* inspect the local port number of the connection to see if we're
212 if ((p_monitor->port_range_begin <= p_connection->local_port)
213 && (p_connection->local_port <= p_monitor->port_range_end)) {
214 /* the connection is in the range of the monitor. */
216 /* first check the hash to see if the connection is already there. */
217 if ((p_conn_hash = g_hash_table_lookup(p_monitor->hash,
218 (gconstpointer) p_connection->key))) {
219 /* it's already in the hash. reset the age of the connection. */
220 p_conn_hash->age = TCP_CONNECTION_STARTING_AGE;
225 /* Connection is not yet in the hash.
226 * Add it if max_connections not exceeded. */
227 if (g_hash_table_size(p_monitor->hash)
228 >= p_monitor->max_port_monitor_connections) {
232 /* create a new connection node */
233 if ((p_node = (tcp_connection_node_t *)
234 calloc(1, sizeof(tcp_connection_node_t))) == NULL) {
238 /* copy the connection data */
239 if (copy_tcp_connection(&p_node->connection, p_connection) != 0) {
240 /* error copying the connection data. deallocate p_node to
241 * avoid leaks and return. */
246 p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
247 p_node->p_next = NULL;
249 /* insert it into the monitor's hash table */
251 fprintf(stderr, "monitor hash insert of connection [%s]\n",
252 p_node->connection.key);
254 g_hash_table_insert(p_monitor->hash,
255 (gpointer) p_node->connection.key, (gpointer) &p_node->connection);
257 /* append the node to the monitor's connection list */
258 if (p_monitor->connection_list.p_tail == NULL) {
259 /* assume p_head is NULL too */
260 p_monitor->connection_list.p_head = p_node;
261 p_monitor->connection_list.p_tail = p_node;
262 p_node->p_prev = NULL;
264 p_monitor->connection_list.p_tail->p_next = p_node;
265 p_node->p_prev = p_monitor->connection_list.p_tail;
266 p_monitor->connection_list.p_tail = p_node;
271 /* ------------------------------------------------------------------------
272 * Apply a tcp_port_monitor_function_ptr_t function to each port monitor in
274 * ------------------------------------------------------------------------ */
275 void for_each_tcp_port_monitor_in_collection(
276 tcp_port_monitor_collection_t *p_collection,
277 tcp_port_monitor_function_ptr_t p_function, void *p_function_args)
279 tcp_port_monitor_node_t *p_current_node, *p_next_node;
281 if (!p_collection || !p_function) {
285 /* for each monitor in the collection */
286 for (p_current_node = p_collection->monitor_list.p_head;
287 p_current_node != NULL; p_current_node = p_next_node) {
288 p_next_node = p_current_node->p_next; /* do this first! */
290 if (p_current_node->p_monitor) {
291 /* apply the function with the given arguments */
292 p_function(p_current_node->p_monitor, p_function_args);
297 /* ----------------------------------------------------------------------
300 * Clients should call only those functions below this line.
301 * ---------------------------------------------------------------------- */
303 /* ----------------------------------
304 * Client operations on port monitors
305 * ---------------------------------- */
307 /* Clients should first try to "find_tcp_port_monitor" before creating one
308 * so that there are no redundant monitors. */
309 tcp_port_monitor_t *create_tcp_port_monitor(in_port_t port_range_begin,
310 in_port_t port_range_end, tcp_port_monitor_args_t *p_creation_args)
312 tcp_port_monitor_t *p_monitor;
314 /* create the monitor */
315 p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t));
320 p_monitor->max_port_monitor_connections =
321 p_creation_args->max_port_monitor_connections;
323 /* build the monitor key for the collection hash */
324 g_sprintf(p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
326 /* create the monitor's connection hash */
327 if ((p_monitor->hash = g_hash_table_new(g_str_hash, g_str_equal)) == NULL) {
328 /* we failed to create the hash, so destroy the monitor completely
329 * so we don't leak */
330 destroy_tcp_port_monitor(p_monitor, NULL);
334 /* create the monitor's peek array */
335 if ((p_monitor->p_peek = (tcp_connection_t **)
336 calloc(p_monitor->max_port_monitor_connections,
337 sizeof(tcp_connection_t *))) == NULL) {
338 /* we failed to create the peek array,
339 * so destroy the monitor completely, again, so we don't leak */
340 destroy_tcp_port_monitor(p_monitor, NULL);
344 p_monitor->port_range_begin = port_range_begin;
345 p_monitor->port_range_end = port_range_end;
347 p_monitor->connection_list.p_head = NULL;
348 p_monitor->connection_list.p_tail = NULL;
353 /* Clients use this function to get connection data from the indicated
355 * The requested monitor value is copied into a client-supplied char buffer.
356 * Returns 0 on success, -1 otherwise. */
357 int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
358 int connection_index, char *p_buffer, size_t buffer_size)
361 struct sockaddr_in sa;
363 sa.sin_family = AF_INET;
365 if (!p_monitor || !p_buffer || connection_index < 0) {
369 memset(p_buffer, 0, buffer_size);
370 memset(&net, 0, sizeof(net));
372 /* if the connection index is out of range, we simply return with no error,
373 * having first cleared the client-supplied buffer. */
374 if ((item != COUNT) && (connection_index
375 > (int) g_hash_table_size(p_monitor->hash) - 1)) {
383 snprintf(p_buffer, buffer_size, "%d",
384 g_hash_table_size(p_monitor->hash));
389 net.s_addr = p_monitor->p_peek[connection_index]->remote_addr;
390 snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
395 memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->remote_addr, sizeof(sa.sin_addr.s_addr));
396 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
401 snprintf(p_buffer, buffer_size, "%d",
402 p_monitor->p_peek[connection_index]->remote_port);
407 sa.sin_port=htons(p_monitor->p_peek[connection_index]->remote_port);
408 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
413 net.s_addr = p_monitor->p_peek[connection_index]->local_addr;
414 snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
419 memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->local_addr, sizeof(sa.sin_addr.s_addr));
420 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
425 snprintf(p_buffer, buffer_size, "%d",
426 p_monitor->p_peek[connection_index]->local_port);
431 sa.sin_port=htons(p_monitor->p_peek[connection_index]->local_port);
432 getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
442 /* --------------------------------
443 * Client operations on collections
444 * -------------------------------- */
446 /* Create a monitor collection. Do this one first. */
447 tcp_port_monitor_collection_t *create_tcp_port_monitor_collection(void)
449 tcp_port_monitor_collection_t *p_collection;
451 p_collection = (tcp_port_monitor_collection_t *)
452 calloc(1, sizeof(tcp_port_monitor_collection_t));
457 /* create the collection's monitor hash */
458 if ((p_collection->hash = g_hash_table_new(g_str_hash, g_str_equal))
460 /* we failed to create the hash,
461 * so destroy the monitor completely so we don't leak */
462 destroy_tcp_port_monitor_collection(p_collection);
466 p_collection->monitor_list.p_head = NULL;
467 p_collection->monitor_list.p_tail = NULL;
472 /* Destroy the monitor collection (and the monitors inside).
473 * Do this one last. */
474 void destroy_tcp_port_monitor_collection(
475 tcp_port_monitor_collection_t *p_collection)
477 tcp_port_monitor_node_t *p_current_node, *p_next_node;
483 /* destroy the monitors */
484 for_each_tcp_port_monitor_in_collection(p_collection,
485 &destroy_tcp_port_monitor, NULL);
487 /* next destroy the empty monitor nodes */
488 for (p_current_node = p_collection->monitor_list.p_head;
489 p_current_node != NULL; p_current_node = p_next_node) {
490 p_next_node = p_current_node->p_next; /* do this first! */
492 free(p_current_node);
495 /* destroy the collection's hash */
496 g_hash_table_destroy(p_collection->hash);
497 p_collection->hash = NULL;
503 /* Updates the tcp statistics for all monitors within a collection */
504 void update_tcp_port_monitor_collection(
505 tcp_port_monitor_collection_t *p_collection)
509 tcp_connection_t conn;
510 unsigned long inode, uid, state;
516 /* age the connections in all port monitors. */
517 for_each_tcp_port_monitor_in_collection(p_collection,
518 &age_tcp_port_monitor, NULL);
520 /* read tcp data from /proc/net/tcp */
521 if ((fp = fopen("/proc/net/tcp", "r")) == NULL) {
525 /* ignore field name line */
528 /* read all tcp connections */
529 while (fgets(buf, sizeof(buf), fp) != NULL) {
532 "%*d: %x:%hx %x:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
533 (unsigned int *) &conn.local_addr, &conn.local_port,
534 (unsigned int *) &conn.remote_addr, &conn.remote_port,
535 (unsigned long *) &state, (unsigned long *) &uid,
536 (unsigned long *) &inode) != 7) {
537 fprintf(stderr, "/proc/net/tcp: bad file format\n");
539 /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
540 //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
541 if((inode == 0) || (state != 1)) {
546 g_sprintf(conn.key, "%08X:%04X %08X:%04X", conn.local_addr,
547 conn.local_port, conn.remote_addr, conn.remote_port);
549 /* show the connection to each port monitor. */
550 for_each_tcp_port_monitor_in_collection(p_collection,
551 &show_connection_to_tcp_port_monitor, (void *) &conn);
556 /* rebuild the connection peek tables of all monitors
557 * so clients can peek in O(1) time */
558 for_each_tcp_port_monitor_in_collection(p_collection,
559 &rebuild_tcp_port_monitor_peek_table, NULL);
562 /* After clients create a monitor, use this to add it to the collection.
563 * Returns 0 on success, -1 otherwise. */
564 int insert_tcp_port_monitor_into_collection(
565 tcp_port_monitor_collection_t *p_collection,
566 tcp_port_monitor_t *p_monitor)
568 tcp_port_monitor_node_t *p_node;
570 if (!p_collection || !p_monitor) {
574 /* create a container node for this monitor */
575 p_node = (tcp_port_monitor_node_t *)
576 calloc(1, sizeof(tcp_port_monitor_node_t));
581 /* populate the node */
582 p_node->p_monitor = p_monitor;
583 p_node->p_next = NULL;
585 /* add a pointer to this monitor to the collection's hash */
587 fprintf(stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
589 g_hash_table_insert(p_collection->hash, (gpointer) p_monitor->key,
590 (gpointer) p_monitor);
592 /* tail of the container gets this node */
593 if (!p_collection->monitor_list.p_tail) {
594 p_collection->monitor_list.p_tail = p_node;
596 /* p_next of the tail better be NULL */
597 if (p_collection->monitor_list.p_tail->p_next != NULL) {
601 /* splice node onto tail */
602 p_collection->monitor_list.p_tail->p_next = p_node;
603 p_collection->monitor_list.p_tail = p_node;
606 /* if this was the first element added */
607 if (!p_collection->monitor_list.p_head) {
608 p_collection->monitor_list.p_head = p_collection->monitor_list.p_tail;
614 /* Clients need a way to find monitors */
615 tcp_port_monitor_t *find_tcp_port_monitor(
616 const tcp_port_monitor_collection_t *p_collection,
617 in_port_t port_range_begin, in_port_t port_range_end)
619 tcp_port_monitor_t *p_monitor;
626 /* is monitor in hash? */
627 g_sprintf(key, ":%04X :%04X", port_range_begin, port_range_end);
628 p_monitor = g_hash_table_lookup(p_collection->hash, (gconstpointer) key);