[tor-commits] [stem/master] Basic IPv6 support in get_connections()
atagar at torproject.org
atagar at torproject.org
Sat Jan 16 23:35:59 UTC 2016
commit 48dcdb9434924d01915a7d2a937bbbe458369f48
Author: Damian Johnson <atagar at torproject.org>
Date: Sat Jan 16 15:10:56 2016 -0800
Basic IPv6 support in get_connections()
Now that I have some IPv6 output courtesy of ticket 18079 I can add IPv6
support. This is presently pretty simple in that we merely include IPv6
addresses if they're present. Many connection resolvers probably need special
arguments to include them in their output and we don't yet do that (but patches
welcome!).
---
docs/change_log.rst | 3 +-
stem/util/connection.py | 62 ++++++++++++++++++++++++++----------------
test/unit/util/connection.py | 8 ++++++
3 files changed, 48 insertions(+), 25 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index 359156e..0640aef 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -72,7 +72,8 @@ The following are only available within Stem's `git repository
* **Utilities**
* Added :func:`~stem.util.__init__.datetime_to_unix`
- * Pattern used by our 'ss' connection resolver didn't work on Gentoo (:trac:`18079`)
+ * Basic IPv6 support in :func:`~stem.util.connection.get_connections`
+ * The 'ss' connection resolver didn't work on Gentoo (:trac:`18079`)
* **Interpreter**
diff --git a/stem/util/connection.py b/stem/util/connection.py
index c780f2d..0490f9c 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -110,25 +110,25 @@ RESOLVER_FILTER = {
Resolver.PROC: '',
# tcp 0 586 192.168.0.1:44284 38.229.79.2:443 ESTABLISHED 15843/tor
- Resolver.NETSTAT: '^{protocol}\s+.*\s+{local_address}:{local_port}\s+{remote_address}:{remote_port}\s+ESTABLISHED\s+{pid}/{name}\s*$',
+ Resolver.NETSTAT: '^{protocol}\s+.*\s+{local}\s+{remote}\s+ESTABLISHED\s+{pid}/{name}\s*$',
# tcp 586 192.168.0.1:44284 38.229.79.2:443 ESTABLISHED 15843
- Resolver.NETSTAT_WINDOWS: '^\s*{protocol}\s+{local_address}:{local_port}\s+{remote_address}:{remote_port}\s+ESTABLISHED\s+{pid}\s*$',
+ Resolver.NETSTAT_WINDOWS: '^\s*{protocol}\s+{local}\s+{remote}\s+ESTABLISHED\s+{pid}\s*$',
# tcp ESTAB 0 0 192.168.0.20:44415 38.229.79.2:443 users:(("tor",15843,9))
- Resolver.SS: '^{protocol}\s+ESTAB\s+.*\s+{local_address}:{local_port}\s+{remote_address}:{remote_port}\s+users:\(\("{name}",(?:pid=)?{pid},(?:fd=)?[0-9]+\)\)$',
+ Resolver.SS: '^{protocol}\s+ESTAB\s+.*\s+{local}\s+{remote}\s+users:\(\("{name}",(?:pid=)?{pid},(?:fd=)?[0-9]+\)\)$',
# tor 3873 atagar 45u IPv4 40994 0t0 TCP 10.243.55.20:45724->194.154.227.109:9001 (ESTABLISHED)
- Resolver.LSOF: '^{name}\s+{pid}\s+.*\s+{protocol}\s+{local_address}:{local_port}->{remote_address}:{remote_port} \(ESTABLISHED\)$',
+ Resolver.LSOF: '^{name}\s+{pid}\s+.*\s+{protocol}\s+{local}->{remote} \(ESTABLISHED\)$',
# atagar tor 15843 tcp4 192.168.0.20:44092 68.169.35.102:443 ESTABLISHED
- Resolver.SOCKSTAT: '^\S+\s+{name}\s+{pid}\s+{protocol}4\s+{local_address}:{local_port}\s+{remote_address}:{remote_port}\s+ESTABLISHED$',
+ Resolver.SOCKSTAT: '^\S+\s+{name}\s+{pid}\s+{protocol}4\s+{local}\s+{remote}\s+ESTABLISHED$',
# _tor tor 4397 12 tcp4 172.27.72.202:54011 127.0.0.1:9001
- Resolver.BSD_SOCKSTAT: '^\S+\s+{name}\s+{pid}\s+\S+\s+{protocol}4\s+{local_address}:{local_port}\s+{remote_address}:{remote_port}$',
+ Resolver.BSD_SOCKSTAT: '^\S+\s+{name}\s+{pid}\s+\S+\s+{protocol}4\s+{local}\s+{remote}$',
# 3561 tor 4 s - rw---n-- 2 0 TCP 10.0.0.2:9050 10.0.0.1:22370
- Resolver.BSD_PROCSTAT: '^\s*{pid}\s+{name}\s+.*\s+{protocol}\s+{local_address}:{local_port}\s+{remote_address}:{remote_port}$',
+ Resolver.BSD_PROCSTAT: '^\s*{pid}\s+{name}\s+.*\s+{protocol}\s+{local}\s+{remote}$',
}
@@ -147,10 +147,16 @@ class Connection(collections.namedtuple('Connection', ['local_address', 'local_p
def get_connections(resolver, process_pid = None, process_name = None):
"""
Retrieves a list of the current connections for a given process. This
- provides a list of :class:`~stem.util.connection.Connection`.
+ provides a list of :class:`~stem.util.connection.Connection`. Note that
+ addresses may be IPv4 *or* IPv6 depending on what the platform supports.
.. versionadded:: 1.1.0
+ .. versionchanged:: 1.5.0
+ Basic IPv6 support. This is incomplete in that resolver commands we run
+ may not surface IPv6 connections. But when present this function now
+ includes them in our results.
+
:param Resolver resolver: method of connection resolution to use
:param int process_pid: pid of the process to retrieve
:param str process_name: name of the process to retrieve
@@ -197,10 +203,8 @@ def get_connections(resolver, process_pid = None, process_name = None):
resolver_regex_str = RESOLVER_FILTER[resolver].format(
protocol = '(?P<protocol>\S+)',
- local_address = '(?P<local_address>[0-9.]+)',
- local_port = '(?P<local_port>[0-9]+)',
- remote_address = '(?P<remote_address>[0-9.]+)',
- remote_port = '(?P<remote_port>[0-9]+)',
+ local = '(?P<local>[0-9a-f.:]+)',
+ remote = '(?P<remote>[0-9a-f.:]+)',
pid = process_pid if process_pid else '[0-9]*',
name = process_name if process_name else '\S*',
)
@@ -211,26 +215,36 @@ def get_connections(resolver, process_pid = None, process_name = None):
connections = []
resolver_regex = re.compile(resolver_regex_str)
+ def _parse_address_str(addr_type, addr_str, line):
+ addr, port = addr_str.rsplit(':', 1)
+
+ if not is_valid_ipv4_address(addr) and not is_valid_ipv6_address(addr):
+ _log('Invalid %s address (%s): %s' % (addr_type, addr, line))
+ return None, None
+ elif not is_valid_port(port):
+ _log('Invalid %s port (%s): %s' % (addr_type, port, line))
+ return None, None
+ else:
+ _log('Valid %s:%s: %s' % (addr, port, line))
+ return addr, int(port)
+
for line in results:
match = resolver_regex.match(line)
if match:
attr = match.groupdict()
- local_addr = attr['local_address']
- local_port = int(attr['local_port'])
- remote_addr = attr['remote_address']
- remote_port = int(attr['remote_port'])
- protocol = attr['protocol'].lower()
- if remote_addr == '0.0.0.0':
- continue # procstat response for unestablished connections
+ local_addr, local_port = _parse_address_str('local', attr['local'], line)
+ remote_addr, remote_port = _parse_address_str('remote', attr['remote'], line)
+
+ if not (local_addr and local_port and remote_addr and remote_port):
+ continue # missing or malformed field
+
+ protocol = attr['protocol'].lower()
- if not (is_valid_ipv4_address(local_addr) and is_valid_ipv4_address(remote_addr)):
- _log('Invalid address (%s or %s): %s' % (local_addr, remote_addr, line))
- elif not (is_valid_port(local_port) and is_valid_port(remote_port)):
- _log('Invalid port (%s or %s): %s' % (local_port, remote_port, line))
- elif protocol not in ('tcp', 'udp'):
+ if protocol not in ('tcp', 'udp'):
_log('Unrecognized protocol (%s): %s' % (protocol, line))
+ continue
conn = Connection(local_addr, local_port, remote_addr, remote_port, protocol)
connections.append(conn)
diff --git a/test/unit/util/connection.py b/test/unit/util/connection.py
index 8f3820c..776182d 100644
--- a/test/unit/util/connection.py
+++ b/test/unit/util/connection.py
@@ -253,9 +253,17 @@ class TestConnection(unittest.TestCase):
expected = [
Connection('5.9.158.75', 443, '107.170.93.13', 56159, 'tcp'),
Connection('5.9.158.75', 443, '159.203.97.91', 37802, 'tcp'),
+ Connection('2a01:4f8:190:514a::2', 443, '2001:638:a000:4140::ffff:189', 38556, 'tcp'),
+ Connection('2a01:4f8:190:514a::2', 443, '2001:858:2:2:aabb:0:563b:1526', 51428, 'tcp'),
]
self.assertEqual(expected, stem.util.connection.get_connections(Resolver.SS, process_pid = 25056, process_name = 'tor'))
+ # TODO: This example ss output has entries like "::ffff:5.9.158.75:5222".
+ # What is this? Looks like a IPv6 *and* IPv4 address mashed together. Our
+ # resolver understandably thinks this is invalid right now.
+
+ self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_name = 'beam')
+
@patch('stem.util.system.call')
def test_get_connections_by_lsof(self, call_mock):
"""
More information about the tor-commits
mailing list