Merge pull request #1709 from croneter/py3-fix-dos

Fix Kodi getting blocked and losing PMS access e.g. due to cloudflare
This commit is contained in:
croneter 2021-11-16 19:45:14 +01:00 committed by GitHub
commit e123c874c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -18,7 +18,11 @@ requests.packages.urllib3.disable_warnings()
# Timeout (connection timeout, read timeout) # Timeout (connection timeout, read timeout)
# The later is up to 20 seconds, if the PMS has nothing to tell us # The later is up to 20 seconds, if the PMS has nothing to tell us
# THIS WILL PREVENT PKC FROM SHUTTING DOWN CORRECTLY # THIS WILL PREVENT PKC FROM SHUTTING DOWN CORRECTLY
TIMEOUT = (5.0, 3.0) TIMEOUT = (5.0, 4.0)
# Max. timeout for the Listener: 2 ^ MAX_TIMEOUT
# Corresponds to 2 ^ 7 = 128 seconds
MAX_TIMEOUT = 7
log = getLogger('PLEX.companion.listener') log = getLogger('PLEX.companion.listener')
@ -34,6 +38,7 @@ class Listener(backgroundthread.KillableThread):
def __init__(self, playstate_mgr): def __init__(self, playstate_mgr):
self.s = None self.s = None
self.playstate_mgr = playstate_mgr self.playstate_mgr = playstate_mgr
self._sleep_timer = 0
super().__init__() super().__init__()
def _get_requests_session(self): def _get_requests_session(self):
@ -56,6 +61,19 @@ class Listener(backgroundthread.KillableThread):
pass pass
self.s = None self.s = None
def _unauthorized(self):
"""Puts this thread to sleep until e.g. a PMS changes wakes it up"""
log.warn('We are not authorized to poll the PMS (http error 401). '
'Plex Companion will not work.')
self.suspend()
def _on_connection_error(self, req=None):
if req:
log_error(log.error, 'Error while contacting the PMS', req)
self.sleep(2 ^ self._sleep_timer)
if self._sleep_timer < MAX_TIMEOUT:
self._sleep_timer += 1
def ok_message(self, command_id): def ok_message(self, command_id):
url = f'{app.CONN.server}/player/proxy/response?commandID={command_id}' url = f'{app.CONN.server}/player/proxy/response?commandID={command_id}'
try: try:
@ -129,28 +147,33 @@ class Listener(backgroundthread.KillableThread):
# No command received from the PMS - try again immediately # No command received from the PMS - try again immediately
continue continue
except requests.RequestException: except requests.RequestException:
self.sleep(0.5) self._on_connection_error()
continue continue
except SystemExit: except SystemExit:
# We need to quit PKC entirely # We need to quit PKC entirely
break break
# Sanity checks # Sanity checks
if not req.ok: if req.status_code == 401:
log_error(log.error, 'Error while contacting the PMS', req) # We can't reach a PMS that is not in the local LAN
self.sleep(0.5) # This might even lead to e.g. cloudflare blocking us, thinking
# we're staging a DOS attach
self._unauthorized()
continue continue
elif not req.ok:
self._on_connection_error(req)
continue
elif not ('content-type' in req.headers
and 'xml' in req.headers['content-type']):
self._on_connection_error(req)
continue
if not req.text: if not req.text:
# Means the connection timed-out (usually after 20 seconds), # Means the connection timed-out (usually after 20 seconds),
# because there was no command from the PMS or a client to # because there was no command from the PMS or a client to
# remote-control anything no the PKC-side # remote-control anything no the PKC-side
# Received an empty body, but still header Content-Type: xml # Received an empty body, but still header Content-Type: xml
continue continue
if not ('content-type' in req.headers
and 'xml' in req.headers['content-type']):
log_error(log.error, 'Unexpected answer from the PMS', req)
self.sleep(0.5)
continue
# Parsing # Parsing
try: try:
@ -160,8 +183,8 @@ class Listener(backgroundthread.KillableThread):
# We should always just get ONE command per message # We should always just get ONE command per message
raise IndexError() raise IndexError()
except (utils.ParseError, IndexError): except (utils.ParseError, IndexError):
log_error(log.error, 'Could not parse the PMS xml:', req) log.error('Could not parse the PMS xml:')
self.sleep(0.5) self._on_connection_error()
continue continue
# Do the work # Do the work
@ -170,3 +193,4 @@ class Listener(backgroundthread.KillableThread):
self.playstate_mgr.check_subscriber(cmd) self.playstate_mgr.check_subscriber(cmd)
if process_proxy_xml(cmd): if process_proxy_xml(cmd):
self.ok_message(cmd.get('commandID')) self.ok_message(cmd.get('commandID'))
self._sleep_timer = 0