removed gnulib completely. use a simple getnameinfo function check for --enable-portmon.
[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 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "libtcp-portmon.h"
27 #include <glib/gprintf.h>
28
29 /* -------------------------------------------------------------------
30  * IMPLEMENTATION INTERFACE
31  *
32  * Implementation-specific interface begins here.  Clients should not
33  * manipulate these structures directly, nor call the defined helper
34  * functions.  Use the "Client interface" functions defined at bottom.
35  * ------------------------------------------------------------------- */
36
37 /* -----------------------------------
38  * Copy a tcp_connection_t
39  *
40  * Returns 0 on success, -1 otherwise.
41  * ----------------------------------- */
42 int copy_tcp_connection(tcp_connection_t *p_dest_connection,
43                 const tcp_connection_t *p_source_connection)
44 {
45         if (!p_dest_connection || !p_source_connection) {
46                 return -1;
47         }
48
49         g_strlcpy(p_dest_connection->key, p_source_connection->key,
50                 sizeof(p_dest_connection->key));
51         p_dest_connection->local_addr = p_source_connection->local_addr;
52         p_dest_connection->local_port = p_source_connection->local_port;
53         p_dest_connection->remote_addr = p_source_connection->remote_addr;
54         p_dest_connection->remote_port = p_source_connection->remote_port;
55         p_dest_connection->age = p_source_connection->age;
56
57         return 0;
58 }
59
60 /* -------------------------------------------
61  * Port monitor utility functions implementing
62  * tcp_port_monitor_function_ptr_t
63  * ------------------------------------------- */
64 void destroy_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
65 {
66         tcp_connection_node_t *p_node, *p_temp;
67
68         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
69                 return;
70         }
71
72         /* destroy the monitor's peek array */
73         free(p_monitor->p_peek);
74
75         /* destroy the monitor's connection list */
76         for (p_node = p_monitor->connection_list.p_head; p_node != NULL; ) {
77                 /* p_temp is for the next iteration */
78                 p_temp = p_node->p_next;
79
80                 free(p_node);
81
82                 p_node = p_temp;
83         }
84
85         /* destroy the monitor's hash */
86         g_hash_table_destroy(p_monitor->hash);
87         p_monitor->hash = NULL;
88
89         /* destroy the monitor */
90         free(p_monitor);
91         p_monitor = NULL;
92 }
93
94 void age_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
95 {
96         /* Run through the monitor's connections and decrement the age variable.
97          * If the age goes negative, we remove the connection from the monitor.
98          * Function takes O(n) time on the number of connections. */
99
100         tcp_connection_node_t *p_node, *p_temp;
101         tcp_connection_t *p_conn;
102
103         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
104                 return;
105         }
106
107         if (!p_monitor->p_peek) {
108                 return;
109         }
110
111         for (p_node = p_monitor->connection_list.p_head; p_node; ) {
112                 if (--p_node->connection.age >= 0) {
113                         p_node = p_node->p_next;
114                         continue;
115                 }
116
117                 /* connection on p_node is old.  remove connection from the hash. */
118                 p_conn = &p_node->connection;
119 #ifdef HASH_DEBUG
120                 fprintf(stderr, "monitor hash removal of connection [%s]", p_conn->key);
121                 if (!g_hash_table_remove(p_monitor->hash,
122                                 (gconstpointer) p_conn->key)) {
123                         fprintf(stderr, " - ERROR NOT FOUND\n");
124                         return;
125                 }
126                 fprintf(stderr, " - OK\n");
127 #else
128                 if (!g_hash_table_remove(p_monitor->hash,
129                                 (gconstpointer) p_conn->key)) {
130                         return;
131                 }
132 #endif
133
134                 /* splice p_node out of the connection_list */
135                 if (p_node->p_prev != NULL) {
136                         p_node->p_prev->p_next = p_node->p_next;
137                 }
138                 if (p_node->p_next != NULL) {
139                         p_node->p_next->p_prev = p_node->p_prev;
140                 }
141
142                 /* correct the list head and tail if necessary */
143                 if (p_monitor->connection_list.p_head == p_node) {
144                         p_monitor->connection_list.p_head = p_node->p_next;
145                 }
146                 if (p_monitor->connection_list.p_tail == p_node) {
147                         p_monitor->connection_list.p_tail = p_node->p_prev;
148                 }
149
150                 /* p_temp is for the next iteration */
151                 p_temp = p_node->p_next;
152
153                 /* destroy the node */
154                 free(p_node);
155
156                 p_node = p_temp;
157         }
158 }
159
160 void rebuild_tcp_port_monitor_peek_table(tcp_port_monitor_t *p_monitor,
161                 void *p_void)
162 {
163         /* Run through the monitor's connections and rebuild the peek table of
164          * connection pointers.  This is done so peeking into the monitor can be
165          * done in O(1) time instead of O(n) time for each peek. */
166
167         tcp_connection_node_t *p_node;
168         int i = 0;
169
170         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
171                 return;
172         }
173
174         /* zero out the peek array */
175         memset(p_monitor->p_peek, 0, p_monitor->max_port_monitor_connections *
176                 sizeof(tcp_connection_t *));
177
178         for (p_node = p_monitor->connection_list.p_head; p_node != NULL;
179                         p_node = p_node->p_next, i++) {
180                 p_monitor->p_peek[i] = &p_node->connection;
181         }
182 }
183
184 void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
185                 void *p_void)
186 {
187         /* The monitor gets to look at each connection to see if it falls within
188          * the monitor's port range of interest.  Connections of interest are first
189          * looked up in the hash to see if they are already there.  If they are, we
190          * reset the age of the connection so it is not deleted.  If the connection
191          * is not in the hash, we add it, but only if we haven't exceeded the
192          * maximum connection limit for the monitor.
193          * The function takes O(1) time. */
194
195         tcp_connection_node_t *p_node;
196         tcp_connection_t *p_connection, *p_conn_hash;
197
198         if (!p_monitor || !p_void) {
199                 return;
200         }
201
202         /* This p_connection is on caller's stack and not the heap.
203          * If we are interested, we will create a copy of the connection
204          * (on the heap) and add it to our list. */
205         p_connection = (tcp_connection_t *) p_void;
206
207         /* inspect the local port number of the connection to see if we're
208          * interested. */
209         if ((p_monitor->port_range_begin <= p_connection->local_port)
210                         && (p_connection->local_port <= p_monitor->port_range_end)) {
211                 /* the connection is in the range of the monitor. */
212
213                 /* first check the hash to see if the connection is already there. */
214                 if ((p_conn_hash = g_hash_table_lookup(p_monitor->hash,
215                                 (gconstpointer) p_connection->key))) {
216                         /* it's already in the hash.  reset the age of the connection. */
217                         p_conn_hash->age = TCP_CONNECTION_STARTING_AGE;
218
219                         return;
220                 }
221
222                 /* Connection is not yet in the hash.
223                  * Add it if max_connections not exceeded. */
224                 if (g_hash_table_size(p_monitor->hash)
225                                 >= p_monitor->max_port_monitor_connections) {
226                         return;
227                 }
228
229                 /* create a new connection node */
230                 if ((p_node = (tcp_connection_node_t *)
231                                 calloc(1, sizeof(tcp_connection_node_t))) == NULL) {
232                         return;
233                 }
234
235                 /* copy the connection data */
236                 if (copy_tcp_connection(&p_node->connection, p_connection) != 0) {
237                         /* error copying the connection data. deallocate p_node to
238                          * avoid leaks and return. */
239                         free(p_node);
240                         return;
241                 }
242
243                 p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
244                 p_node->p_next = NULL;
245
246                 /* insert it into the monitor's hash table */
247 #ifdef HASH_DEBUG
248                 fprintf(stderr, "monitor hash insert of connection [%s]\n",
249                         p_node->connection.key);
250 #endif
251                 g_hash_table_insert(p_monitor->hash,
252                         (gpointer) p_node->connection.key, (gpointer) &p_node->connection);
253
254                 /* append the node to the monitor's connection list */
255                 if (p_monitor->connection_list.p_tail == NULL) {
256                         /* assume p_head is NULL too */
257                         p_monitor->connection_list.p_head = p_node;
258                         p_monitor->connection_list.p_tail = p_node;
259                         p_node->p_prev = NULL;
260                 } else {
261                         p_monitor->connection_list.p_tail->p_next = p_node;
262                         p_node->p_prev = p_monitor->connection_list.p_tail;
263                         p_monitor->connection_list.p_tail = p_node;
264                 }
265         }
266 }
267
268 /* ------------------------------------------------------------------------
269  * Apply a tcp_port_monitor_function_ptr_t function to each port monitor in
270  * the collection.
271  * ------------------------------------------------------------------------ */
272 void for_each_tcp_port_monitor_in_collection(
273                 tcp_port_monitor_collection_t *p_collection,
274                 tcp_port_monitor_function_ptr_t p_function, void *p_function_args)
275 {
276         tcp_port_monitor_node_t *p_current_node, *p_next_node;
277
278         if (!p_collection || !p_function) {
279                 return;
280         }
281
282         /* for each monitor in the collection */
283         for (p_current_node = p_collection->monitor_list.p_head;
284                         p_current_node != NULL; p_current_node = p_next_node) {
285                 p_next_node = p_current_node->p_next;   /* do this first! */
286
287                 if (p_current_node->p_monitor) {
288                         /* apply the function with the given arguments */
289                         p_function(p_current_node->p_monitor, p_function_args);
290                 }
291         }
292 }
293
294 /* ----------------------------------------------------------------------
295  * CLIENT INTERFACE
296  *
297  * Clients should call only those functions below this line.
298  * ---------------------------------------------------------------------- */
299
300 /* ----------------------------------
301  * Client operations on port monitors
302  * ---------------------------------- */
303
304 /* Clients should first try to "find_tcp_port_monitor" before creating one
305  * so that there are no redundant monitors. */
306 tcp_port_monitor_t *create_tcp_port_monitor(in_port_t port_range_begin,
307                 in_port_t port_range_end, tcp_port_monitor_args_t *p_creation_args)
308 {
309         tcp_port_monitor_t *p_monitor;
310
311         /* create the monitor */
312         p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t));
313         if (!p_monitor) {
314                 return NULL;
315         }
316
317         p_monitor->max_port_monitor_connections =
318                 p_creation_args->max_port_monitor_connections;
319
320         /* build the monitor key for the collection hash */
321         g_sprintf(p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
322
323         /* create the monitor's connection hash */
324         if ((p_monitor->hash = g_hash_table_new(g_str_hash, g_str_equal)) == NULL) {
325                 /* we failed to create the hash, so destroy the monitor completely
326                  * so we don't leak */
327                 destroy_tcp_port_monitor(p_monitor, NULL);
328                 return NULL;
329         }
330
331         /* create the monitor's peek array */
332         if ((p_monitor->p_peek = (tcp_connection_t **)
333                         calloc(p_monitor->max_port_monitor_connections,
334                         sizeof(tcp_connection_t *))) == NULL) {
335                 /* we failed to create the peek array,
336                  * so destroy the monitor completely, again, so we don't leak */
337                 destroy_tcp_port_monitor(p_monitor, NULL);
338                 return NULL;
339         }
340
341         p_monitor->port_range_begin = port_range_begin;
342         p_monitor->port_range_end = port_range_end;
343
344         p_monitor->connection_list.p_head = NULL;
345         p_monitor->connection_list.p_tail = NULL;
346
347         return p_monitor;
348 }
349
350 /* Clients use this function to get connection data from the indicated
351  * port monitor.
352  * The requested monitor value is copied into a client-supplied char buffer.
353  * Returns 0 on success, -1 otherwise. */
354 int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
355                 int connection_index, char *p_buffer, size_t buffer_size)
356 {
357         struct in_addr net;
358         struct sockaddr_in sa;
359
360         sa.sin_family = AF_INET;
361         
362         if (!p_monitor || !p_buffer || connection_index < 0) {
363                 return -1;
364         }
365
366         memset(p_buffer, 0, buffer_size);
367         memset(&net, 0, sizeof(net));
368
369         /* if the connection index is out of range, we simply return with no error,
370          * having first cleared the client-supplied buffer. */
371         if ((item != COUNT) && (connection_index
372                         > (int) g_hash_table_size(p_monitor->hash) - 1)) {
373                 return 0;
374         }
375
376         switch (item) {
377
378                 case COUNT:
379
380                         snprintf(p_buffer, buffer_size, "%d",
381                                 g_hash_table_size(p_monitor->hash));
382                         break;
383
384                 case REMOTEIP:
385
386                         net.s_addr = p_monitor->p_peek[connection_index]->remote_addr;
387                         snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
388                         break;
389
390                 case REMOTEHOST:
391
392                         memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->remote_addr, sizeof(sa.sin_addr.s_addr));
393                         getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
394                         break;
395
396                 case REMOTEPORT:
397
398                         snprintf(p_buffer, buffer_size, "%d",
399                                 p_monitor->p_peek[connection_index]->remote_port);
400                         break;
401
402                 case REMOTESERVICE:
403
404                         sa.sin_port=htons(p_monitor->p_peek[connection_index]->remote_port);
405                         getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
406                         break;
407
408                 case LOCALIP:
409
410                         net.s_addr = p_monitor->p_peek[connection_index]->local_addr;
411                         snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
412                         break;
413
414                 case LOCALHOST:
415
416                         memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->local_addr, sizeof(sa.sin_addr.s_addr));
417                         getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
418                         break;
419
420                 case LOCALPORT:
421
422                         snprintf(p_buffer, buffer_size, "%d",
423                                 p_monitor->p_peek[connection_index]->local_port);
424                         break;
425
426                 case LOCALSERVICE:
427
428                         sa.sin_port=htons(p_monitor->p_peek[connection_index]->local_port);
429                         getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), NULL, 0, p_buffer, buffer_size, NI_NUMERICHOST);
430                         break;
431
432                 default:
433                         return -1;
434         }
435
436         return 0;
437 }
438
439 /* --------------------------------
440  * Client operations on collections
441  * -------------------------------- */
442
443 /* Create a monitor collection.  Do this one first. */
444 tcp_port_monitor_collection_t *create_tcp_port_monitor_collection(void)
445 {
446         tcp_port_monitor_collection_t *p_collection;
447
448         p_collection = (tcp_port_monitor_collection_t *)
449                 calloc(1, sizeof(tcp_port_monitor_collection_t));
450         if (!p_collection) {
451                 return NULL;
452         }
453
454         /* create the collection's monitor hash */
455         if ((p_collection->hash = g_hash_table_new(g_str_hash, g_str_equal))
456                         == NULL) {
457                 /* we failed to create the hash,
458                  * so destroy the monitor completely so we don't leak */
459                 destroy_tcp_port_monitor_collection(p_collection);
460                 return NULL;
461         }
462
463         p_collection->monitor_list.p_head = NULL;
464         p_collection->monitor_list.p_tail = NULL;
465
466         return p_collection;
467 }
468
469 /* Destroy the monitor collection (and the monitors inside).
470  * Do this one last. */
471 void destroy_tcp_port_monitor_collection(
472                 tcp_port_monitor_collection_t *p_collection)
473 {
474         tcp_port_monitor_node_t *p_current_node, *p_next_node;
475
476         if (!p_collection) {
477                 return;
478         }
479
480         /* destroy the monitors */
481         for_each_tcp_port_monitor_in_collection(p_collection,
482                 &destroy_tcp_port_monitor, NULL);
483
484         /* next destroy the empty monitor nodes */
485         for (p_current_node = p_collection->monitor_list.p_head;
486                         p_current_node != NULL; p_current_node = p_next_node) {
487                 p_next_node = p_current_node->p_next;   /* do this first! */
488
489                 free(p_current_node);
490         }
491
492         /* destroy the collection's hash */
493         g_hash_table_destroy(p_collection->hash);
494         p_collection->hash = NULL;
495
496         free(p_collection);
497         p_collection = NULL;
498 }
499
500 /* Updates the tcp statistics for all monitors within a collection */
501 void update_tcp_port_monitor_collection(
502                 tcp_port_monitor_collection_t *p_collection)
503 {
504         FILE *fp;
505         char buf[256];
506         tcp_connection_t conn;
507         unsigned long inode, uid, state;
508
509         if (!p_collection) {
510                 return;
511         }
512
513         /* age the connections in all port monitors. */
514         for_each_tcp_port_monitor_in_collection(p_collection,
515                 &age_tcp_port_monitor, NULL);
516
517         /* read tcp data from /proc/net/tcp */
518         if ((fp = fopen("/proc/net/tcp", "r")) == NULL) {
519                 return;
520         }
521
522         /* ignore field name line */
523         fgets(buf, 255, fp);
524
525         /* read all tcp connections */
526         while (fgets(buf, sizeof(buf), fp) != NULL) {
527
528                 if (sscanf(buf,
529                                 "%*d: %x:%hx %x:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
530                                 (unsigned int *) &conn.local_addr, &conn.local_port,
531                                 (unsigned int *) &conn.remote_addr, &conn.remote_port,
532                                 (unsigned long *) &state, (unsigned long *) &uid,
533                                 (unsigned long *) &inode) != 7) {
534                         fprintf(stderr, "/proc/net/tcp: bad file format\n");
535                 }
536                 /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
537                 //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
538                 if((inode == 0) || (state != 1)) {
539                         continue;
540                 }
541
542                 /* build hash key */
543                 g_sprintf(conn.key, "%08X:%04X %08X:%04X", conn.local_addr,
544                         conn.local_port, conn.remote_addr, conn.remote_port);
545
546                 /* show the connection to each port monitor. */
547                 for_each_tcp_port_monitor_in_collection(p_collection,
548                         &show_connection_to_tcp_port_monitor, (void *) &conn);
549         }
550
551         fclose(fp);
552
553         /* rebuild the connection peek tables of all monitors
554          * so clients can peek in O(1) time */
555         for_each_tcp_port_monitor_in_collection(p_collection,
556                 &rebuild_tcp_port_monitor_peek_table, NULL);
557 }
558
559 /* After clients create a monitor, use this to add it to the collection.
560  * Returns 0 on success, -1 otherwise. */
561 int insert_tcp_port_monitor_into_collection(
562                 tcp_port_monitor_collection_t *p_collection,
563                 tcp_port_monitor_t *p_monitor)
564 {
565         tcp_port_monitor_node_t *p_node;
566
567         if (!p_collection || !p_monitor) {
568                 return -1;
569         }
570
571         /* create a container node for this monitor */
572         p_node = (tcp_port_monitor_node_t *)
573                 calloc(1, sizeof(tcp_port_monitor_node_t));
574         if (!p_node) {
575                 return -1;
576         }
577
578         /* populate the node */
579         p_node->p_monitor = p_monitor;
580         p_node->p_next = NULL;
581
582         /* add a pointer to this monitor to the collection's hash */
583 #ifdef HASH_DEBUG
584         fprintf(stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
585 #endif
586         g_hash_table_insert(p_collection->hash, (gpointer) p_monitor->key,
587                 (gpointer) p_monitor);
588
589         /* tail of the container gets this node */
590         if (!p_collection->monitor_list.p_tail) {
591                 p_collection->monitor_list.p_tail = p_node;
592         } else {
593                 /* p_next of the tail better be NULL */
594                 if (p_collection->monitor_list.p_tail->p_next != NULL) {
595                         return -1;
596                 }
597
598                 /* splice node onto tail */
599                 p_collection->monitor_list.p_tail->p_next = p_node;
600                 p_collection->monitor_list.p_tail = p_node;
601         }
602
603         /* if this was the first element added */
604         if (!p_collection->monitor_list.p_head) {
605                 p_collection->monitor_list.p_head = p_collection->monitor_list.p_tail;
606         }
607
608         return 0;
609 }
610
611 /* Clients need a way to find monitors */
612 tcp_port_monitor_t *find_tcp_port_monitor(
613                 const tcp_port_monitor_collection_t *p_collection,
614                 in_port_t port_range_begin, in_port_t port_range_end)
615 {
616         tcp_port_monitor_t *p_monitor;
617         gchar key[12];
618
619         if (!p_collection) {
620                 return NULL;
621         }
622
623         /* is monitor in hash? */
624         g_sprintf(key, ":%04X :%04X", port_range_begin, port_range_end);
625         p_monitor = g_hash_table_lookup(p_collection->hash, (gconstpointer) key);
626         return p_monitor;
627 }