[tor-commits] [stem/master] proc support for ipv6 addresses

atagar at torproject.org atagar at torproject.org
Sun Jan 24 23:40:06 UTC 2016


commit f315a2a7212b3846423ce59cb452107d76815b52
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Jan 24 15:38:25 2016 -0800

    proc support for ipv6 addresses
    
    Refactoring our proc handler a bit in addition to adding ipv6 support. IPv6
    simply required...
    
      * Reading /proc/net/tcp6 and /proc/net/udp6 in addition to their ipv4
        counterparts.
    
      * Provide socket.AF_INET6 when decoding the address.
---
 stem/util/proc.py      |   52 +++++++++++++++---------------
 test/unit/util/proc.py |   83 ++++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 98 insertions(+), 37 deletions(-)

diff --git a/stem/util/proc.py b/stem/util/proc.py
index 1432710..d519e6f 100644
--- a/stem/util/proc.py
+++ b/stem/util/proc.py
@@ -55,6 +55,7 @@ import sys
 import time
 
 import stem.util.enum
+import stem.util.str_tools
 
 from stem.util import log
 
@@ -369,7 +370,7 @@ def connections(pid):
       fd_name = os.readlink(fd_path)
 
       if fd_name.startswith('socket:['):
-        inodes.append(fd_name[8:-1])
+        inodes.append(stem.util.str_tools._to_bytes(fd_name[8:-1]))
     except OSError as exc:
       if not os.path.exists(fd_path):
         continue  # descriptors may shift while we're in the middle of iterating over them
@@ -387,25 +388,27 @@ def connections(pid):
 
   conn = []
 
-  for proc_file_path in ('/proc/net/tcp', '/proc/net/udp'):
+  for proc_file_path in ('/proc/net/tcp', '/proc/net/tcp6', '/proc/net/udp', '/proc/net/udp6'):
+    if not os.path.exists(proc_file_path):
+      continue
+
     try:
-      proc_file = open(proc_file_path)
-      proc_file.readline()  # skip the first line
+      with open(proc_file_path) as proc_file:
+        proc_file.readline()  # skip the first line
 
-      for line in proc_file:
-        _, l_addr, f_addr, status, _, _, _, _, _, inode = line.split()[:10]
+        for line in proc_file:
+          _, l_addr, f_addr, status, _, _, _, _, _, inode = line.split()[:10]
 
-        if inode in inodes:
-          # if a tcp connection, skip if it isn't yet established
-          if proc_file_path.endswith('/tcp') and status != '01':
-            continue
+          if inode in inodes:
+            protocol = proc_file_path[10:].rstrip('6')  # 'tcp' or 'udp'
+            is_ipv6 = proc_file_path.endswith('6')
 
-          local_ip, local_port = _decode_proc_address_encoding(l_addr)
-          foreign_ip, foreign_port = _decode_proc_address_encoding(f_addr)
-          protocol = proc_file_path[10:]
-          conn.append((local_ip, local_port, foreign_ip, foreign_port, protocol, False))
+            if protocol == 'tcp' and status != b'01':
+              continue  # skip tcp connections that aren't yet established
 
-      proc_file.close()
+            local_ip, local_port = _decode_proc_address_encoding(l_addr, is_ipv6)
+            foreign_ip, foreign_port = _decode_proc_address_encoding(f_addr, is_ipv6)
+            conn.append((local_ip, local_port, foreign_ip, foreign_port, protocol, is_ipv6))
     except IOError as exc:
       exc = IOError("unable to read '%s': %s" % (proc_file_path, exc))
       _log_failure(parameter, exc)
@@ -419,7 +422,7 @@ def connections(pid):
   return conn
 
 
-def _decode_proc_address_encoding(addr):
+def _decode_proc_address_encoding(addr, is_ipv6):
   """
   Translates an address entry in the /proc/net/* contents to a human readable
   form (`reference <http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html>`_,
@@ -434,26 +437,21 @@ def _decode_proc_address_encoding(addr):
   :returns: **tuple** of the form **(addr, port)**, with addr as a string and port an int
   """
 
-  ip, port = addr.rsplit(':', 1)
-
-  # the port is represented as a two-byte hexadecimal number
-  port = int(port, 16)
+  ip, port = addr.rsplit(b':', 1)
 
-  if sys.version_info >= (3,):
-    ip = ip.encode('ascii')
+  port = int(port, 16)  # the port is represented as a two-byte hexadecimal number
 
-  # The IPv4 address portion is a little-endian four-byte hexadecimal number.
+  # The IP address portion is a little-endian four-byte hexadecimal number.
   # That is, the least significant byte is listed first, so we need to reverse
   # the order of the bytes to convert it to an IP address.
   #
   # This needs to account for the endian ordering as per...
+  #
   # http://code.google.com/p/psutil/issues/detail?id=201
   # https://trac.torproject.org/projects/tor/ticket/4777
 
-  if sys.byteorder == 'little':
-    ip = socket.inet_ntop(socket.AF_INET, base64.b16decode(ip)[::-1])
-  else:
-    ip = socket.inet_ntop(socket.AF_INET, base64.b16decode(ip))
+  ip_encoded = base64.b16decode(ip)[::-1] if sys.byteorder == 'little' else base64.b16decode(ip)
+  ip = socket.inet_ntop(socket.AF_INET6 if is_ipv6 else socket.AF_INET, ip_encoded)
 
   return (ip, port)
 
diff --git a/test/unit/util/proc.py b/test/unit/util/proc.py
index b6cb0d6..4b7d37f 100644
--- a/test/unit/util/proc.py
+++ b/test/unit/util/proc.py
@@ -2,21 +2,32 @@
 Unit testing code for the stem.util.proc functions.
 """
 
+import io
 import unittest
 
 from stem.util import proc
 from test import mocking
 
 try:
-  from StringIO import StringIO
-except ImportError:
-  from io import StringIO
-
-try:
   from unittest.mock import Mock, patch
 except ImportError:
   from mock import Mock, patch
 
+TCP6_CONTENT = b"""\
+  sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
+   0: 00000000000000000000000000000000:1495 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000   106        0 14347030 1 0000000000000000 100 0 0 10 0
+   1: 00000000000000000000000000000000:0035 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 1457 1 0000000000000000 100 0 0 10 0
+   2: 00000000000000000000000000000000:0217 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 6606 1 0000000000000000 100 0 0 10 0
+   3: F804012A4A5190010000000002000000:01BB 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 4372 1 0000000000000000 100 0 0 10 0
+   4: 00000000000000000000000000000000:14A1 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000   106        0 14347031 1 0000000000000000 100 0 0 10 0
+   5: 00000000000000000000000000000000:1466 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000   106        0 14347029 1 0000000000000000 100 0 0 10 0
+   6: F804012A4A5190010000000002000000:01BB 38060120404100A0000000008901FFFF:9DF3 01 00000000:00000000 00:00000000 00000000   101        0 42088802 1 0000000000000000 20 4 25 10 7
+   7: F804012A4A5190010000000002000000:01BB 58080120020002000000BBAA26153B56:ADB5 01 00000000:00000000 00:00000000 00000000   101        0 41691357 1 0000000000000000 24 4 32 10 7
+   8: 0000000000000000FFFF00004B9E0905:1466 0000000000000000FFFF00002186364E:95BA 01 00000000:00000000 02:000A5B3D 00000000   106        0 41878761 2 0000000000000000 26 4 30 10 -1
+   9: F804012A4A5190010000000002000000:1495 F806012011006F120000000026000000:C5A2 01 00000000:00000000 02:000A5B3D 00000000   106        0 41825895 2 0000000000000000 21 4 15 10 -1
+  10: 0000000000000000FFFF00004B9E0905:1466 0000000000000000FFFF00002186364E:951E 01 00000000:00000000 02:00090E70 00000000   106        0 41512577 2 0000000000000000 26 4 31 10 -1
+"""
+
 
 class TestProc(unittest.TestCase):
   @patch('stem.util.proc._get_line')
@@ -178,9 +189,10 @@ class TestProc(unittest.TestCase):
     self.assertEqual(6, proc.file_descriptors_used('2118'))
 
   @patch('os.listdir')
+  @patch('os.path.exists')
   @patch('os.readlink')
   @patch('stem.util.proc.open', create = True)
-  def test_connections(self, open_mock, readlink_mock, listdir_mock):
+  def test_connections(self, open_mock, readlink_mock, path_exists_mock, listdir_mock):
     """
     Tests the connections function.
     """
@@ -198,12 +210,19 @@ class TestProc(unittest.TestCase):
       '/proc/%s/fd/4' % pid: 'pipe:[40404]',
     }[param]
 
-    tcp = '\n 0: 11111111:1111 22222222:2222 01 44444444:44444444 55:55555555 66666666 1111 8 99999999'
-    udp = '\n A: BBBBBBBB:BBBB CCCCCCCC:CCCC DD EEEEEEEE:EEEEEEEE FF:FFFFFFFF GGGGGGGG 1111 H IIIIIIII'
+    tcp = b'\n 0: 11111111:1111 22222222:2222 01 44444444:44444444 55:55555555 66666666 1111 8 99999999'
+    udp = b'\n A: BBBBBBBB:BBBB CCCCCCCC:CCCC DD EEEEEEEE:EEEEEEEE FF:FFFFFFFF GGGGGGGG 1111 H IIIIIIII'
+
+    path_exists_mock.side_effect = lambda param: {
+      '/proc/net/tcp': True,
+      '/proc/net/tcp6': False,
+      '/proc/net/udp': True,
+      '/proc/net/udp6': False
+    }[param]
 
     open_mock.side_effect = lambda param: {
-      '/proc/net/tcp': StringIO(tcp),
-      '/proc/net/udp': StringIO(udp)
+      '/proc/net/tcp': io.BytesIO(tcp),
+      '/proc/net/udp': io.BytesIO(udp)
     }[param]
 
     # tests the edge case of pid = 0
@@ -215,3 +234,47 @@ class TestProc(unittest.TestCase):
     ]
 
     self.assertEqual(expected_results, proc.connections(pid))
+
+  @patch('os.listdir')
+  @patch('os.path.exists')
+  @patch('os.readlink')
+  @patch('stem.util.proc.open', create = True)
+  def test_connections_ipv6(self, open_mock, readlink_mock, path_exists_mock, listdir_mock):
+    """
+    Tests the connections function with ipv6 addresses.
+    """
+
+    pid = 1111
+
+    listdir_mock.side_effect = lambda param: {
+      '/proc/%s/fd' % pid: ['1', '2', '3', '4'],
+    }[param]
+
+    readlink_mock.side_effect = lambda param: {
+      '/proc/%s/fd/1' % pid: 'socket:[42088802]',
+      '/proc/%s/fd/2' % pid: 'socket:[41691357]',
+      '/proc/%s/fd/3' % pid: 'socket:[41878761]',
+      '/proc/%s/fd/4' % pid: 'socket:[41825895]',
+      '/proc/%s/fd/5' % pid: 'socket:[41512577]',
+      '/proc/%s/fd/6' % pid: 'socket:[14347030]',  # this shouldn't be present due to being unestablished
+    }[param]
+
+    path_exists_mock.side_effect = lambda param: {
+      '/proc/net/tcp': False,
+      '/proc/net/tcp6': True,
+      '/proc/net/udp': False,
+      '/proc/net/udp6': False
+    }[param]
+
+    open_mock.side_effect = lambda param: {
+      '/proc/net/tcp6': io.BytesIO(TCP6_CONTENT),
+    }[param]
+
+    expected_results = [
+      ('0:2::190:514a:2a01:4f8', 443, 'ffff:189::a000:4140:2001:638', 40435, 'tcp', True),
+      ('0:2::190:514a:2a01:4f8', 443, '563b:1526:aabb:0:2:2:2001:858', 44469, 'tcp', True),
+      ('509:9e4b:0:ffff::', 5222, '4e36:8621:0:ffff::', 38330, 'tcp', True),
+      ('0:2::190:514a:2a01:4f8', 5269, '0:26::126f:11:2001:6f8', 50594, 'tcp', True),
+    ]
+
+    self.assertEqual(expected_results, proc.connections(pid))



More information about the tor-commits mailing list