Browse Source

Add DROP_CLIENT env variable (#432)

This will disconnect any client based on their version string,
using a regular expression.
Useful for dropping buggy/unsupported clients.
patch-2
John L. Jegutanis 7 years ago
committed by Neil
parent
commit
90f28314d2
  1. 6
      docs/environment.rst
  2. 11
      lib/env_base.py
  3. 3
      server/controller.py
  4. 2
      server/env.py
  5. 4
      server/session.py
  6. 11
      tests/server/test_env.py

6
docs/environment.rst

@ -201,6 +201,12 @@ These environment variables are optional:
If you are not sure what this means leave it unset. If you are not sure what this means leave it unset.
.. envvar:: DROP_CLIENT
Set a regular expression to disconnect any client based on their
version string. For example to drop versions from 1.0 to 1.2 use
the regex ``1\.[0-2]\.\d+``.
Resource Usage Limits Resource Usage Limits
===================== =====================

11
lib/env_base.py

@ -53,6 +53,17 @@ class EnvBase(lib_util.LoggedClass):
raise cls.Error('cannot convert envvar {} value {} to an integer' raise cls.Error('cannot convert envvar {} value {} to an integer'
.format(envvar, value)) .format(envvar, value))
@classmethod
def custom(cls, envvar, default, parse):
value = environ.get(envvar)
if value is None:
return default
try:
return parse(value)
except Exception as e:
raise cls.Error('cannot parse envvar {} value {}'
.format(envvar, value)) from e
@classmethod @classmethod
def obsolete(cls, envvars): def obsolete(cls, envvars):
bad = [envvar for envvar in envvars if environ.get(envvar)] bad = [envvar for envvar in envvars if environ.get(envvar)]

3
server/controller.py

@ -254,6 +254,9 @@ class Controller(ServerBase):
self.logger.info('max subscriptions per session: {:,d}' self.logger.info('max subscriptions per session: {:,d}'
.format(self.env.max_session_subs)) .format(self.env.max_session_subs))
self.logger.info('bands: {}'.format(self.bands)) self.logger.info('bands: {}'.format(self.bands))
if self.env.drop_client is not None:
self.logger.info('drop clients matching: {}'
.format(self.env.drop_client.pattern))
await self.start_external_servers() await self.start_external_servers()
async def start_external_servers(self): async def start_external_servers(self):

2
server/env.py

@ -8,6 +8,7 @@
'''Class for handling environment configuration and defaults.''' '''Class for handling environment configuration and defaults.'''
import re
import resource import resource
from collections import namedtuple from collections import namedtuple
from ipaddress import ip_address from ipaddress import ip_address
@ -67,6 +68,7 @@ class Env(EnvBase):
self.max_session_subs = self.integer('MAX_SESSION_SUBS', 50000) self.max_session_subs = self.integer('MAX_SESSION_SUBS', 50000)
self.bandwidth_limit = self.integer('BANDWIDTH_LIMIT', 2000000) self.bandwidth_limit = self.integer('BANDWIDTH_LIMIT', 2000000)
self.session_timeout = self.integer('SESSION_TIMEOUT', 600) self.session_timeout = self.integer('SESSION_TIMEOUT', 600)
self.drop_client = self.custom("DROP_CLIENT", None, re.compile)
# Identities # Identities
clearnet_identity = self.clearnet_identity() clearnet_identity = self.clearnet_identity()

4
server/session.py

@ -358,6 +358,10 @@ class ElectrumX(SessionBase):
protocol_version: the protocol version spoken by the client protocol_version: the protocol version spoken by the client
''' '''
if client_name: if client_name:
if self.env.drop_client is not None and \
self.env.drop_client.match(client_name):
raise RPCError('unsupported client: {}'
.format(client_name), JSONRPC.FATAL_ERROR)
self.client = str(client_name)[:17] self.client = str(client_name)[:17]
try: try:
self.client_version = tuple(int(part) for part self.client_version = tuple(int(part) for part

11
tests/server/test_env.py

@ -2,6 +2,7 @@
import os import os
import random import random
import re
import pytest import pytest
@ -340,3 +341,13 @@ def test_tor_identity():
assert ident.host == tor_host assert ident.host == tor_host
assert ident.tcp_port == 234 assert ident.tcp_port == 234
assert ident.ssl_port == 432 assert ident.ssl_port == 432
def test_ban_versions():
e = Env()
assert e.drop_client is None
ban_re = '1\.[0-2]\.\d+?[_\w]*'
os.environ['DROP_CLIENT'] = ban_re
e = Env()
assert e.drop_client == re.compile(ban_re)
assert e.drop_client.match("1.2.3_buggy_client")
assert e.drop_client.match("1.3.0_good_client") is None

Loading…
Cancel
Save