* Added NVIDIA Graficcard support patch (thanks meissna)
[monky] / src / libtcp-portmon.c
1 /* $Id$ */
2
3 /* libtcp-portmon.c:  tcp port monitoring library.
4  *
5  * Copyright (C) 2005-2007 Philip Kovacs pkovacs@users.sourceforge.net
6  *
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.
11  *
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.
16  *
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
20  * USA. */
21
22 #include <glib/gprintf.h>
23 #include "libtcp-portmon.h"
24
25 /* -------------------------------------------------------------------
26  * IMPLEMENTATION INTERFACE
27  *
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  * ------------------------------------------------------------------- */
32
33 /* -----------------------------------
34  * Copy a tcp_connection_t
35  *
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)
40 {
41         if (!p_dest_connection || !p_source_connection) {
42                 return -1;
43         }
44
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;
52
53         return 0;
54 }
55
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)
61 {
62         tcp_connection_node_t *p_node, *p_temp;
63
64         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
65                 return;
66         }
67
68         /* destroy the monitor's peek array */
69         free(p_monitor->p_peek);
70
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;
75
76                 free(p_node);
77
78                 p_node = p_temp;
79         }
80
81         /* destroy the monitor's hash */
82         g_hash_table_destroy(p_monitor->hash);
83         p_monitor->hash = NULL;
84
85         /* destroy the monitor */
86         free(p_monitor);
87         p_monitor = NULL;
88 }
89
90 void age_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
91 {
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. */
95
96         tcp_connection_node_t *p_node, *p_temp;
97         tcp_connection_t *p_conn;
98
99         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
100                 return;
101         }
102
103         if (!p_monitor->p_peek) {
104                 return;
105         }
106
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;
110                         continue;
111                 }
112
113                 /* connection on p_node is old.  remove connection from the hash. */
114                 p_conn = &p_node->connection;
115 #ifdef HASH_DEBUG
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");
120                         return;
121                 }
122                 fprintf(stderr, " - OK\n");
123 #else
124                 if (!g_hash_table_remove(p_monitor->hash,
125                                 (gconstpointer) p_conn->key)) {
126                         return;
127                 }
128 #endif
129
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;
133                 }
134                 if (p_node->p_next != NULL) {
135                         p_node->p_next->p_prev = p_node->p_prev;
136                 }
137
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                 }
142                 if (p_monitor->connection_list.p_tail == p_node) {
143                         p_monitor->connection_list.p_tail = p_node->p_prev;
144                 }
145
146                 /* p_temp is for the next iteration */
147                 p_temp = p_node->p_next;
148
149                 /* destroy the node */
150                 free(p_node);
151
152                 p_node = p_temp;
153         }
154 }
155
156 void rebuild_tcp_port_monitor_peek_table(tcp_port_monitor_t *p_monitor,
157                 void *p_void)
158 {
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. */
162
163         tcp_connection_node_t *p_node;
164         int i = 0;
165
166         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
167                 return;
168         }
169
170         /* zero out the peek array */
171         memset(p_monitor->p_peek, 0, p_monitor->max_port_monitor_connections *
172                 sizeof(tcp_connection_t *));
173
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;
177         }
178 }
179
180 void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
181                 void *p_void)
182 {
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. */
190
191         tcp_connection_node_t *p_node;
192         tcp_connection_t *p_connection, *p_conn_hash;
193
194         if (!p_monitor || !p_void) {
195                 return;
196         }
197
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;
202
203         /* inspect the local port number of the connection to see if we're
204          * interested. */
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. */
208
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;
214
215                         return;
216                 }
217
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) {
222                         return;
223                 }
224
225                 /* create a new connection node */
226                 if ((p_node = (tcp_connection_node_t *)
227                                 calloc(1, sizeof(tcp_connection_node_t))) == NULL) {
228                         return;
229                 }
230
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. */
235                         free(p_node);
236                         return;
237                 }
238
239                 p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
240                 p_node->p_next = NULL;
241
242                 /* insert it into the monitor's hash table */
243 #ifdef HASH_DEBUG
244                 fprintf(stderr, "monitor hash insert of connection [%s]\n",
245                         p_node->connection.key);
246 #endif
247                 g_hash_table_insert(p_monitor->hash,
248                         (gpointer) p_node->connection.key, (gpointer) &p_node->connection);
249
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;
256                 } else {
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;
260                 }
261         }
262 }
263
264 /* ------------------------------------------------------------------------
265  * Apply a tcp_port_monitor_function_ptr_t function to each port monitor in
266  * the collection.
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)
271 {
272         tcp_port_monitor_node_t *p_current_node, *p_next_node;
273
274         if (!p_collection || !p_function) {
275                 return;
276         }
277
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! */
282
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);
286                 }
287         }
288 }
289
290 /* ----------------------------------------------------------------------
291  * CLIENT INTERFACE
292  *
293  * Clients should call only those functions below this line.
294  * ---------------------------------------------------------------------- */
295
296 /* ----------------------------------
297  * Client operations on port monitors
298  * ---------------------------------- */
299
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)
304 {
305         tcp_port_monitor_t *p_monitor;
306
307         /* create the monitor */
308         p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t));
309         if (!p_monitor) {
310                 return NULL;
311         }
312
313         p_monitor->max_port_monitor_connections =
314                 p_creation_args->max_port_monitor_connections;
315
316         /* build the monitor key for the collection hash */
317         g_sprintf(p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
318
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);
324                 return NULL;
325         }
326
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);
334                 return NULL;
335         }
336
337         p_monitor->port_range_begin = port_range_begin;
338         p_monitor->port_range_end = port_range_end;
339
340         p_monitor->connection_list.p_head = NULL;
341         p_monitor->connection_list.p_tail = NULL;
342
343         return p_monitor;
344 }
345
346 /* Clients use this function to get connection data from the indicated
347  * port monitor.
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)
352 {
353         struct hostent *p_hostent;
354         struct servent *p_servent;
355         struct in_addr net;
356
357         if (!p_monitor || !p_buffer || connection_index < 0) {
358                 return -1;
359         }
360
361         memset(p_buffer, 0, buffer_size);
362         memset(&net, 0, sizeof(net));
363
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)) {
368                 return 0;
369         }
370
371         switch (item) {
372
373                 case COUNT:
374
375                         snprintf(p_buffer, buffer_size, "%d",
376                                 g_hash_table_size(p_monitor->hash));
377                         break;
378
379                 case REMOTEIP:
380
381                         net.s_addr = p_monitor->p_peek[connection_index]->remote_addr;
382                         snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
383                         break;
384
385                 case REMOTEHOST:
386
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));
394                                 break;
395                         }
396                         snprintf(p_buffer, buffer_size, "%s", p_hostent->h_name);
397                         break;
398
399                 case REMOTEPORT:
400
401                         snprintf(p_buffer, buffer_size, "%d",
402                                 p_monitor->p_peek[connection_index]->remote_port);
403                         break;
404
405                 case REMOTESERVICE:
406
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);
414                         } else {
415                                 snprintf(p_buffer, buffer_size, "%s", p_servent->s_name);
416                         }
417                         break;
418
419                 case LOCALIP:
420
421                         net.s_addr = p_monitor->p_peek[connection_index]->local_addr;
422                         snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
423                         break;
424
425                 case LOCALHOST:
426
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));
434                                 break;
435                         }
436                         snprintf(p_buffer, buffer_size, "%s", p_hostent->h_name);
437                         break;
438
439                 case LOCALPORT:
440
441                         snprintf(p_buffer, buffer_size, "%d",
442                                 p_monitor->p_peek[connection_index]->local_port);
443                         break;
444
445                 case LOCALSERVICE:
446
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);
454                                 break;
455                         }
456                         snprintf(p_buffer, buffer_size, "%s", p_servent->s_name);
457                         break;
458
459                 default:
460                         return -1;
461         }
462
463         return 0;
464 }
465
466 /* --------------------------------
467  * Client operations on collections
468  * -------------------------------- */
469
470 /* Create a monitor collection.  Do this one first. */
471 tcp_port_monitor_collection_t *create_tcp_port_monitor_collection(void)
472 {
473         tcp_port_monitor_collection_t *p_collection;
474
475         p_collection = (tcp_port_monitor_collection_t *)
476                 calloc(1, sizeof(tcp_port_monitor_collection_t));
477         if (!p_collection) {
478                 return NULL;
479         }
480
481         /* create the collection's monitor hash */
482         if ((p_collection->hash = g_hash_table_new(g_str_hash, g_str_equal))
483                         == NULL) {
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);
487                 return NULL;
488         }
489
490         p_collection->monitor_list.p_head = NULL;
491         p_collection->monitor_list.p_tail = NULL;
492
493         return p_collection;
494 }
495
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)
500 {
501         tcp_port_monitor_node_t *p_current_node, *p_next_node;
502
503         if (!p_collection) {
504                 return;
505         }
506
507         /* destroy the monitors */
508         for_each_tcp_port_monitor_in_collection(p_collection,
509                 &destroy_tcp_port_monitor, NULL);
510
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! */
515
516                 free(p_current_node);
517         }
518
519         /* destroy the collection's hash */
520         g_hash_table_destroy(p_collection->hash);
521         p_collection->hash = NULL;
522
523         free(p_collection);
524         p_collection = NULL;
525 }
526
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)
530 {
531         FILE *fp;
532         char buf[256];
533         tcp_connection_t conn;
534         unsigned long inode, uid, state;
535
536         if (!p_collection) {
537                 return;
538         }
539
540         /* age the connections in all port monitors. */
541         for_each_tcp_port_monitor_in_collection(p_collection,
542                 &age_tcp_port_monitor, NULL);
543
544         /* read tcp data from /proc/net/tcp */
545         if ((fp = fopen("/proc/net/tcp", "r")) == NULL) {
546                 return;
547         }
548
549         /* ignore field name line */
550         fgets(buf, 255, fp);
551
552         /* read all tcp connections */
553         while (fgets(buf, sizeof(buf), fp) != NULL) {
554
555                 if (sscanf(buf,
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");
562                 }
563                 /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
564                 //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
565                 if((inode == 0) || (state != 1)) {
566                         continue;
567                 }
568
569                 /* build hash key */
570                 g_sprintf(conn.key, "%08X:%04X %08X:%04X", conn.local_addr,
571                         conn.local_port, conn.remote_addr, conn.remote_port);
572
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);
576         }
577
578         fclose(fp);
579
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);
584 }
585
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)
591 {
592         tcp_port_monitor_node_t *p_node;
593
594         if (!p_collection || !p_monitor) {
595                 return -1;
596         }
597
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));
601         if (!p_node) {
602                 return -1;
603         }
604
605         /* populate the node */
606         p_node->p_monitor = p_monitor;
607         p_node->p_next = NULL;
608
609         /* add a pointer to this monitor to the collection's hash */
610 #ifdef HASH_DEBUG
611         fprintf(stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
612 #endif
613         g_hash_table_insert(p_collection->hash, (gpointer) p_monitor->key,
614                 (gpointer) p_monitor);
615
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;
619         } else {
620                 /* p_next of the tail better be NULL */
621                 if (p_collection->monitor_list.p_tail->p_next != NULL) {
622                         return -1;
623                 }
624
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;
628         }
629
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;
633         }
634
635         return 0;
636 }
637
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)
642 {
643         tcp_port_monitor_t *p_monitor;
644         gchar key[12];
645
646         if (!p_collection) {
647                 return NULL;
648         }
649
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);
653         return p_monitor;
654 }