[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Allow stem.util.proc.connection() to query by user
commit 4082270d068338fa01a433a9006a1073fc4d087b
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Sat Jan 30 12:56:27 2016 -0800
Allow stem.util.proc.connection() to query by user
Our proc contents provide connection metadata with only two things we can
filter by: inode and uid. Thus far we've used inodes to allow lookups by pid
(thing we usually want to resolve connections by), but soon I'm gonna have
a need for resolution by uid.
This also allows the funciton to provide all system connections if no pid or
user is provided.
---
stem/util/connection.py | 2 +-
stem/util/proc.py | 133 ++++++++++++++++++++++++++----------------------
test/unit/util/proc.py | 47 +++++++++++++----
3 files changed, 111 insertions(+), 71 deletions(-)
diff --git a/stem/util/connection.py b/stem/util/connection.py
index e72bd12..342afde 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -202,7 +202,7 @@ def get_connections(resolver, process_pid = None, process_name = None):
raise IOError("There's multiple processes named '%s'. %s requires a single pid to provide the connections." % (process_name, resolver))
if resolver == Resolver.PROC:
- return [Connection(*conn) for conn in stem.util.proc.connections(process_pid)]
+ return stem.util.proc.connections(pid = process_pid)
resolver_command = RESOLVER_COMMAND[resolver].format(pid = process_pid)
diff --git a/stem/util/proc.py b/stem/util/proc.py
index 7fdd70f..40f9ba8 100644
--- a/stem/util/proc.py
+++ b/stem/util/proc.py
@@ -50,10 +50,12 @@ future, use them at your own risk.**
import base64
import os
import platform
+import pwd
import socket
import sys
import time
+import stem.util.connection
import stem.util.enum
import stem.util.str_tools
@@ -325,35 +327,87 @@ def file_descriptors_used(pid):
raise IOError('Unable to check number of file descriptors used: %s' % exc)
-def connections(pid):
+def connections(pid = None, user = None):
"""
- Queries connection related information from the proc contents. This provides
- similar results to netstat, lsof, sockstat, and other connection resolution
- utilities (though the lookup is far quicker).
+ Queries connections from the proc contents. This matches netstat, lsof, and
+ friends but is much faster. If no **pid** or **user** are provided this
+ provides all present connections.
- :param int pid: process id of the process to be queried
+ :param int pid: pid to provide connections for
+ :param str user: username to look up connections for
- :returns: A listing of connection tuples of the form **[(local_ipAddr1,
- local_port1, foreign_ipAddr1, foreign_port1, protocol, is_ipv6), ...]**
- (addresses and protocols are strings and ports are ints)
+ :returns: **list** of :class:`~stem.util.connection.Connection` instances
:raises: **IOError** if it can't be determined
"""
+ start_time, conn = time.time(), []
+
+ if pid:
+ parameter = 'connections for pid %s' % pid
+
+ try:
+ pid = int(pid)
+
+ if pid < 0:
+ raise IOError("Process pids can't be negative: %s" % pid)
+ except (ValueError, TypeError):
+ raise IOError('Process pid was non-numeric: %s' % pid)
+ elif user:
+ parameter = 'connections for user %s' % user
+ else:
+ parameter = 'all connections'
+
try:
- pid = int(pid)
+ inodes = _inodes_for_sockets(pid) if pid else []
+ process_uid = pwd.getpwnam(user).pw_uid if user else None
- if pid < 0:
- raise IOError("Process pids can't be negative: %s" % pid)
- except (ValueError, TypeError):
- raise IOError('Process pid was non-numeric: %s' % pid)
+ for proc_file_path in ('/proc/net/tcp', '/proc/net/tcp6', '/proc/net/udp', '/proc/net/udp6'):
+ if proc_file_path.endswith('6') and not os.path.exists(proc_file_path):
+ continue # ipv6 proc contents are optional
- if pid == 0:
- return []
+ try:
+ with open(proc_file_path, 'rb') as proc_file:
+ proc_file.readline() # skip the first line
+
+ for line in proc_file:
+ _, l_addr, f_addr, status, _, _, _, uid, _, inode = line.split()[:10]
+ protocol = proc_file_path[10:].rstrip('6') # 'tcp' or 'udp'
+ is_ipv6 = proc_file_path.endswith('6')
+
+ if inodes and inode not in inodes:
+ continue
+ elif process_uid and int(uid) != process_uid:
+ continue
+ elif protocol == 'tcp' and status != b'01':
+ continue # skip tcp connections that aren't yet established
+
+ 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(stem.util.connection.Connection(local_ip, local_port, foreign_ip, foreign_port, protocol, is_ipv6))
+ except IOError as exc:
+ raise IOError("unable to read '%s': %s" % (proc_file_path, exc))
+ except Exception as exc:
+ raise IOError("unable to parse '%s': %s" % (proc_file_path, exc))
+
+ _log_runtime(parameter, '/proc/net/[tcp|udp]', start_time)
+ return conn
+ except IOError as exc:
+ _log_failure(parameter, exc)
+ raise
+
+
+def _inodes_for_sockets(pid):
+ """
+ Provides inodes in use by a process for its sockets.
- # fetches the inode numbers for socket file descriptors
+ :param int pid: process id of the process to be queried
+
+ :returns: **list** with inodes for its sockets
+
+ :raises: **IOError** if it can't be determined
+ """
- start_time, parameter = time.time(), 'process connections'
inodes = []
try:
@@ -376,50 +430,9 @@ def connections(pid):
continue # descriptors may shift while we're in the middle of iterating over them
# most likely couldn't be read due to permissions
- exc = IOError('unable to determine file descriptor destination (%s): %s' % (exc, fd_path))
- _log_failure(parameter, exc)
- raise exc
-
- if not inodes:
- # unable to fetch any connections for this process
- return []
-
- # check for the connection information from the /proc/net contents
-
- conn = []
-
- 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:
- with open(proc_file_path, 'rb') as proc_file:
- proc_file.readline() # skip the first line
-
- for line in proc_file:
- _, l_addr, f_addr, status, _, _, _, _, _, inode = line.split()[:10]
-
- if inode in inodes:
- protocol = proc_file_path[10:].rstrip('6') # 'tcp' or 'udp'
- is_ipv6 = proc_file_path.endswith('6')
-
- if protocol == 'tcp' and status != b'01':
- continue # skip tcp connections that aren't yet established
-
- 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)
- raise exc
- except Exception as exc:
- exc = IOError("unable to parse '%s': %s" % (proc_file_path, exc))
- _log_failure(parameter, exc)
- raise exc
+ raise IOError('unable to determine file descriptor destination (%s): %s' % (exc, fd_path))
- _log_runtime(parameter, '/proc/net/[tcp|udp]', start_time)
- return conn
+ return inodes
def _decode_proc_address_encoding(addr, is_ipv6):
diff --git a/test/unit/util/proc.py b/test/unit/util/proc.py
index ee52d21..a828981 100644
--- a/test/unit/util/proc.py
+++ b/test/unit/util/proc.py
@@ -6,6 +6,7 @@ import io
import unittest
from stem.util import proc
+from stem.util.connection import Connection
from test import mocking
try:
@@ -225,12 +226,9 @@ class TestProc(unittest.TestCase):
'/proc/net/udp': io.BytesIO(udp)
}[param]
- # tests the edge case of pid = 0
- self.assertEqual([], proc.connections(0))
-
expected_results = [
- ('17.17.17.17', 4369, '34.34.34.34', 8738, 'tcp', False),
- ('187.187.187.187', 48059, '204.204.204.204', 52428, 'udp', False),
+ Connection('17.17.17.17', 4369, '34.34.34.34', 8738, 'tcp', False),
+ Connection('187.187.187.187', 48059, '204.204.204.204', 52428, 'udp', False),
]
self.assertEqual(expected_results, proc.connections(pid))
@@ -256,19 +254,48 @@ class TestProc(unittest.TestCase):
}[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, mode: {
+ '/proc/net/tcp': io.BytesIO(''),
'/proc/net/tcp6': io.BytesIO(TCP6_CONTENT),
+ '/proc/net/udp': io.BytesIO(''),
}[param]
expected_results = [
- ('2a01:4f8:190:514a::2', 443, '2001:638:a000:4140::ffff:189', 40435, 'tcp', True),
- ('2a01:4f8:190:514a::2', 443, '2001:858:2:2:aabb:0:563b:1526', 44469, 'tcp', True),
+ Connection('2a01:4f8:190:514a::2', 443, '2001:638:a000:4140::ffff:189', 40435, 'tcp', True),
+ Connection('2a01:4f8:190:514a::2', 443, '2001:858:2:2:aabb:0:563b:1526', 44469, 'tcp', True),
]
- self.assertEqual(expected_results, proc.connections(pid))
+ self.assertEqual(expected_results, proc.connections(pid = pid))
+
+ @patch('os.path.exists')
+ @patch('pwd.getpwnam')
+ @patch('stem.util.proc.open', create = True)
+ def test_connections_ipv6_by_user(self, open_mock, getpwnam_mock, path_exists_mock):
+ """
+ Tests the connections function with ipv6 addresses.
+ """
+
+ getpwnam_mock('me').pw_uid = 106
+
+ path_exists_mock.side_effect = lambda param: {
+ '/proc/net/tcp6': True,
+ '/proc/net/udp6': False
+ }[param]
+
+ open_mock.side_effect = lambda param, mode: {
+ '/proc/net/tcp': io.BytesIO(''),
+ '/proc/net/tcp6': io.BytesIO(TCP6_CONTENT),
+ '/proc/net/udp': io.BytesIO(''),
+ }[param]
+
+ expected_results = [
+ Connection('::ffff:5.9.158.75', 5222, '::ffff:78.54.134.33', 38330, 'tcp', True),
+ Connection('2a01:4f8:190:514a::2', 5269, '2001:6f8:126f:11::26', 50594, 'tcp', True),
+ Connection('::ffff:5.9.158.75', 5222, '::ffff:78.54.134.33', 38174, 'tcp', True),
+ ]
+
+ self.assertEqual(expected_results, proc.connections(user = 'me'))
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits