Fix for segfault in top_name stuff.
[monky] / src / libtcp-portmon.c
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
3  *
4  * libtcp-portmon.c:  tcp port monitoring library.
5  *
6  * Copyright (C) 2005-2007 Philip Kovacs pkovacs@users.sourceforge.net
7  *
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.
12  *
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.
17  *
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
21  * USA.
22  *
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include "libtcp-portmon.h"
30 #include <glib/gprintf.h>
31
32 /* -------------------------------------------------------------------
33  * IMPLEMENTATION INTERFACE
34  *
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  * ------------------------------------------------------------------- */
39
40 /* -----------------------------------
41  * Copy a tcp_connection_t
42  *
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)
47 {
48         if (!p_dest_connection || !p_source_connection) {
49                 return -1;
50         }
51
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;
57
58         return 0;
59 }
60
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)
66 {
67         tcp_connection_node_t *p_node, *p_temp;
68
69         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
70                 return;
71         }
72
73         /* destroy the monitor's peek array */
74         free(p_monitor->p_peek);
75
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;
80
81                 free(p_node);
82
83                 p_node = p_temp;
84         }
85
86         /* destroy the monitor's hash */
87         g_hash_table_destroy(p_monitor->hash);
88         p_monitor->hash = NULL;
89
90         /* destroy the monitor */
91         free(p_monitor);
92         p_monitor = NULL;
93 }
94
95 void age_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
96 {
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. */
100
101         tcp_connection_node_t *p_node, *p_temp;
102         tcp_connection_t *p_conn;
103
104         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
105                 return;
106         }
107
108         if (!p_monitor->p_peek) {
109                 return;
110         }
111
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;
115                         continue;
116                 }
117
118                 /* connection on p_node is old.  remove connection from the hash. */
119                 p_conn = &p_node->connection;
120 #ifdef HASH_DEBUG
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");
125                         return;
126                 }
127                 fprintf(stderr, " - OK\n");
128 #else
129                 if (!g_hash_table_remove(p_monitor->hash,
130                                 (gconstpointer) p_conn)) {
131                         return;
132                 }
133 #endif
134
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;
138                 }
139                 if (p_node->p_next != NULL) {
140                         p_node->p_next->p_prev = p_node->p_prev;
141                 }
142
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;
146                 }
147                 if (p_monitor->connection_list.p_tail == p_node) {
148                         p_monitor->connection_list.p_tail = p_node->p_prev;
149                 }
150
151                 /* p_temp is for the next iteration */
152                 p_temp = p_node->p_next;
153
154                 /* destroy the node */
155                 free(p_node);
156
157                 p_node = p_temp;
158         }
159 }
160
161 void rebuild_tcp_port_monitor_peek_table(tcp_port_monitor_t *p_monitor,
162                 void *p_void)
163 {
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. */
167
168         tcp_connection_node_t *p_node;
169         int i = 0;
170
171         if (!p_monitor || p_void) {     /* p_void should be NULL in this context */
172                 return;
173         }
174
175         /* zero out the peek array */
176         memset(p_monitor->p_peek, 0, p_monitor->max_port_monitor_connections *
177                 sizeof(tcp_connection_t *));
178
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;
182         }
183 }
184
185 void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
186                 void *p_void)
187 {
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. */
195
196         tcp_connection_node_t *p_node;
197         tcp_connection_t *p_connection, *p_conn_hash;
198
199         if (!p_monitor || !p_void) {
200                 return;
201         }
202
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;
207
208         /* inspect the local port number of the connection to see if we're
209          * interested. */
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. */
213
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;
219
220                         return;
221                 }
222
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) {
227                         return;
228                 }
229
230                 /* create a new connection node */
231                 if ((p_node = (tcp_connection_node_t *)
232                                 calloc(1, sizeof(tcp_connection_node_t))) == NULL) {
233                         return;
234                 }
235
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. */
240                         free(p_node);
241                         return;
242                 }
243
244                 p_node->connection.age = TCP_CONNECTION_STARTING_AGE;
245                 p_node->p_next = NULL;
246
247                 /* insert it into the monitor's hash table */
248 #ifdef HASH_DEBUG
249                 fprintf(stderr, "monitor hash insert of connection [%s]\n",
250                         p_node->connection.key);
251 #endif
252                 g_hash_table_insert(p_monitor->hash,
253                         (gpointer) &p_node->connection, (gpointer) &p_node->connection);
254
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;
261                 } else {
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;
265                 }
266         }
267 }
268
269 /* ------------------------------------------------------------------------
270  * Apply a tcp_port_monitor_function_ptr_t function to each port monitor in
271  * the collection.
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)
276 {
277         tcp_port_monitor_node_t *p_current_node, *p_next_node;
278
279         if (!p_collection || !p_function) {
280                 return;
281         }
282
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! */
287
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);
291                 }
292         }
293 }
294
295 static const unsigned char prefix_4on6[] = {
296         0x00, 0x00, 0x00, 0x00,
297         0x00, 0x00, 0x00, 0x00,
298         0x00, 0x00, 0xff, 0xff
299 };
300
301 union sockaddr_in46 {
302         struct sockaddr_in  sa4;
303         struct sockaddr_in6 sa6;
304         struct sockaddr     sa;
305 };
306
307 /* checks whether the address is a IPv4-mapped IPv6 address */
308 static int is_4on6(const struct in6_addr *addr)
309 {
310         return ! memcmp(&addr->s6_addr, prefix_4on6, sizeof(prefix_4on6));
311 }
312
313
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)
316 {
317         union sockaddr_in46 sa;
318         socklen_t slen;
319
320         memset(&sa, 0, sizeof(sa));
321
322         if(is_4on6(addr)) {
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);
326         } else {
327                 sa.sa6.sin6_family = AF_INET6;
328                 memcpy(&sa.sa6.sin6_addr, addr, sizeof(struct in6_addr));
329                 slen = sizeof(sa.sa6);
330         }
331
332         getnameinfo(&sa.sa, slen, p_buffer, buffer_size, NULL, 0, fqdn?0:NI_NUMERICHOST);
333 }
334
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)
337 {
338         size_t i;
339
340         if(strlen(p_buffer) < 32) { //IPv4 address
341                 i = sizeof(prefix_4on6);
342                 memcpy(addr->s6_addr, prefix_4on6, i);
343         } else {
344                 i = 0;
345         }
346
347         for( ; i < sizeof(addr->s6_addr); i+=4, p_buffer+=8) {
348                 sscanf(p_buffer, "%8x", (unsigned *)&addr->s6_addr[i]);
349         }
350 }
351
352 /* hash function for tcp_connections */
353 static guint tcp_connection_hash(gconstpointer A)
354 {
355         const tcp_connection_t *a = (const tcp_connection_t *) A;
356         guint hash = 0;
357         size_t i;
358
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];
365
366         return hash;
367 }
368
369 /* comparison function for tcp_connections */
370 static gboolean tcp_connection_equal(gconstpointer A, gconstpointer B)
371 {
372         const tcp_connection_t *a = (const tcp_connection_t *) A;
373         const tcp_connection_t *b = (const tcp_connection_t *) B;
374
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));
378 }
379
380 /* adds connections from file to the collection */
381 static void process_file(tcp_port_monitor_collection_t *p_collection, const char *file)
382 {
383         FILE *fp;
384         char buf[256];
385         char local_addr[40];
386         char remote_addr[40];
387         tcp_connection_t conn;
388         unsigned long inode, uid, state;
389
390         if ((fp = fopen(file, "r")) == NULL) {
391                 return;
392         }
393
394         /* ignore field name line */
395         fgets(buf, 255, fp);
396
397         /* read all tcp connections */
398         while (fgets(buf, sizeof(buf), fp) != NULL) {
399
400                 if (sscanf(buf,
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");
407                 }
408                 /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
409                 //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
410                 if((inode == 0) || (state != 1)) {
411                         continue;
412                 }
413
414                 string_to_addr(&conn.local_addr, local_addr);
415                 string_to_addr(&conn.remote_addr, remote_addr);
416
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);
420         }
421
422         fclose(fp);
423 }
424
425 /* ----------------------------------------------------------------------
426  * CLIENT INTERFACE
427  *
428  * Clients should call only those functions below this line.
429  * ---------------------------------------------------------------------- */
430
431 /* ----------------------------------
432  * Client operations on port monitors
433  * ---------------------------------- */
434
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)
439 {
440         tcp_port_monitor_t *p_monitor;
441
442         /* create the monitor */
443         p_monitor = (tcp_port_monitor_t *) calloc(1, sizeof(tcp_port_monitor_t));
444         if (!p_monitor) {
445                 return NULL;
446         }
447
448         p_monitor->max_port_monitor_connections =
449                 p_creation_args->max_port_monitor_connections;
450
451         /* build the monitor key for the collection hash */
452         g_sprintf(p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
453
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);
459                 return NULL;
460         }
461
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);
469                 return NULL;
470         }
471
472         p_monitor->port_range_begin = port_range_begin;
473         p_monitor->port_range_end = port_range_end;
474
475         p_monitor->connection_list.p_head = NULL;
476         p_monitor->connection_list.p_tail = NULL;
477
478         return p_monitor;
479 }
480
481 /* Clients use this function to get connection data from the indicated
482  * port monitor.
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)
487 {
488         struct sockaddr_in sa;
489
490         if (!p_monitor || !p_buffer || connection_index < 0) {
491                 return -1;
492         }
493
494         memset(p_buffer, 0, buffer_size);
495         memset(&sa, 0, sizeof(sa));
496
497         sa.sin_family = AF_INET;
498         
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)) {
503                 return 0;
504         }
505
506         switch (item) {
507
508                 case COUNT:
509
510                         snprintf(p_buffer, buffer_size, "%d",
511                                 g_hash_table_size(p_monitor->hash));
512                         break;
513
514                 case REMOTEIP:
515
516                         print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->remote_addr, 0);
517                         break;
518
519                 case REMOTEHOST:
520
521                         print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->remote_addr, 1);
522                         break;
523
524                 case REMOTEPORT:
525
526                         snprintf(p_buffer, buffer_size, "%d",
527                                 p_monitor->p_peek[connection_index]->remote_port);
528                         break;
529
530                 case REMOTESERVICE:
531
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);
534                         break;
535
536                 case LOCALIP:
537
538                         print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->local_addr, 0);
539                         break;
540
541                 case LOCALHOST:
542
543                         print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->local_addr, 1);
544                         break;
545
546                 case LOCALPORT:
547
548                         snprintf(p_buffer, buffer_size, "%d",
549                                 p_monitor->p_peek[connection_index]->local_port);
550                         break;
551
552                 case LOCALSERVICE:
553
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);
556                         break;
557
558                 default:
559                         return -1;
560         }
561
562         return 0;
563 }
564
565 /* --------------------------------
566  * Client operations on collections
567  * -------------------------------- */
568
569 /* Create a monitor collection.  Do this one first. */
570 tcp_port_monitor_collection_t *create_tcp_port_monitor_collection(void)
571 {
572         tcp_port_monitor_collection_t *p_collection;
573
574         p_collection = (tcp_port_monitor_collection_t *)
575                 calloc(1, sizeof(tcp_port_monitor_collection_t));
576         if (!p_collection) {
577                 return NULL;
578         }
579
580         /* create the collection's monitor hash */
581         if ((p_collection->hash = g_hash_table_new(g_str_hash, g_str_equal))
582                         == NULL) {
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);
586                 return NULL;
587         }
588
589         p_collection->monitor_list.p_head = NULL;
590         p_collection->monitor_list.p_tail = NULL;
591
592         return p_collection;
593 }
594
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)
599 {
600         tcp_port_monitor_node_t *p_current_node, *p_next_node;
601
602         if (!p_collection) {
603                 return;
604         }
605
606         /* destroy the monitors */
607         for_each_tcp_port_monitor_in_collection(p_collection,
608                 &destroy_tcp_port_monitor, NULL);
609
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! */
614
615                 free(p_current_node);
616         }
617
618         /* destroy the collection's hash */
619         g_hash_table_destroy(p_collection->hash);
620         p_collection->hash = NULL;
621
622         free(p_collection);
623         p_collection = NULL;
624 }
625
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)
629 {
630         if (!p_collection) {
631                 return;
632         }
633
634         process_file(p_collection, "/proc/net/tcp");
635         process_file(p_collection, "/proc/net/tcp6");
636
637         /* age the connections in all port monitors. */
638         for_each_tcp_port_monitor_in_collection(p_collection,
639                 &age_tcp_port_monitor, NULL);
640
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);
645 }
646
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)
652 {
653         tcp_port_monitor_node_t *p_node;
654
655         if (!p_collection || !p_monitor) {
656                 return -1;
657         }
658
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));
662         if (!p_node) {
663                 return -1;
664         }
665
666         /* populate the node */
667         p_node->p_monitor = p_monitor;
668         p_node->p_next = NULL;
669
670         /* add a pointer to this monitor to the collection's hash */
671 #ifdef HASH_DEBUG
672         fprintf(stderr, "collection hash insert of monitor [%s]\n", p_monitor->key);
673 #endif
674         g_hash_table_insert(p_collection->hash, (gpointer) p_monitor->key,
675                 (gpointer) p_monitor);
676
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;
680         } else {
681                 /* p_next of the tail better be NULL */
682                 if (p_collection->monitor_list.p_tail->p_next != NULL) {
683                         return -1;
684                 }
685
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;
689         }
690
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;
694         }
695
696         return 0;
697 }
698
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)
703 {
704         tcp_port_monitor_t *p_monitor;
705         gchar key[12];
706
707         if (!p_collection) {
708                 return NULL;
709         }
710
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);
714         return p_monitor;
715 }