Skip to content

Commit

Permalink
Address Code Review
Browse files Browse the repository at this point in the history
Added a test checking the correct handling of missing /proc/PID/ns/net
file
  • Loading branch information
davereikher committed Feb 23, 2020
1 parent e4fcf2b commit ec3760b
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 54 deletions.
107 changes: 54 additions & 53 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import collections
import errno
import functools
import itertools
import glob
import os
import re
Expand Down Expand Up @@ -72,7 +73,6 @@
# --- globals
# =====================================================================

# TODO remove this comment
POWER_SUPPLY_PATH = "/sys/class/power_supply"
HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
HAS_PRLIMIT = hasattr(cext, "linux_prlimit")
Expand Down Expand Up @@ -867,12 +867,11 @@ def decode_address(addr, family):
raise
return _common.addr(ip, port)

@staticmethod
def network_namespaces():
def network_namespaces(self):
net_namespaces = defaultdict(list)
for pid in pids():
try:
ns = readlink("%s/%s/ns/net" % (get_procfs_path(), pid))
ns = readlink("%s/%s/ns/net" % (self._procfs_path, pid))
except (FileNotFoundError, ProcessLookupError, PermissionError):
continue
except OSError as err:
Expand All @@ -886,56 +885,46 @@ def network_namespaces():
net_namespaces[ns].append(pid)
return net_namespaces

def process_inet(self, file_name, family, type_, inodes, filter_pid=None):
@staticmethod
def process_inet(file, family, type_, inodes, filter_pid=None):
"""Parse /proc/*/net/tcp* and /proc/*/net/udp* files."""
net_ns = Connections.network_namespaces()
files = []
if filter_pid is None:
for k in net_ns.keys():
files.append("%s/%s/net/%s" % (self._procfs_path, net_ns[k][0],
file_name))
else:
files.append("%s/%s/net/%s" % (self._procfs_path, filter_pid,
file_name))

for file in files:
if file.endswith('6') and not os.path.exists(file):
# IPv6 not supported
continue
with open_text(file, buffering=BIGFILE_BUFFERING) as f:
f.readline() # skip the first line
for lineno, line in enumerate(f, 1):
try:
_, laddr, raddr, status, _, _, _, _, _, inode = \
line.split()[:10]
except ValueError:
raise RuntimeError(
"error while parsing %s; malformed line %s %r" % (
file, lineno, line))
if inode in inodes:
# # We assume inet sockets are unique, so we error
# # out if there are multiple references to the
# # same inode. We won't do this for UNIX sockets.
# if len(inodes[inode]) > 1 and
# family != socket.AF_UNIX:
# raise ValueError("ambiguos inode with multiple "
# "PIDs references")
pid, fd = inodes[inode][0]
if file.endswith('6') and not os.path.exists(file):
# IPv6 not supported
return
with open_text(file, buffering=BIGFILE_BUFFERING) as f:
f.readline() # skip the first line
for lineno, line in enumerate(f, 1):
try:
_, laddr, raddr, status, _, _, _, _, _, inode = \
line.split()[:10]
except ValueError:
raise RuntimeError(
"error while parsing %s; malformed line %s %r" % (
file, lineno, line))
if inode in inodes:
# # We assume inet sockets are unique, so we error
# # out if there are multiple references to the
# # same inode. We won't do this for UNIX sockets.
# if len(inodes[inode]) > 1 and
# family != socket.AF_UNIX:
# raise ValueError("ambiguos inode with multiple "
# "PIDs references")
pid, fd = inodes[inode][0]
else:
pid, fd = None, -1
if filter_pid is not None and filter_pid != pid:
continue
else:
if type_ == socket.SOCK_STREAM:
status = TCP_STATUSES[status]
else:
pid, fd = None, -1
if filter_pid is not None and filter_pid != pid:
status = _common.CONN_NONE
try:
laddr = Connections.decode_address(laddr, family)
raddr = Connections.decode_address(raddr, family)
except _Ipv6UnsupportedError:
continue
else:
if type_ == socket.SOCK_STREAM:
status = TCP_STATUSES[status]
else:
status = _common.CONN_NONE
try:
laddr = Connections.decode_address(laddr, family)
raddr = Connections.decode_address(raddr, family)
except _Ipv6UnsupportedError:
continue
yield (fd, family, type_, laddr, raddr, status, pid)
yield (fd, family, type_, laddr, raddr, status, pid)

@staticmethod
def process_unix(file, family, inodes, filter_pid=None):
Expand Down Expand Up @@ -991,8 +980,20 @@ def retrieve(self, kind, pid=None):
for proto_name, family, type_ in self.tmap[kind]:
path = "%s/net/%s" % (self._procfs_path, proto_name)
if family in (socket.AF_INET, socket.AF_INET6):
ls = self.process_inet(
path, family, type_, inodes, filter_pid=pid)
files = []
if pid is None:
net_ns = self.network_namespaces()
if net_ns:
for k in net_ns.keys():
files.append("%s/%s/net/%s" % (self._procfs_path,
net_ns[k][0], proto_name))
else:
files = [path]
else:
files.append("%s/%s/net/%s" % (self._procfs_path, pid,
proto_name))
ls = itertools.chain(*[self.process_inet(
f, family, type_, inodes, filter_pid=pid) for f in files])
else:
ls = self.process_unix(
path, family, inodes, filter_pid=pid)
Expand Down
39 changes: 38 additions & 1 deletion psutil/tests/test_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,8 @@ def mock_readlink(path, paths_links_map, orig_method):
return ""

orig_readlink = psutil._pslinux.readlink
connections = psutil._pslinux.Connections()
connections._procfs_path = psutil._common.get_procfs_path()

p1 = get_test_subprocess()
p2 = get_test_subprocess()
Expand All @@ -1115,11 +1117,46 @@ def mock_readlink(path, paths_links_map, orig_method):
new=lambda path: mock_readlink(
path, mock_paths_links_map, orig_readlink)):

namespaces = psutil._pslinux.Connections.network_namespaces()
namespaces = connections.network_namespaces()

self.assertIn(mock_inode1, namespaces)
self.assertIn(mock_inode2, namespaces)

def test_detect_connections_no_ns_folder(self):
def mock_process_inet(orig_method, *args):
return orig_method(*args)

def mock_readlink(path, orig_method):
if path.endswith("/ns/net"):
raise FileNotFoundError(
errno.ENOENT, os.strerror(errno.ENOENT), path)
try:
return orig_method(path)
except PermissionError:
return ""

orig_process_inet = psutil._pslinux.Connections.process_inet
orig_readlink = psutil._pslinux.readlink
connections = psutil._pslinux.Connections()

with mock.patch(
"psutil._pslinux.readlink",
new=lambda path: mock_readlink(
path, orig_readlink)), \
mock.patch(
"psutil._pslinux.Connections.process_inet",
mock.Mock(
wraps=lambda *args, **kwargs: mock_process_inet(
orig_process_inet, *args))) as m, \
mock.patch(
"psutil._pslinux.Connections.get_proc_inodes",
return_value={1: [2, 3]}):
connections.retrieve('tcp4')
m.assert_called_once_with('/proc/net/tcp', socket.AF_INET,
socket.SOCK_STREAM,
{1: [2, 3]}, filter_pid=None)


# =====================================================================
# --- system disks
# =====================================================================
Expand Down

0 comments on commit ec3760b

Please sign in to comment.