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:
Peter Šurda 2019-11-16 11:52:36 +01:00
parent 7e1f1d2604
commit 2a165380bb
Signed by: PeterSurda
GPG Key ID: 0C5F50C0B5F37D87
5 changed files with 84 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View 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))