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.
This commit is contained in:
John L. Jegutanis 2018-04-05 00:49:56 +03:00 committed by Neil
parent 02ab8e5742
commit 90f28314d2
6 changed files with 37 additions and 0 deletions

View File

@ -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
===================== =====================

View File

@ -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)]

View File

@ -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):

View File

@ -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()

View File

@ -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

View File

@ -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