diff --git a/letsencrypt/client/standalone_authenticator.py b/letsencrypt/client/standalone_authenticator.py index 4bd6d3379..ef926f0ea 100644 --- a/letsencrypt/client/standalone_authenticator.py +++ b/letsencrypt/client/standalone_authenticator.py @@ -272,15 +272,18 @@ class StandaloneAuthenticator(object): :param int port: The TCP port in question. :returns: True or False.""" - listeners = [conn.pid for conn in psutil.net_connections() \ - if conn.status == 'LISTEN' and \ - conn.type == socket.SOCK_STREAM and \ - (conn.laddr == ('0.0.0.0', port) or \ - conn.laddr == ('::', port))] + listeners = [conn.pid for conn in psutil.net_connections() + if conn.status == 'LISTEN' and + conn.type == socket.SOCK_STREAM and + (conn.laddr[1] == port)] try: - if listeners and listeners[0]: + if listeners and listeners[0] is not None: # conn.pid may be None if the current process doesn't have - # permission to identify the listening process! + # permission to identify the listening process! Additionally, + # listeners may have more than one element if separate + # sockets have bound the same port on separate interfaces. + # We currently only have UI to notify the user about one + # of them at a time. pid = listeners[0] name = psutil.Process(pid).name() display = zope.component.getUtility(interfaces.IDisplay) @@ -290,9 +293,11 @@ class StandaloneAuthenticator(object): "that port. Please stop the {0} program temporarily " "and then try again.".format(name, pid, port)) return True - except psutil.NoSuchProcess: + except (psutil.NoSuchProcess, psutil.AccessDenied): # Perhaps the result of a race where the process could have - # exited or relinquished the port. + # exited or relinquished the port (NoSuchProcess), or the result + # of an OS policy where we're not allowed to look up the process + # name (AccessDenied). pass return False diff --git a/letsencrypt/client/tests/standalone_authenticator_test.py b/letsencrypt/client/tests/standalone_authenticator_test.py index abe3ff42d..542915c7a 100644 --- a/letsencrypt/client/tests/standalone_authenticator_test.py +++ b/letsencrypt/client/tests/standalone_authenticator_test.py @@ -1,6 +1,7 @@ """Tests for letsencrypt.client.standalone_authenticator.""" import os import pkg_resources +import psutil import signal import socket import unittest @@ -197,7 +198,6 @@ class AlreadyListeningTest(unittest.TestCase): # incompatibility in which, for some reason, no process name can be # found to match the identified listening PID. from psutil._common import sconn - from psutil import NoSuchProcess conns = [ sconn(fd=-1, family=2, type=1, laddr=('0.0.0.0', 30), raddr=(), status='LISTEN', pid=None), @@ -208,7 +208,7 @@ class AlreadyListeningTest(unittest.TestCase): sconn(fd=3, family=2, type=1, laddr=('0.0.0.0', 17), raddr=(), status='LISTEN', pid=4416)] mock_net.return_value = conns - mock_process.side_effect = NoSuchProcess("No such PID") + mock_process.side_effect = psutil.NoSuchProcess("No such PID") # We simulate being unable to find the process name of PID 4416, # which results in returning False. self.assertFalse(self.authenticator.already_listening(17)) diff --git a/setup.py b/setup.py index 1136202ff..d8243af8a 100755 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ install_requires = [ 'ConfArgParse', 'jsonschema', 'mock', - 'psutil', + 'psutil>2.1.0', # net_connections introduced in 2.1.0 'pycrypto', 'PyOpenSSL', 'python-augeas',