Restrict outbound connections on network groups
Logic borrowed from bitcoin, see CNetAddr::GetGroup() in src/netaddress.cpp Simplified, so may not work fully identically but for our purposes it's good enough. Won't connect to more than one host from a /16 subnet on IPv4 and a /32 subnet on IPv6.
This commit is contained in:
parent
7e1f1d2604
commit
2a165380bb
|
@ -71,6 +71,8 @@ class BMProto(AdvancedDispatcher, ObjectTracker):
|
||||||
# packet/connection from a local IP
|
# packet/connection from a local IP
|
||||||
self.local = False
|
self.local = False
|
||||||
self.pendingUpload = RandomTrackingDict()
|
self.pendingUpload = RandomTrackingDict()
|
||||||
|
# canonical identifier of network group
|
||||||
|
self.network_group = None
|
||||||
|
|
||||||
def bm_proto_reset(self):
|
def bm_proto_reset(self):
|
||||||
"""Reset the bitmessage object parser"""
|
"""Reset the bitmessage object parser"""
|
||||||
|
|
|
@ -229,6 +229,7 @@ class BMConnectionPool(object):
|
||||||
|
|
||||||
def loop(self): # pylint: disable=too-many-branches,too-many-statements
|
def loop(self): # pylint: disable=too-many-branches,too-many-statements
|
||||||
"""Main Connectionpool's loop"""
|
"""Main Connectionpool's loop"""
|
||||||
|
# pylint: disable=too-many-locals
|
||||||
# defaults to empty loop if outbound connections are maxed
|
# defaults to empty loop if outbound connections are maxed
|
||||||
spawnConnections = False
|
spawnConnections = False
|
||||||
acceptConnections = True
|
acceptConnections = True
|
||||||
|
@ -297,6 +298,19 @@ class BMConnectionPool(object):
|
||||||
# don't connect to self
|
# don't connect to self
|
||||||
if chosen in state.ownAddresses:
|
if chosen in state.ownAddresses:
|
||||||
continue
|
continue
|
||||||
|
# don't connect to the hosts from the same
|
||||||
|
# network group, defense against sibyl attacks
|
||||||
|
host_network_group = protocol.network_group(
|
||||||
|
chosen.host)
|
||||||
|
same_group = False
|
||||||
|
for j in self.outboundConnections.values():
|
||||||
|
if host_network_group == j.network_group:
|
||||||
|
same_group = True
|
||||||
|
if chosen.host == j.destination.host:
|
||||||
|
knownnodes.decreaseRating(chosen)
|
||||||
|
break
|
||||||
|
if same_group:
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if chosen.host.endswith(".onion") and Proxy.onion_proxy:
|
if chosen.host.endswith(".onion") and Proxy.onion_proxy:
|
||||||
|
|
|
@ -84,6 +84,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
)
|
)
|
||||||
except socket.error:
|
except socket.error:
|
||||||
pass # it's probably a hostname
|
pass # it's probably a hostname
|
||||||
|
self.network_group = protocol.network_group(self.destination.host)
|
||||||
ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called
|
ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called
|
||||||
self.bm_proto_reset()
|
self.bm_proto_reset()
|
||||||
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
||||||
|
|
|
@ -105,6 +105,34 @@ def networkType(host):
|
||||||
return 'IPv6'
|
return 'IPv6'
|
||||||
|
|
||||||
|
|
||||||
|
def network_group(host):
|
||||||
|
"""Canonical identifier of network group
|
||||||
|
simplified, borrowed from
|
||||||
|
GetGroup() in src/netaddresses.cpp in bitcoin core"""
|
||||||
|
if not isinstance(host, str):
|
||||||
|
return None
|
||||||
|
network_type = networkType(host)
|
||||||
|
try:
|
||||||
|
raw_host = encodeHost(host)
|
||||||
|
except socket.error:
|
||||||
|
return host
|
||||||
|
if network_type == 'IPv4':
|
||||||
|
decoded_host = checkIPv4Address(raw_host[12:], True)
|
||||||
|
if decoded_host:
|
||||||
|
# /16 subnet
|
||||||
|
return raw_host[12:14]
|
||||||
|
elif network_type == 'IPv6':
|
||||||
|
decoded_host = checkIPv6Address(raw_host, True)
|
||||||
|
if decoded_host:
|
||||||
|
# /32 subnet
|
||||||
|
return raw_host[0:12]
|
||||||
|
else:
|
||||||
|
# just host, e.g. for tor
|
||||||
|
return host
|
||||||
|
# global network type group for local, private, unroutable
|
||||||
|
return network_type
|
||||||
|
|
||||||
|
|
||||||
def checkIPAddress(host, private=False):
|
def checkIPAddress(host, private=False):
|
||||||
"""Returns hostStandardFormat if it is a valid IP address, otherwise returns False"""
|
"""Returns hostStandardFormat if it is a valid IP address, otherwise returns False"""
|
||||||
if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
|
if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
|
||||||
|
|
39
src/tests/test_networkgroup.py
Normal file
39
src/tests/test_networkgroup.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
"""
|
||||||
|
Test for network group
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkGroup(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Test case for network group
|
||||||
|
"""
|
||||||
|
def test_network_group(self):
|
||||||
|
"""Test various types of network groups"""
|
||||||
|
from pybitmessage.protocol import network_group
|
||||||
|
|
||||||
|
test_ip = '1.2.3.4'
|
||||||
|
self.assertEqual('\x01\x02', network_group(test_ip))
|
||||||
|
|
||||||
|
test_ip = '127.0.0.1'
|
||||||
|
self.assertEqual('IPv4', network_group(test_ip))
|
||||||
|
|
||||||
|
test_ip = '0102:0304:0506:0708:090A:0B0C:0D0E:0F10'
|
||||||
|
self.assertEqual(
|
||||||
|
'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C',
|
||||||
|
network_group(test_ip))
|
||||||
|
|
||||||
|
test_ip = 'bootstrap8444.bitmessage.org'
|
||||||
|
self.assertEqual(
|
||||||
|
'bootstrap8444.bitmessage.org',
|
||||||
|
network_group(test_ip))
|
||||||
|
|
||||||
|
test_ip = 'quzwelsuziwqgpt2.onion'
|
||||||
|
self.assertEqual(
|
||||||
|
test_ip,
|
||||||
|
network_group(test_ip))
|
||||||
|
|
||||||
|
test_ip = None
|
||||||
|
self.assertEqual(
|
||||||
|
None,
|
||||||
|
network_group(test_ip))
|
Reference in New Issue
Block a user