commit f315a2a7212b3846423ce59cb452107d76815b52 Author: Damian Johnson atagar@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))
tor-commits@lists.torproject.org