Browse Source

Be stricter accepting add_peer requests

- rate-limit onion add_peer requests
- for clearnet peers only accept if the peer resolves to the
  source address
master
Neil Booth 8 years ago
parent
commit
7a2f29aabe
  1. 10
      server/controller.py
  2. 46
      server/peers.py
  3. 10
      server/session.py

10
server/controller.py

@ -49,7 +49,6 @@ class Controller(util.LoggedClass):
self.executor = ThreadPoolExecutor()
self.loop.set_default_executor(self.executor)
self.start_time = time.time()
self.next_add_peer_time = self.start_time
self.coin = env.coin
self.daemon = Daemon(env.coin.daemon_urls(env.daemon_url))
self.bp = BlockProcessor(env, self, self.daemon)
@ -136,15 +135,6 @@ class Controller(util.LoggedClass):
def is_deprioritized(self, session):
return self.session_priority(session) > self.BANDS
def permit_add_peer(self):
'''To prevent lots of add_peer requests filling up the peer
table, accept only one per random time interval.'''
now = time.time()
if now < self.next_add_peer_time:
return False
self.next_add_peer_time = now + random.randrange(0, 1800)
return True
async def run_in_executor(self, func, *args):
'''Wait whilst running func in the executor.'''
return await self.loop.run_in_executor(None, func, *args)

46
server/peers.py

@ -14,6 +14,7 @@ import ssl
import time
from collections import defaultdict, Counter
from functools import partial
from socket import SOCK_STREAM
from lib.jsonrpc import JSONSession
from lib.peer import Peer
@ -221,6 +222,7 @@ class PeerManager(util.LoggedClass):
# any other peers with the same host name or IP address.
self.peers = set()
self.onion_peers = []
self.permit_onion_peer_time = time.time()
self.last_tor_retry_time = 0
self.tor_proxy = SocksProxy(env.tor_proxy_host, env.tor_proxy_port,
loop=self.loop)
@ -302,15 +304,43 @@ class PeerManager(util.LoggedClass):
if retry:
self.retry_event.set()
def on_add_peer(self, features, source):
'''Add peers from an incoming connection.'''
def permit_new_onion_peer(self):
'''Accept a new onion peer only once per random time interval.'''
now = time.time()
if now < self.permit_onion_peer_time:
return False
self.permit_onion_peer_time = now + random.randrange(0, 1200)
return True
async def on_add_peer(self, features, source_info):
'''Add a peer (but only if the peer resolves to the source).'''
if not source_info:
return False
source = source_info[0]
peers = Peer.peers_from_features(features, source)
if peers:
hosts = [peer.host for peer in peers]
self.log_info('add_peer request from {} for {}'
.format(source, ', '.join(hosts)))
self.add_peers(peers, check_ports=True)
return bool(peers)
if not peers:
return False
# Just look at the first peer, require it
peer = peers[0]
host = peer.host
if peer.is_tor:
permit = self.permit_new_onion_peer()
reason = 'rate limiting'
else:
infos = await self.loop.getaddrinfo(host, 80, type=SOCK_STREAM)
permit = any(source == info[-1][0] for info in infos)
reason = 'source-destination mismatch'
if permit:
self.log_info('accepted add_peer request from {} for {}'
.format(source, host))
self.add_peers([peer], check_ports=True)
else:
self.log_warning('rejected add_peer request from {} for {} ({})'
.format(source, host, reason))
return permit
def on_peers_subscribe(self, is_tor):
'''Returns the server peers as a list of (ip, host, details) tuples.

10
server/session.py

@ -193,14 +193,10 @@ class ElectrumX(SessionBase):
self.subscribe_height = True
return self.height()
def add_peer(self, features):
'''Add a peer.'''
if not self.controller.permit_add_peer():
return False
async def add_peer(self, features):
'''Add a peer (but only if the peer resolves to the source).'''
peer_mgr = self.controller.peer_mgr
peer_info = self.peer_info()
source = peer_info[0] if peer_info else 'unknown'
return peer_mgr.on_add_peer(features, source)
return await peer_mgr.on_add_peer(features, self.peer_info())
def peers_subscribe(self):
'''Return the server peers as a list of (ip, host, details) tuples.'''

Loading…
Cancel
Save