[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [stem/master] Connection resolution support



commit 1082349b59eb6ecd89eca1a1f2aa2ab1df007315
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date:   Sun Sep 22 23:38:17 2013 -0700

    Connection resolution support
    
    Adding long overdue support for process connection resolution...
    
    https://trac.torproject.org/7910
    
    This is a highly popular capability of arm, and stem's counterpart for it is
    quite a bit cleaner (with tests!). That said, this still doesn't get around
    tor's annoying DisableDebuggerAttachment feature which screws up proc
    permissions. That's something I'll need to figure out before our next arm or
    stem release...
---
 docs/change_log.rst           |    1 +
 stem/util/connection.py       |  195 ++++++++++++++++++++++++++++++++++--
 stem/util/proc.py             |   18 +++-
 test/integ/util/__init__.py   |    2 +-
 test/integ/util/connection.py |   33 +++++++
 test/settings.cfg             |    1 +
 test/unit/util/connection.py  |  217 ++++++++++++++++++++++++++++++++++++++++-
 7 files changed, 455 insertions(+), 12 deletions(-)

diff --git a/docs/change_log.rst b/docs/change_log.rst
index 13b256d..505261a 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -58,6 +58,7 @@ The following are only available within stem's `git repository
 
  * **Utilities**
 
+  * Connection resolution via the :func:`~stem.util.connection.get_connections` function (:trac:`7910`)
   * :func:`~stem.util.system.set_process_name` inserted spaces between characters (:trac:`8631`)
   * :func:`~stem.util.system.get_pid_by_name` can now pull for all processes with a given name
   * :func:`~stem.util.system.call` ignored the subprocess' exit status
diff --git a/stem/util/connection.py b/stem/util/connection.py
index e0b08df..4605a57 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -2,22 +2,40 @@
 # See LICENSE for licensing information
 
 """
-Connection and networking based utility functions. This will likely be expanded
-later to have all of `arm's functions
-<https://gitweb.torproject.org/arm.git/blob/HEAD:/src/util/connections.py>`_,
-but for now just moving the parts we need.
+Connection and networking based utility functions.
 
 ::
 
+  get_connections - quieries the connections belonging to a given process
+  get_system_resolvers - provides connection resolution methods that are likely to be available
+
   is_valid_ipv4_address - checks if a string is a valid IPv4 address
   is_valid_ipv6_address - checks if a string is a valid IPv6 address
   is_valid_port - checks if something is a valid representation for a port
   is_private_address - checks if an IPv4 address belongs to a private range or not
+
   expand_ipv6_address - provides an IPv6 address with its collapsed portions expanded
   get_mask_ipv4 - provides the mask representation for a given number of bits
   get_mask_ipv6 - provides the IPv6 mask representation for a given number of bits
+
+.. data:: Resolver (enum)
+
+  Method for resolving a process' connections.
+
+  ================= ===========
+  Resolver          Description
+  ================= ===========
+  **PROC**          /proc contents
+  **NETSTAT**       netstat command
+  **SS**            ss command
+  **LSOF**          lsof command
+  **SOCKSTAT**      sockstat command under *nix
+  **BSD_SOCKSTAT**  sockstat command under FreeBSD
+  **BSD_PROCSTAT**  procstat command under FreeBSD
+  ================= ===========
 """
 
+import collections
 import hashlib
 import hmac
 import os
@@ -25,8 +43,16 @@ import platform
 import re
 
 import stem.util.proc
+import stem.util.system
 
-from stem.util import enum
+from stem.util import enum, log
+
+# Connection resolution is risky to log about since it's highly likely to
+# contain sensitive information. That said, it's also difficult to get right in
+# a platform independent fashion. To opt into the logging requried to
+# troubleshoot connection resolution set the following...
+
+LOG_CONNECTION_RESOLUTION = False
 
 Resolver = enum.Enum(
   ('PROC', 'proc'),
@@ -38,11 +64,162 @@ Resolver = enum.Enum(
   ('BSD_PROCSTAT', 'procstat (bsd)')
 )
 
+Connection = collections.namedtuple('Connection', [
+  'local_address',
+  'local_port',
+  'remote_address',
+  'remote_port',
+  'protocol',
+])
+
 FULL_IPv4_MASK = "255.255.255.255"
 FULL_IPv6_MASK = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"
 
 CRYPTOVARIABLE_EQUALITY_COMPARISON_NONCE = os.urandom(32)
 
+RESOLVER_COMMAND = {
+  Resolver.PROC: '',
+
+  # -n = prevents dns lookups, -p = include process
+  Resolver.NETSTAT: 'netstat -np',
+
+  # -n = numeric ports, -p = include process, -t = tcp sockets, -u = udp sockets
+  Resolver.SS: 'ss -nptu',
+
+  # -n = prevent dns lookups, -P = show port numbers (not names), -i = ip only, -w = no warnings
+  # (lsof provides a '-p <pid>' but oddly in practice it seems to be ~11-28% slower)
+  Resolver.LSOF: 'lsof -wnPi',
+
+  Resolver.SOCKSTAT: 'sockstat',
+
+  # -4 = IPv4, -c = connected sockets
+  Resolver.BSD_SOCKSTAT: 'sockstat -4c',
+
+  # -f <pid> = process pid
+  Resolver.BSD_PROCSTAT: 'procstat -f {pid}',
+}
+
+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*$',
+
+  # 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},[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\)$',
+
+  # 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$',
+
+  # _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}$',
+
+  # 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}$',
+}
+
+
+def get_connections(resolver, process_pid = None, process_name = None):
+  """
+  Retrieves a list of the current connections for a given process. The provides
+  a list of Connection instances, which have four attributes...
+
+    * local_address (str)
+    * local_port (int)
+    * remote_address (str)
+    * remote_port (int)
+    * protocol (str, generally either 'tcp' or 'udp')
+
+  :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
+
+  :raises:
+    * **ValueError** if using **Resolver.PROC** or **Resolver.BSD_PROCSTAT**
+      and the process_pid wasn't provided
+
+    * **IOError** if no connections are available or resolution fails
+      (generally they're indistinguishable). The common causes are the
+      command being unavailable or permissions.
+  """
+
+  def _log(msg):
+    if LOG_CONNECTION_RESOLUTION:
+      log.debug(msg)
+
+  _log("=" * 80)
+  _log("Querying connections for resolver: %s, pid: %s, name: %s" % (resolver, process_pid, process_name))
+
+  if isinstance(process_pid, str):
+    try:
+      process_pid = int(process_pid)
+    except ValueError:
+      raise ValueError("Process pid was non-numeric: %s" % process_pid)
+
+  if process_pid is None and resolver in (Resolver.PROC, Resolver.BSD_PROCSTAT):
+    raise ValueError("%s resolution requires a pid" % resolver)
+
+  if resolver == Resolver.PROC:
+    return [Connection(*conn) for conn in stem.util.proc.get_connections(process_pid)]
+
+  resolver_command = RESOLVER_COMMAND[resolver].format(pid = process_pid)
+
+  try:
+    results = stem.util.system.call(resolver_command)
+  except OSError as exc:
+    raise IOError("Unable to query '%s': %s" % (resolver_command, exc))
+
+  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]+)',
+    pid = process_pid if process_pid else '[0-9]*',
+    name = process_name if process_name else '\S*',
+  )
+
+  _log("Resolver regex: %s" % resolver_regex_str)
+  _log("Resolver results:\n%s" % '\n'.join(results))
+
+  connections = []
+  resolver_regex = re.compile(resolver_regex_str)
+
+  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
+
+      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'):
+        _log("Unrecognized protocol (%s): %s" % (protocol, line))
+
+      conn = Connection(local_addr, local_port, remote_addr, remote_port, protocol)
+      connections.append(conn)
+      _log(str(conn))
+
+  _log("%i connections found" % len(connections))
+
+  if not connections:
+    raise IOError("No results found using: %s" % resolver_command)
+
+  return connections
+
 
 def get_system_resolvers(system = None):
   """
@@ -62,8 +239,14 @@ def get_system_resolvers(system = None):
   elif system in ('Darwin', 'OpenBSD'):
     resolvers = [Resolver.LSOF]
   elif system == 'FreeBSD':
+    # Netstat is available, but lacks a '-p' equivilant so we can't associate
+    # the results to processes. The platform also has a ss command, but it
+    # belongs to a spreadsheet application.
+
     resolvers = [Resolver.BSD_SOCKSTAT, Resolver.BSD_PROCSTAT, Resolver.LSOF]
   else:
+    # Sockstat isn't available by default on ubuntu.
+
     resolvers = [Resolver.NETSTAT, Resolver.SOCKSTAT, Resolver.LSOF, Resolver.SS]
 
   # proc resolution, by far, outperforms the others so defaults to this is able
@@ -193,7 +376,7 @@ def is_private_address(address):
   if address.startswith("172."):
     second_octet = int(address.split('.')[1])
 
-    if second_octet >= 16 and second_octet <= 31: 
+    if second_octet >= 16 and second_octet <= 31:
       return True
 
   return False
diff --git a/stem/util/proc.py b/stem/util/proc.py
index b7efdef..401c319 100644
--- a/stem/util/proc.py
+++ b/stem/util/proc.py
@@ -309,23 +309,32 @@ def get_connections(pid):
   :param int pid: process id of the process to be queried
 
   :returns: A listing of connection tuples of the form **[(local_ipAddr1,
-    local_port1, foreign_ipAddr1, foreign_port1), ...]** (IP addresses are
-    strings and ports are ints)
+    local_port1, foreign_ipAddr1, foreign_port1, protocol), ...]** (addresses
+    and protocols are strings and ports are ints)
 
   :raises: **IOError** if it can't be determined
   """
 
+  if isinstance(pid, str):
+    try:
+      pid = int(pid)
+    except ValueError:
+      raise IOError("Process pid was non-numeric: %s" % pid)
+
   if pid == 0:
     return []
 
   # fetches the inode numbers for socket file descriptors
+
   start_time, parameter = time.time(), "process connections"
   inodes = []
+
   for fd in os.listdir("/proc/%s/fd" % pid):
     fd_path = "/proc/%s/fd/%s" % (pid, fd)
 
     try:
       # File descriptor link, such as 'socket:[30899]'
+
       fd_name = os.readlink(fd_path)
 
       if fd_name.startswith('socket:['):
@@ -341,7 +350,9 @@ def get_connections(pid):
     return []
 
   # check for the connection information from the /proc/net contents
+
   conn = []
+
   for proc_file_path in ("/proc/net/tcp", "/proc/net/udp"):
     try:
       proc_file = open(proc_file_path)
@@ -357,7 +368,8 @@ def get_connections(pid):
 
           local_ip, local_port = _decode_proc_address_encoding(l_addr)
           foreign_ip, foreign_port = _decode_proc_address_encoding(f_addr)
-          conn.append((local_ip, local_port, foreign_ip, foreign_port))
+          protocol = proc_file_path[10:]
+          conn.append((local_ip, local_port, foreign_ip, foreign_port, protocol))
 
       proc_file.close()
     except IOError as exc:
diff --git a/test/integ/util/__init__.py b/test/integ/util/__init__.py
index eac6be5..2371a4e 100644
--- a/test/integ/util/__init__.py
+++ b/test/integ/util/__init__.py
@@ -2,4 +2,4 @@
 Integration tests for stem.util.* contents.
 """
 
-__all__ = ["conf", "proc", "system"]
+__all__ = ["conf", "connection", "proc", "system"]
diff --git a/test/integ/util/connection.py b/test/integ/util/connection.py
new file mode 100644
index 0000000..70ad7f9
--- /dev/null
+++ b/test/integ/util/connection.py
@@ -0,0 +1,33 @@
+"""
+Integration tests for stem.util.connection functions against the tor process
+that we're running.
+"""
+
+import unittest
+
+import test.runner
+
+from stem.util.connection import get_connections, get_system_resolvers
+
+
+class TestConnection(unittest.TestCase):
+  def test_get_connections(self):
+    runner = test.runner.get_runner()
+
+    if not test.runner.Torrc.PORT in runner.get_options():
+      test.runner.skip(self, "(no control port)")
+      return
+    elif not test.runner.get_runner().is_ptraceable():
+      test.runner.skip(self, "(DisableDebuggerAttachment is set)")
+      return
+
+    for resolver in get_system_resolvers():
+      with runner.get_tor_socket():
+        tor_pid = test.runner.get_runner().get_pid()
+        connections = get_connections(resolver, process_pid = tor_pid)
+
+        for conn in connections:
+          if conn.local_address == '127.0.0.1' and conn.local_port == test.runner.CONTROL_PORT:
+            return
+
+        self.fail("Unable to find localhost connection with %s:\n%s" % (resolver, '\n'.join(connections)))
diff --git a/test/settings.cfg b/test/settings.cfg
index b97c57f..9838012 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -185,6 +185,7 @@ test.unit_tests
 
 test.integ_tests
 |test.integ.util.conf.TestConf
+|test.integ.util.connection.TestConnection
 |test.integ.util.proc.TestProc
 |test.integ.util.system.TestSystem
 |test.integ.descriptor.reader.TestDescriptorReader
diff --git a/test/unit/util/connection.py b/test/unit/util/connection.py
index 40a2e02..03d83c1 100644
--- a/test/unit/util/connection.py
+++ b/test/unit/util/connection.py
@@ -9,7 +9,83 @@ from mock import patch
 
 import stem.util.connection
 
-from stem.util.connection import Resolver
+from stem.util.connection import Resolver, Connection
+
+NETSTAT_OUTPUT = """\
+Active Internet connections (w/o servers)
+Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
+tcp        0      0 192.168.0.1:5939        73.94.23.87:443         ESTABLISHED 20586/firefox
+tcp        0      0 192.168.0.1:4325        73.94.23.55:443         ESTABLISHED 20586/firefox
+tcp        1      0 192.168.0.1:4378        29.208.141.42:443       CLOSE_WAIT  20586/firefox
+tcp        0      0 127.0.0.1:22            127.0.0.1:56673         ESTABLISHED -
+tcp        0    586 192.168.0.1:44284       38.229.79.2:443         ESTABLISHED 15843/tor
+tcp        0      0 192.168.0.1:37909       16.111.19.278:6697      ESTABLISHED -
+Active UNIX domain sockets (w/o servers)
+Proto RefCnt Flags       Type       State         I-Node   PID/Program name    Path
+unix  14     [ ]         DGRAM                    8433     -                   /dev/log
+unix  3      [ ]         STREAM     CONNECTED     34164277 15843/tor
+unix  3      [ ]         STREAM     CONNECTED     34164276 15843/tor
+unix  3      [ ]         STREAM     CONNECTED     7951     -
+"""
+
+SS_OUTPUT = """\
+Netid  State      Recv-Q Send-Q     Local Address:Port       Peer Address:Port
+tcp    CLOSE-WAIT 1      0           192.168.0.1:43780      53.203.145.45:443    users:(("firefox",20586,118))
+tcp    ESTAB      55274  0           192.168.0.1:46136     196.153.236.35:80     users:(("firefox",20586,93))
+tcp    ESTAB      0      0           192.168.0.1:44092      23.112.135.72:443    users:(("tor",15843,10))
+tcp    ESTAB      0      0              127.0.0.1:22            127.0.0.1:56673
+tcp    ESTAB      0      0           192.168.0.1:44415        38.229.79.2:443    users:(("tor",15843,9))
+"""
+
+LSOF_OUTPUT = """\
+COMMAND     PID   USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
+ubuntu-ge  2164 atagar   11u  IPv4    13593      0t0  TCP 192.168.0.1:55395->21.89.91.78:80 (CLOSE_WAIT)
+tor       15843 atagar    6u  IPv4 34164278      0t0  TCP 127.0.0.1:9050 (LISTEN)
+tor       15843 atagar    7u  IPv4 34164279      0t0  TCP 127.0.0.1:9051 (LISTEN)
+tor       15843 atagar    9u  IPv4 34188132      0t0  TCP 192.168.0.1:44415->38.229.79.2:443 (ESTABLISHED)
+tor       15843 atagar   10u  IPv4 34165291      0t0  TCP 192.168.0.1:44092->68.169.35.102:443 (ESTABLISHED)
+python    16422 atagar    3u  IPv4 34203773      0t0  UDP 127.0.0.1:39624->127.0.0.1:53
+firefox   20586 atagar   66u  IPv4  5765353      0t0  TCP 192.168.0.1:47486->62.135.16.134:443 (ESTABLISHED)
+firefox   20586 atagar   71u  IPv4 13094989      0t0  TCP 192.168.0.1:43762->182.3.10.42:443 (CLOSE_WAIT)
+"""
+
+SOCKSTAT_OUTPUT = """\
+USER     PROCESS              PID      PROTO  SOURCE ADDRESS            FOREIGN ADDRESS           STATE
+atagar   ubuntu-geoip-pr      2164     tcp4   192.168.0.1:55395         141.18.34.33:80           CLOSE_WAIT
+atagar   tor                  15843    tcp4   127.0.0.1:9050            *:*                       LISTEN
+atagar   tor                  15843    tcp4   127.0.0.1:9051            *:*                       LISTEN
+atagar   tor                  15843    tcp4   192.168.0.1:44415         38.229.79.2:443           ESTABLISHED
+atagar   tor                  15843    tcp4   192.168.0.1:44092         68.169.35.102:443         ESTABLISHED
+atagar   firefox              20586    tcp4   192.168.0.1:47486         213.24.100.160:443        ESTABLISHED
+atagar   firefox              20586    tcp4   192.168.0.1:43762         32.188.221.72:443         CLOSE_WAIT
+"""
+
+# I don't have actual sockstat and procstat output for FreeBSD. Rather, these
+# are snippets of output from email threads.
+
+BSD_SOCKSTAT_OUTPUT = """\
+_tor     tor        4397  7  tcp4   172.27.72.202:9050    *:*
+_tor     tor        4397  8  udp4   172.27.72.202:53      *:*
+_tor     tor        4397  9  tcp4   172.27.72.202:9051    *:*
+_tor     tor        4397  12 tcp4   172.27.72.202:54011   38.229.79.2:9001
+_tor     tor        4397  15 tcp4   172.27.72.202:59374   68.169.35.102:9001
+_tor     tor        4397  19 tcp4   172.27.72.202:59673   213.24.100.160:9001
+_tor     tor        4397  20 tcp4   172.27.72.202:51946   32.188.221.72:443
+_tor     tor        4397  22 tcp4   172.27.72.202:60344   21.89.91.78:9001
+"""
+
+BSD_PROCSTAT_OUTPUT = """\
+  PID COMM               FD T V FLAGS    REF  OFFSET PRO NAME
+ 3561 tor                 4 s - rw---n--   2       0 TCP 10.0.0.2:9050 10.0.0.1:22370
+ 3561 tor                 5 s - rw---n--   2       0 TCP 10.0.0.2:9050 0.0.0.0:0
+ 3561 tor                 6 s - rw---n--   2       0 TCP 10.0.0.2:9040 0.0.0.0:0
+ 3561 tor                 7 s - rw---n--   2       0 UDP 10.0.0.2:53 0.0.0.0:0
+ 3561 tor                 8 s - rw---n--   2       0 TCP 10.0.0.2:9051 0.0.0.0:0
+ 3561 tor                14 s - rw---n--   2       0 TCP 10.0.0.2:9050 10.0.0.1:44381
+ 3561 tor                15 s - rw---n--   2       0 TCP 10.0.0.2:33734 38.229.79.2:443
+ 3561 tor                16 s - rw---n--   2       0 TCP 10.0.0.2:47704 68.169.35.102:9001
+"""
+
 
 class TestConnection(unittest.TestCase):
   @patch('stem.util.proc.is_available')
@@ -27,7 +103,6 @@ class TestConnection(unittest.TestCase):
     self.assertEqual([Resolver.NETSTAT, Resolver.SOCKSTAT, Resolver.LSOF, Resolver.SS], stem.util.connection.get_system_resolvers('Linux'))
 
     proc_mock.return_value = True
-
     self.assertEqual([Resolver.PROC, Resolver.NETSTAT, Resolver.SOCKSTAT, Resolver.LSOF, Resolver.SS], stem.util.connection.get_system_resolvers('Linux'))
 
     # check that calling without an argument is equivilant to calling for this
@@ -35,6 +110,144 @@ class TestConnection(unittest.TestCase):
 
     self.assertEqual(stem.util.connection.get_system_resolvers(platform.system()), stem.util.connection.get_system_resolvers())
 
+  @patch('stem.util.proc.get_connections')
+  def test_get_connections_by_proc(self, proc_mock):
+    """
+    Checks the get_connections function with the proc resolver.
+    """
+
+    proc_mock.return_value = [
+      ('17.17.17.17', 4369, '34.34.34.34', 8738, 'tcp'),
+      ('187.187.187.187', 48059, '204.204.204.204', 52428, 'tcp'),
+    ]
+
+    expected = [
+      Connection('17.17.17.17', 4369, '34.34.34.34', 8738, 'tcp'),
+      Connection('187.187.187.187', 48059, '204.204.204.204', 52428, 'tcp'),
+    ]
+
+    self.assertEqual(expected, stem.util.connection.get_connections(Resolver.PROC, process_pid = 1111))
+
+    proc_mock.side_effect = IOError('No connections for you!')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.PROC, process_pid = 1111)
+
+  @patch('stem.util.system.call')
+  def test_get_connections_by_netstat(self, call_mock):
+    """
+    Checks the get_connections function with the netstat resolver.
+    """
+
+    call_mock.return_value = NETSTAT_OUTPUT.split('\n')
+    expected = [Connection('192.168.0.1', 44284, '38.229.79.2', 443, 'tcp')]
+    self.assertEqual(expected, stem.util.connection.get_connections(Resolver.NETSTAT, process_pid = 15843, process_name = 'tor'))
+
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 15843, process_name = 'stuff')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 1111, process_name = 'tor')
+
+    call_mock.side_effect = OSError('Unable to call netstat')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 1111)
+
+  @patch('stem.util.system.call')
+  def test_get_connections_by_ss(self, call_mock):
+    """
+    Checks the get_connections function with the ss resolver.
+    """
+
+    call_mock.return_value = SS_OUTPUT.split('\n')
+    expected = [
+      Connection('192.168.0.1', 44092, '23.112.135.72', 443, 'tcp'),
+      Connection('192.168.0.1', 44415, '38.229.79.2', 443, 'tcp'),
+    ]
+    self.assertEqual(expected, stem.util.connection.get_connections(Resolver.SS, process_pid = 15843, process_name = 'tor'))
+
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_pid = 15843, process_name = 'stuff')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_pid = 1111, process_name = 'tor')
+
+    call_mock.side_effect = OSError('Unable to call ss')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_pid = 1111)
+
+  @patch('stem.util.system.call')
+  def test_get_connections_by_lsof(self, call_mock):
+    """
+    Checks the get_connections function with the lsof resolver.
+    """
+
+    call_mock.return_value = LSOF_OUTPUT.split('\n')
+    expected = [
+      Connection('192.168.0.1', 44415, '38.229.79.2', 443, 'tcp'),
+      Connection('192.168.0.1', 44092, '68.169.35.102', 443, 'tcp'),
+    ]
+    self.assertEqual(expected, stem.util.connection.get_connections(Resolver.LSOF, process_pid = 15843, process_name = 'tor'))
+
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 15843, process_name = 'stuff')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 1111, process_name = 'tor')
+
+    call_mock.side_effect = OSError('Unable to call lsof')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 1111)
+
+  @patch('stem.util.system.call')
+  def test_get_connections_by_sockstat(self, call_mock):
+    """
+    Checks the get_connections function with the sockstat resolver.
+    """
+
+    call_mock.return_value = SOCKSTAT_OUTPUT.split('\n')
+    expected = [
+      Connection('192.168.0.1', 44415, '38.229.79.2', 443, 'tcp'),
+      Connection('192.168.0.1', 44092, '68.169.35.102', 443, 'tcp'),
+    ]
+    self.assertEqual(expected, stem.util.connection.get_connections(Resolver.SOCKSTAT, process_pid = 15843, process_name = 'tor'))
+
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SOCKSTAT, process_pid = 15843, process_name = 'stuff')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SOCKSTAT, process_pid = 1111, process_name = 'tor')
+
+    call_mock.side_effect = OSError('Unable to call sockstat')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SOCKSTAT, process_pid = 1111)
+
+  @patch('stem.util.system.call')
+  def test_get_connections_by_sockstat_for_bsd(self, call_mock):
+    """
+    Checks the get_connections function with the bsd variant of the sockstat
+    resolver.
+    """
+
+    call_mock.return_value = BSD_SOCKSTAT_OUTPUT.split('\n')
+    expected = [
+      Connection('172.27.72.202', 54011, '38.229.79.2', 9001, 'tcp'),
+      Connection('172.27.72.202', 59374, '68.169.35.102', 9001, 'tcp'),
+      Connection('172.27.72.202', 59673, '213.24.100.160', 9001, 'tcp'),
+      Connection('172.27.72.202', 51946, '32.188.221.72', 443, 'tcp'),
+      Connection('172.27.72.202', 60344, '21.89.91.78', 9001, 'tcp'),
+    ]
+    self.assertEqual(expected, stem.util.connection.get_connections(Resolver.BSD_SOCKSTAT, process_pid = 4397, process_name = 'tor'))
+
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 4397, process_name = 'stuff')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 1111, process_name = 'tor')
+
+    call_mock.side_effect = OSError('Unable to call sockstat')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 1111)
+
+  @patch('stem.util.system.call')
+  def test_get_connections_by_procstat(self, call_mock):
+    """
+    Checks the get_connections function with the procstat resolver.
+    """
+
+    call_mock.return_value = BSD_PROCSTAT_OUTPUT.split('\n')
+    expected = [
+      Connection('10.0.0.2', 9050, '10.0.0.1', 22370, 'tcp'),
+      Connection('10.0.0.2', 9050, '10.0.0.1', 44381, 'tcp'),
+      Connection('10.0.0.2', 33734, '38.229.79.2', 443, 'tcp'),
+      Connection('10.0.0.2', 47704, '68.169.35.102', 9001, 'tcp'),
+    ]
+    self.assertEqual(expected, stem.util.connection.get_connections(Resolver.BSD_PROCSTAT, process_pid = 3561, process_name = 'tor'))
+
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 3561, process_name = 'stuff')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 1111, process_name = 'tor')
+
+    call_mock.side_effect = OSError('Unable to call procstat')
+    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 1111)
+
   def test_is_valid_ipv4_address(self):
     """
     Checks the is_valid_ipv4_address function.

_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits