def test_match_peer_id(self): network = 'testnet' peer_id1 = PeerId() peer_id2 = PeerId() manager1 = self.create_peer(network, peer_id=peer_id1) manager2 = self.create_peer(network, peer_id=peer_id2) conn = FakeConnection(manager1, manager2) self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.HELLO)) matcher = NetfilterMatchPeerId(str(peer_id1.id)) context = NetfilterContext(protocol=conn.proto2) self.assertFalse(matcher.match(context)) conn.run_one_step() self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.PEER_ID)) self.assertFalse(matcher.match(context)) # Success because the connection is ready and proto2 is connected to proto1. conn.run_one_step() conn.run_one_step() self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.READY)) self.assertTrue(matcher.match(context)) # Fail because proto1 is connected to proto2, and the peer id cannot match. context = NetfilterContext(protocol=conn.proto1) self.assertFalse(matcher.match(context))
def test_match_ip_address_ipv4_ip(self): matcher = NetfilterMatchIPAddress('192.168.0.1/32') context = NetfilterContext(addr=IPv4Address('TCP', '192.168.0.1', 1234)) self.assertTrue(matcher.match(context)) context = NetfilterContext(addr=IPv4Address('TCP', '192.168.0.10', 1234)) self.assertFalse(matcher.match(context)) context = NetfilterContext(addr=IPv4Address('TCP', '', 1234)) self.assertFalse(matcher.match(context))
def test_match_ip_address_ipv6_ip(self): matcher = NetfilterMatchIPAddress('2001:0db8:0:f101::1/128') context = NetfilterContext(addr=IPv6Address('TCP', '2001:db8:0:f101::1', 1234)) self.assertTrue(matcher.match(context)) context = NetfilterContext(addr=IPv6Address('TCP', '2001:db8::8a2e:370:7334', 1234)) self.assertFalse(matcher.match(context)) context = NetfilterContext(addr=IPv6Address('TCP', '2001:db8:0:f101:2::7334', 1234)) self.assertFalse(matcher.match(context))
def buildProtocol(self, addr: IAddress) -> Protocol: context = NetfilterContext( connections=self.connections, addr=addr, ) verdict = get_table('filter').get_chain('pre_conn').process(context) if not bool(verdict): return None return super().buildProtocol(addr)
def handle_hello(self, payload: str) -> None: """ Executed when a HELLO message is received. It basically checks the application compatibility. """ protocol = self.protocol try: data = json.loads(payload) except ValueError: protocol.send_error_and_close_connection('Invalid payload.') return required_fields = { 'app', 'network', 'remote_address', 'genesis_short_hash', 'timestamp', 'capabilities' } # settings_dict is optional if not set(data).issuperset(required_fields): # If data does not contain all required fields protocol.send_error_and_close_connection('Invalid payload.') return if settings.ENABLE_PEER_WHITELIST and settings.CAPABILITY_WHITELIST not in data[ 'capabilities']: # If peer is not sending whitelist capability we must close the connection protocol.send_error_and_close_connection( 'Must have whitelist capability.') return my_sync_versions = self._get_sync_versions() try: remote_sync_versions = _parse_sync_versions(data) except HathorError as e: # this will only happen if the remote implementation is wrong self.log.warn('invalid protocol', error=e) protocol.send_error_and_close_connection('invalid protocol') return common_sync_versions = my_sync_versions & remote_sync_versions if not common_sync_versions: # no compatible sync version to use, this is fine though we just can't connect to this peer self.log.info('no compatible sync version to use') protocol.send_error_and_close_connection( 'no compatible sync version to use') return # choose the best version, sorting is implemented in hathor.p2p.sync_versions.__lt__ protocol.sync_version = max(common_sync_versions) if data['app'] != self._app(): self.log.warn('different versions', theirs=data['app'], ours=self._app()) if data['network'] != protocol.network: protocol.send_error_and_close_connection('Wrong network.') return if data['genesis_short_hash'] != get_genesis_short_hash(): protocol.send_error_and_close_connection('Different genesis.') return dt = data['timestamp'] - protocol.node.reactor.seconds() if abs(dt) > settings.MAX_FUTURE_TIMESTAMP_ALLOWED / 2: protocol.send_error_and_close_connection( 'Nodes timestamps too far apart.') return if 'settings_dict' in data: # If settings_dict is sent we must validate it settings_dict = get_settings_hello_dict() if data['settings_dict'] != settings_dict: protocol.send_error_and_close_connection( 'Settings values are different. {}'.format( json.dumps(settings_dict))) return protocol.app_version = data['app'] protocol.diff_timestamp = dt from hathor.p2p.netfilter import get_table from hathor.p2p.netfilter.context import NetfilterContext context = NetfilterContext( protocol=self.protocol, connections=self.protocol.connections, addr=self.protocol.transport.getPeer(), ) verdict = get_table('filter').get_chain('post_hello').process(context) if not bool(verdict): self.protocol.disconnect( 'rejected by netfilter: filter post_hello', force=True) return protocol.change_state(protocol.PeerState.PEER_ID)
def test_match_ip_address_empty_context(self): matcher = NetfilterMatchIPAddress('192.168.0.0/24') context = NetfilterContext() self.assertFalse(matcher.match(context))
def handle_peer_id(self, payload: str) -> Generator[Any, Any, None]: """ Executed when a PEER-ID is received. It basically checks the identity of the peer. Only after this step, the peer connection is considered established and ready to communicate. """ protocol = self.protocol data = json.loads(payload) peer = PeerId.create_from_json(data) peer.validate() assert peer.id is not None # If the connection URL had a peer-id parameter we need to check it's the same if protocol.expected_peer_id and peer.id != protocol.expected_peer_id: protocol.send_error_and_close_connection( 'Peer id different from the requested one.') return # is it on the whitelist? if peer.id and self._should_block_peer(peer.id): if settings.WHITELIST_WARN_BLOCKED_PEERS: protocol.send_error_and_close_connection( f'Blocked (by {peer.id}). Get in touch with Hathor team.') else: protocol.send_error_and_close_connection( 'Connection rejected.') return if peer.id == protocol.my_peer.id: protocol.send_error_and_close_connection('Are you my clone?!') return if protocol.connections is not None: if protocol.connections.is_peer_connected(peer.id): protocol.send_error_and_close_connection( 'We are already connected.') return entrypoint_valid = yield peer.validate_entrypoint(protocol) if not entrypoint_valid: protocol.send_error_and_close_connection( 'Connection string is not in the entrypoints.') return if protocol.use_ssl: certificate_valid = peer.validate_certificate(protocol) if not certificate_valid: protocol.send_error_and_close_connection( 'Public keys from peer and certificate are not the same.') return # If it gets here, the peer is validated, and we are ready to start communicating. protocol.peer = peer from hathor.p2p.netfilter import get_table from hathor.p2p.netfilter.context import NetfilterContext context = NetfilterContext( protocol=self.protocol, connections=self.protocol.connections, addr=self.protocol.transport.getPeer(), ) verdict = get_table('filter').get_chain('post_peerid').process(context) if not bool(verdict): self.protocol.disconnect( 'rejected by netfilter: filter post_peerid', force=True) return self.send_ready()
def test_match_or_success_01(self): m1 = NetfilterNeverMatch() m2 = NetfilterMatchAll() matcher = NetfilterMatchOr(m1, m2) context = NetfilterContext() self.assertTrue(matcher.match(context))
def test_match_or_fail_00(self): m1 = NetfilterNeverMatch() m2 = NetfilterNeverMatch() matcher = NetfilterMatchOr(m1, m2) context = NetfilterContext() self.assertFalse(matcher.match(context))
def test_match_and_success(self): m1 = NetfilterMatchAll() m2 = NetfilterMatchAll() matcher = NetfilterMatchAnd(m1, m2) context = NetfilterContext() self.assertTrue(matcher.match(context))
def test_match_and_fail_10(self): m1 = NetfilterMatchAll() m2 = NetfilterNeverMatch() matcher = NetfilterMatchAnd(m1, m2) context = NetfilterContext() self.assertFalse(matcher.match(context))
def test_match_all(self): matcher = NetfilterMatchAll() context = NetfilterContext() self.assertTrue(matcher.match(context))
def test_never_match(self): matcher = NetfilterNeverMatch() context = NetfilterContext() self.assertFalse(matcher.match(context))
def test_match_peer_id_empty_context(self): matcher = NetfilterMatchPeerId('123') context = NetfilterContext() self.assertFalse(matcher.match(context))
def test_match_ip_address_ipv4_hostname(self): matcher = NetfilterMatchIPAddress('192.168.0.1/32') context = NetfilterContext(addr=HostnameAddress('hathor.network', 80)) self.assertFalse(matcher.match(context))
def test_match_ip_address_ipv6_ipv4(self): matcher = NetfilterMatchIPAddress('2001:0db8:0:f101::1/128') context = NetfilterContext(addr=IPv4Address('TCP', '192.168.0.1', 1234)) self.assertFalse(matcher.match(context))
def test_match_ip_address_ipv6_unix(self): matcher = NetfilterMatchIPAddress('2001:0db8:0:f101::1/128') context = NetfilterContext(addr=UNIXAddress('/unix.sock')) self.assertFalse(matcher.match(context))
def test_match_ip_address_ipv6_hostname(self): matcher = NetfilterMatchIPAddress('2001:0db8:0:f101::1/128') context = NetfilterContext(addr=HostnameAddress('hathor.network', 80)) self.assertFalse(matcher.match(context))
def test_match_ip_address_ipv4_unix(self): matcher = NetfilterMatchIPAddress('192.168.0.1/32') context = NetfilterContext(addr=UNIXAddress('/unix.sock')) self.assertFalse(matcher.match(context))
def test_match_ip_address_ipv4_ipv6(self): matcher = NetfilterMatchIPAddress('192.168.0.1/32') context = NetfilterContext(addr=IPv6Address('TCP', '2001:db8::', 80)) self.assertFalse(matcher.match(context)) context = NetfilterContext(addr=IPv6Address('TCP', '', 80)) self.assertFalse(matcher.match(context))