[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