IPv6 support for $tcp_portmon
authorPavel Labath <pavelo@centrum.sk>
Mon, 16 Nov 2009 17:17:16 +0000 (18:17 +0100)
committerPavel Labath <pavelo@centrum.sk>
Mon, 16 Nov 2009 17:17:16 +0000 (18:17 +0100)
In theory, this may fail to compile on ancient systems that don't have IPv6 types (struct
sockaddr_in6 et al.) available. If it turns out that such systems are still in use, the best way
to solve it would be to provide dummy declarations via configure tests.

ChangeLog
doc/conky-howto.xml
doc/variables.xml
src/libtcp-portmon.c
src/libtcp-portmon.h

index 41d008f..e0bbe36 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,7 @@
 2009-11-16
        * Added support for $pid_threads, $pid_thread_list, $pid_nice,
        $pid_priority, $pid_time_usermode, $pid_time_kernelmode and $pid_time
+       * Added IPv6 support to $tcp_portmon
 
 2009-11-15
        * Added support for $pid_parent, $pid_uid, $pid_euid, $pid_suid,
index 98225e8..f322f5a 100644 (file)
@@ -54,7 +54,7 @@ Unlike other system monitors such as top, Conky can run as a window in an X sess
 <body>
 
 <p>
-Gentoo provides an ebuild to quickly and easily install Conky.  Pay particular attention to the the USE flags.  You'll most likely want X11 support (<c>X</c>), and make sure you select the USE flags for any music players (other than MPD) which you want.  XMMS (<c>xmms</c>), Audacious (<c>audacious</c>), BMPx (<c>bmpx</c>), or XMMS support via the xmms-infopipe plugin (<c>infopipe</c>).  If you want to use the TCP port monitor, be SURE to disable the <c>ipv6</c> use flag, as the port monitor is for ipv4 systems only.
+Gentoo provides an ebuild to quickly and easily install Conky.  Pay particular attention to the the USE flags.  You'll most likely want X11 support (<c>X</c>), and make sure you select the USE flags for any music players (other than MPD) which you want.  XMMS (<c>xmms</c>), Audacious (<c>audacious</c>), BMPx (<c>bmpx</c>), or XMMS support via the xmms-infopipe plugin (<c>infopipe</c>).
 </p>
 
 <pre caption="/etc/portage/package.use">
index 1f39516..838e27c 100644 (file)
                 <option>tcp_portmon</option>
             </command>
             <option>port_begin port_end item (index)</option>
-            <emphasis>(ip4 only at present)</emphasis>
         </term>
         <listitem>
-            <para>TCP port monitor for specified local ports. Port
-            numbers must be in the range 1 to 65535. Valid items
-            are:</para>
+            <para>TCP port (both IPv6 and IPv4) monitor for
+            specified local ports. Port numbers must be in
+            the range 1 to 65535. Valid items are:</para>
             <simplelist>
                 <member>
                     <command>count</command>
index efd08d8..bedcae5 100644 (file)
@@ -49,8 +49,6 @@ int copy_tcp_connection(tcp_connection_t *p_dest_connection,
                return -1;
        }
 
-       g_strlcpy(p_dest_connection->key, p_source_connection->key,
-               sizeof(p_dest_connection->key));
        p_dest_connection->local_addr = p_source_connection->local_addr;
        p_dest_connection->local_port = p_source_connection->local_port;
        p_dest_connection->remote_addr = p_source_connection->remote_addr;
@@ -129,7 +127,7 @@ void age_tcp_port_monitor(tcp_port_monitor_t *p_monitor, void *p_void)
                fprintf(stderr, " - OK\n");
 #else
                if (!g_hash_table_remove(p_monitor->hash,
-                               (gconstpointer) p_conn->key)) {
+                               (gconstpointer) p_conn)) {
                        return;
                }
 #endif
@@ -215,7 +213,7 @@ void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
 
                /* first check the hash to see if the connection is already there. */
                if ((p_conn_hash = g_hash_table_lookup(p_monitor->hash,
-                               (gconstpointer) p_connection->key))) {
+                               (gconstpointer) p_connection))) {
                        /* it's already in the hash.  reset the age of the connection. */
                        p_conn_hash->age = TCP_CONNECTION_STARTING_AGE;
 
@@ -252,7 +250,7 @@ void show_connection_to_tcp_port_monitor(tcp_port_monitor_t *p_monitor,
                        p_node->connection.key);
 #endif
                g_hash_table_insert(p_monitor->hash,
-                       (gpointer) p_node->connection.key, (gpointer) &p_node->connection);
+                       (gpointer) &p_node->connection, (gpointer) &p_node->connection);
 
                /* append the node to the monitor's connection list */
                if (p_monitor->connection_list.p_tail == NULL) {
@@ -294,6 +292,136 @@ void for_each_tcp_port_monitor_in_collection(
        }
 }
 
+static const unsigned char prefix_4on6[] = {
+       0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0xff, 0xff
+};
+
+union sockaddr_in46 {
+       struct sockaddr_in  sa4;
+       struct sockaddr_in6 sa6;
+       struct sockaddr     sa;
+};
+
+/* checks whether the address is a IPv4-mapped IPv6 address */
+static int is_4on6(const struct in6_addr *addr)
+{
+       return ! memcmp(&addr->s6_addr, prefix_4on6, sizeof(prefix_4on6));
+}
+
+
+/* converts the address to appropriate textual representation (IPv6, IPv4 or fqdn) */
+static void print_host(char *p_buffer, size_t buffer_size, const struct in6_addr *addr, int fqdn)
+{
+       union sockaddr_in46 sa;
+       socklen_t slen;
+
+       memset(&sa, 0, sizeof(sa));
+
+       if(is_4on6(addr)) {
+               sa.sa4.sin_family = AF_INET;
+               memcpy(&sa.sa4.sin_addr.s_addr, &addr->s6_addr[12], 4);
+               slen = sizeof(sa.sa4);
+       } else {
+               sa.sa6.sin6_family = AF_INET6;
+               memcpy(&sa.sa6.sin6_addr, addr, sizeof(struct in6_addr));
+               slen = sizeof(sa.sa6);
+       }
+
+       getnameinfo(&sa.sa, slen, p_buffer, buffer_size, NULL, 0, fqdn?0:NI_NUMERICHOST);
+}
+
+/* converts the textual representation of an IPv4 or IPv6 address to struct in6_addr */
+static void string_to_addr(struct in6_addr *addr, const char *p_buffer)
+{
+       size_t i;
+
+       if(strlen(p_buffer) < 32) { //IPv4 address
+               i = sizeof(prefix_4on6);
+               memcpy(addr->s6_addr, prefix_4on6, i);
+       } else {
+               i = 0;
+       }
+
+       for( ; i < sizeof(addr->s6_addr); i+=4, p_buffer+=8) {
+               sscanf(p_buffer, "%8x", (unsigned *)&addr->s6_addr[i]);
+       }
+}
+
+/* hash function for tcp_connections */
+static guint tcp_connection_hash(gconstpointer A)
+{
+       const tcp_connection_t *a = (const tcp_connection_t *) A;
+       guint hash = 0;
+       size_t i;
+
+       hash = hash*47 + a->local_port;
+       hash = hash*47 + a->remote_port;
+       for(i = 0; i < sizeof(a->local_addr.s6_addr); ++i)
+               hash = hash*47 + a->local_addr.s6_addr[i];
+       for(i = 0; i < sizeof(a->remote_addr.s6_addr); ++i)
+               hash = hash*47 + a->remote_addr.s6_addr[i];
+
+       return hash;
+}
+
+/* comparison function for tcp_connections */
+static gboolean tcp_connection_equal(gconstpointer A, gconstpointer B)
+{
+       const tcp_connection_t *a = (const tcp_connection_t *) A;
+       const tcp_connection_t *b = (const tcp_connection_t *) B;
+
+       return a->local_port == b->local_port && a->remote_port == b->remote_port &&
+               ! memcmp(&a->local_addr, &b->local_addr, sizeof(a->local_addr)) &&
+               ! memcmp(&a->remote_addr.s6_addr, &b->remote_addr, sizeof(a->remote_addr));
+}
+
+/* adds connections from file to the collection */
+static void process_file(tcp_port_monitor_collection_t *p_collection, const char *file)
+{
+       FILE *fp;
+       char buf[256];
+       char local_addr[40];
+       char remote_addr[40];
+       tcp_connection_t conn;
+       unsigned long inode, uid, state;
+
+       if ((fp = fopen(file, "r")) == NULL) {
+               return;
+       }
+
+       /* ignore field name line */
+       fgets(buf, 255, fp);
+
+       /* read all tcp connections */
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+
+               if (sscanf(buf,
+                               "%*d: %39[0-9a-fA-F]:%hx %39[0-9a-fA-F]:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
+                               local_addr, &conn.local_port,
+                               remote_addr, &conn.remote_port,
+                               (unsigned long *) &state, (unsigned long *) &uid,
+                               (unsigned long *) &inode) != 7) {
+                       fprintf(stderr, "/proc/net/tcp: bad file format\n");
+               }
+               /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
+               //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
+               if((inode == 0) || (state != 1)) {
+                       continue;
+               }
+
+               string_to_addr(&conn.local_addr, local_addr);
+               string_to_addr(&conn.remote_addr, remote_addr);
+
+               /* show the connection to each port monitor. */
+               for_each_tcp_port_monitor_in_collection(p_collection,
+                       &show_connection_to_tcp_port_monitor, (void *) &conn);
+       }
+
+       fclose(fp);
+}
+
 /* ----------------------------------------------------------------------
  * CLIENT INTERFACE
  *
@@ -324,7 +452,7 @@ tcp_port_monitor_t *create_tcp_port_monitor(in_port_t port_range_begin,
        g_sprintf(p_monitor->key, ":%04X :%04X", port_range_begin, port_range_end);
 
        /* create the monitor's connection hash */
-       if ((p_monitor->hash = g_hash_table_new(g_str_hash, g_str_equal)) == NULL) {
+       if ((p_monitor->hash = g_hash_table_new(tcp_connection_hash, tcp_connection_equal)) == NULL) {
                /* we failed to create the hash, so destroy the monitor completely
                 * so we don't leak */
                destroy_tcp_port_monitor(p_monitor, NULL);
@@ -357,18 +485,17 @@ tcp_port_monitor_t *create_tcp_port_monitor(in_port_t port_range_begin,
 int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
                int connection_index, char *p_buffer, size_t buffer_size)
 {
-       struct in_addr net;
        struct sockaddr_in sa;
 
-       sa.sin_family = AF_INET;
-       
        if (!p_monitor || !p_buffer || connection_index < 0) {
                return -1;
        }
 
        memset(p_buffer, 0, buffer_size);
-       memset(&net, 0, sizeof(net));
+       memset(&sa, 0, sizeof(sa));
 
+       sa.sin_family = AF_INET;
+       
        /* if the connection index is out of range, we simply return with no error,
         * having first cleared the client-supplied buffer. */
        if ((item != COUNT) && (connection_index
@@ -386,14 +513,12 @@ int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
 
                case REMOTEIP:
 
-                       net.s_addr = p_monitor->p_peek[connection_index]->remote_addr;
-                       snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
+                       print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->remote_addr, 0);
                        break;
 
                case REMOTEHOST:
 
-                       memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->remote_addr, sizeof(sa.sin_addr.s_addr));
-                       getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
+                       print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->remote_addr, 1);
                        break;
 
                case REMOTEPORT:
@@ -410,14 +535,12 @@ int peek_tcp_port_monitor(const tcp_port_monitor_t *p_monitor, int item,
 
                case LOCALIP:
 
-                       net.s_addr = p_monitor->p_peek[connection_index]->local_addr;
-                       snprintf(p_buffer, buffer_size, "%s", inet_ntoa(net));
+                       print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->local_addr, 0);
                        break;
 
                case LOCALHOST:
 
-                       memcpy(&sa.sin_addr.s_addr, &p_monitor->p_peek[connection_index]->local_addr, sizeof(sa.sin_addr.s_addr));
-                       getnameinfo((struct sockaddr *) &sa, sizeof(struct sockaddr_in), p_buffer, buffer_size, NULL, 0, 0);
+                       print_host(p_buffer, buffer_size, &p_monitor->p_peek[connection_index]->local_addr, 1);
                        break;
 
                case LOCALPORT:
@@ -504,55 +627,17 @@ void destroy_tcp_port_monitor_collection(
 void update_tcp_port_monitor_collection(
                tcp_port_monitor_collection_t *p_collection)
 {
-       FILE *fp;
-       char buf[256];
-       tcp_connection_t conn;
-       unsigned long inode, uid, state;
-
        if (!p_collection) {
                return;
        }
 
+       process_file(p_collection, "/proc/net/tcp");
+       process_file(p_collection, "/proc/net/tcp6");
+
        /* age the connections in all port monitors. */
        for_each_tcp_port_monitor_in_collection(p_collection,
                &age_tcp_port_monitor, NULL);
 
-       /* read tcp data from /proc/net/tcp */
-       if ((fp = fopen("/proc/net/tcp", "r")) == NULL) {
-               return;
-       }
-
-       /* ignore field name line */
-       fgets(buf, 255, fp);
-
-       /* read all tcp connections */
-       while (fgets(buf, sizeof(buf), fp) != NULL) {
-
-               if (sscanf(buf,
-                               "%*d: %x:%hx %x:%hx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
-                               (unsigned int *) &conn.local_addr, &conn.local_port,
-                               (unsigned int *) &conn.remote_addr, &conn.remote_port,
-                               (unsigned long *) &state, (unsigned long *) &uid,
-                               (unsigned long *) &inode) != 7) {
-                       fprintf(stderr, "/proc/net/tcp: bad file format\n");
-               }
-               /** TCP_ESTABLISHED equals 1, but is not (always??) included **/
-               //if ((inode == 0) || (state != TCP_ESTABLISHED)) {
-               if((inode == 0) || (state != 1)) {
-                       continue;
-               }
-
-               /* build hash key */
-               g_sprintf(conn.key, "%08X:%04X %08X:%04X", conn.local_addr,
-                       conn.local_port, conn.remote_addr, conn.remote_port);
-
-               /* show the connection to each port monitor. */
-               for_each_tcp_port_monitor_in_collection(p_collection,
-                       &show_connection_to_tcp_port_monitor, (void *) &conn);
-       }
-
-       fclose(fp);
-
        /* rebuild the connection peek tables of all monitors
         * so clients can peek in O(1) time */
        for_each_tcp_port_monitor_in_collection(p_collection,
index 0ff9301..3465e3f 100644 (file)
@@ -39,7 +39,6 @@
 
 /* connection deleted if unseen again after this # of refreshes */
 #define TCP_CONNECTION_STARTING_AGE 1
-#define TCP_CONNECTION_HASH_KEY_SIZE 28
 #define TCP_PORT_MONITOR_HASH_KEY_SIZE 12
 #define MAX_PORT_MONITOR_CONNECTIONS_DEFAULT 256
 
@@ -72,10 +71,9 @@ enum tcp_port_monitor_peekables {
  * ------------------------------------------------------------------------ */
 typedef struct _tcp_connection_t {
        /* connection's key in monitor hash */
-       gchar key[TCP_CONNECTION_HASH_KEY_SIZE];
-       in_addr_t local_addr;
+       struct in6_addr local_addr;
+       struct in6_addr remote_addr;
        in_port_t local_port;
-       in_addr_t remote_addr;
        in_port_t remote_port;
        int age;
 } tcp_connection_t;