Ejemplo n.º 1
0
    def __init__(self, peermanager, connection, remote_pubkey=None):
        super(Peer, self).__init__()
        self.is_stopped = False
        self.hello_received = False
        self.peermanager = peermanager
        self.connection = connection
        self.config = peermanager.config
        self.protocols = OrderedDict()
        log.debug('peer init', peer=self)

        # create multiplexed encrypted session
        privkey = self.config['node']['privkey_hex'].decode('hex')
        hello_packet = P2PProtocol.get_hello_packet(self)
        self.mux = MultiplexedSession(privkey,
                                      hello_packet,
                                      remote_pubkey=remote_pubkey)
        self.remote_pubkey = remote_pubkey

        # register p2p protocol
        assert issubclass(self.peermanager.wire_protocol, P2PProtocol)
        self.connect_service(self.peermanager)

        # assure, we don't get messages while replies are not read
        self.safe_to_read = gevent.event.Event()
        self.safe_to_read.set()

        # Stop peer if hello not received in self.dumb_remote_timeout seconds
        gevent.spawn_later(self.dumb_remote_timeout, self.check_if_dumb_remote)
Ejemplo n.º 2
0
    def __init__(self,
                 peermanager,
                 connection,
                 remote_pubkey=None):  # FIXME node vs remote_pubkey
        super(Peer, self).__init__()
        self.is_stopped = False
        self.peermanager = peermanager
        self.connection = connection
        self.config = peermanager.config
        self.protocols = OrderedDict()
        log.debug('peer init', peer=self)

        # create multiplexed encrypted session
        privkey = self.config['node']['privkey_hex'].decode('hex')
        hello_packet = P2PProtocol.get_hello_packet(self)
        self.mux = MultiplexedSession(privkey,
                                      hello_packet,
                                      token_by_pubkey=dict(),
                                      remote_pubkey=remote_pubkey)

        # register p2p protocol
        assert issubclass(self.peermanager.wire_protocol, P2PProtocol)
        self.connect_service(self.peermanager)

        # assure, we don't get messages while replies are not read
        self.safe_to_read = gevent.event.Event()
        self.safe_to_read.set()
Ejemplo n.º 3
0
    def __init__(self, peermanager, connection, remote_pubkey=None):  # FIXME node vs remote_pubkey
        super(Peer, self).__init__()
        self.is_stopped = False
        self.peermanager = peermanager
        self.connection = connection
        self.config = peermanager.config
        self.protocols = OrderedDict()
        log.debug('peer init', peer=self)

        # create multiplexed encrypted session
        privkey = self.config['node']['privkey_hex'].decode('hex')
        hello_packet = P2PProtocol.get_hello_packet(self)
        self.mux = MultiplexedSession(privkey, hello_packet,
                                      token_by_pubkey=dict(), remote_pubkey=remote_pubkey)

        # register p2p protocol
        assert issubclass(self.peermanager.wire_protocol, P2PProtocol)
        self.connect_service(self.peermanager)

        # assure, we don't get messages while replies are not read
        self.safe_to_read = gevent.event.Event()
        self.safe_to_read.set()
Ejemplo n.º 4
0
class Peer(gevent.Greenlet):

    remote_client_version = ''
    offset_based_dispatch = False
    wait_read_timeout = 0.001
    dumb_remote_timeout = 10.0

    def __init__(self, peermanager, connection, remote_pubkey=None):
        super(Peer, self).__init__()
        self.is_stopped = False
        self.hello_received = False
        self.peermanager = peermanager
        self.connection = connection
        self.config = peermanager.config
        self.protocols = OrderedDict()
        log.debug('peer init', peer=self)

        # create multiplexed encrypted session
        privkey = self.config['node']['privkey_hex'].decode('hex')
        hello_packet = P2PProtocol.get_hello_packet(self)
        self.mux = MultiplexedSession(privkey,
                                      hello_packet,
                                      remote_pubkey=remote_pubkey)
        self.remote_pubkey = remote_pubkey

        # register p2p protocol
        assert issubclass(self.peermanager.wire_protocol, P2PProtocol)
        self.connect_service(self.peermanager)

        # assure, we don't get messages while replies are not read
        self.safe_to_read = gevent.event.Event()
        self.safe_to_read.set()

        # Stop peer if hello not received in self.dumb_remote_timeout seconds
        gevent.spawn_later(self.dumb_remote_timeout, self.check_if_dumb_remote)

    @property
    def remote_pubkey(self):
        "if peer is responder, then the remote_pubkey will not be available"
        "before the first packet is received"
        return self.mux.remote_pubkey

    @remote_pubkey.setter
    def remote_pubkey(self, value):
        self.remote_pubkey_available = True if value else False
        self.mux.remote_pubkey = value

    def __repr__(self):
        try:
            pn = self.connection.getpeername()
        except gevent.socket.error:
            pn = ('not ready', )
        try:
            cv = '/'.join(self.remote_client_version.split('/')[:2])
        except:
            cv = self.remote_client_version
        return '<Peer%r %s>' % (pn, cv)
        # return '<Peer%r>' % repr(pn)

    def report_error(self, reason):
        try:
            ip_port = self.ip_port
        except:
            ip_port = 'ip_port not available fixme'
        self.peermanager.errors.add(ip_port, reason,
                                    self.remote_client_version)

    @property
    def ip_port(self):
        try:
            return self.connection.getpeername()
        except Exception as e:
            log.debug('ip_port failed', e=e)
            raise e

    def connect_service(self, service):
        assert isinstance(service, WiredService)
        protocol_class = service.wire_protocol
        assert issubclass(protocol_class, BaseProtocol)
        # create protcol instance which connects peer with serivce
        protocol = protocol_class(self, service)
        # register protocol
        assert protocol_class not in self.protocols
        log.debug('registering protocol', protocol=protocol.name, peer=self)
        self.protocols[protocol_class] = protocol
        self.mux.add_protocol(protocol.protocol_id)
        protocol.start()

    def has_protocol(self, protocol):
        assert issubclass(protocol, BaseProtocol)
        return protocol in self.protocols

    def receive_hello(self, proto, version, client_version_string,
                      capabilities, listen_port, remote_pubkey):
        log.info('received hello',
                 version=version,
                 client_version=client_version_string,
                 capabilities=capabilities)
        assert isinstance(remote_pubkey, bytes)
        assert len(remote_pubkey) == 64
        if self.remote_pubkey_available:
            assert self.remote_pubkey == remote_pubkey
        self.hello_received = True

        # enable backwards compatibility for legacy peers
        if version < 5:
            self.offset_based_dispatch = True
            max_window_size = 2**32  # disable chunked transfers

        # call peermanager
        agree = self.peermanager.on_hello_received(proto, version,
                                                   client_version_string,
                                                   capabilities, listen_port,
                                                   remote_pubkey)
        if not agree:
            return

        self.remote_client_version = client_version_string
        self.remote_pubkey = remote_pubkey

        # register in common protocols
        log.debug('connecting services',
                  services=self.peermanager.wired_services)
        remote_services = dict(
            (name, version) for name, version in capabilities)
        for service in sorted(self.peermanager.wired_services,
                              key=operator.attrgetter('name')):
            proto = service.wire_protocol
            assert isinstance(service, WiredService)
            if proto.name in remote_services:
                if remote_services[proto.name] == proto.version:
                    if service != self.peermanager:  # p2p protcol already registered
                        self.connect_service(service)
                else:
                    log.debug('wrong version',
                              service=proto.name,
                              local_version=proto.version,
                              remote_version=remote_services[proto.name])
                    self.report_error('wrong version')

    @property
    def capabilities(self):
        return [(s.wire_protocol.name, s.wire_protocol.version)
                for s in self.peermanager.wired_services]

    # sending p2p messages

    def send_packet(self, packet):
        for i, protocol in enumerate(self.protocols.values()):
            if packet.protocol_id == protocol.protocol_id:
                break

        assert packet.protocol_id == protocol.protocol_id, 'no protocol found'
        log.debug('send packet',
                  cmd=protocol.cmd_by_id[packet.cmd_id],
                  protcol=protocol.name,
                  peer=self)
        # rewrite cmd_id (backwards compatibility)
        if self.offset_based_dispatch:
            for i, protocol in enumerate(self.protocols.values()):
                if packet.protocol_id > i:
                    packet.cmd_id += (0 if protocol.max_cmd_id == 0 else
                                      protocol.max_cmd_id + 1)
                if packet.protocol_id == protocol.protocol_id:
                    break
                packet.protocol_id = 0
        self.mux.add_packet(packet)

    # receiving p2p messages

    def protocol_cmd_id_from_packet(self, packet):
        # offset-based dispatch (backwards compatibility)
        if self.offset_based_dispatch:
            max_id = 0
            for protocol in self.protocols.values():
                if packet.cmd_id < max_id + protocol.max_cmd_id + 1:
                    return protocol, packet.cmd_id - (0 if max_id == 0 else
                                                      max_id + 1)
                max_id += protocol.max_cmd_id
            raise UnknownCommandError('no protocol for id %s' % packet.cmd_id)
        # new-style dispatch based on protocol_id
        for i, protocol in enumerate(self.protocols.values()):
            if packet.protocol_id == protocol.protocol_id:
                return protocol, packet.cmd_id
        raise UnknownCommandError('no protocol for protocol id %s' %
                                  packet.protocol_id)

    def _handle_packet(self, packet):
        assert isinstance(packet, multiplexer.Packet)
        try:
            protocol, cmd_id = self.protocol_cmd_id_from_packet(packet)
        except UnknownCommandError, e:
            log.error('received unknown cmd', error=e, packet=packet)
            return
        log.debug('recv packet',
                  cmd=protocol.cmd_by_id[cmd_id],
                  protocol=protocol.name,
                  orig_cmd_id=packet.cmd_id)
        packet.cmd_id = cmd_id  # rewrite
        protocol.receive_packet(packet)
Ejemplo n.º 5
0
class Peer(gevent.Greenlet):

    remote_client_version = ''
    wait_read_timeout = 0.001

    def __init__(self, peermanager, connection, remote_pubkey=None):  # FIXME node vs remote_pubkey
        super(Peer, self).__init__()
        self.is_stopped = False
        self.peermanager = peermanager
        self.connection = connection
        self.config = peermanager.config
        self.protocols = OrderedDict()
        log.debug('peer init', peer=self)

        # create multiplexed encrypted session
        privkey = self.config['node']['privkey_hex'].decode('hex')
        hello_packet = P2PProtocol.get_hello_packet(self)
        self.mux = MultiplexedSession(privkey, hello_packet,
                                      token_by_pubkey=dict(), remote_pubkey=remote_pubkey)

        # register p2p protocol
        assert issubclass(self.peermanager.wire_protocol, P2PProtocol)
        self.connect_service(self.peermanager)

        # assure, we don't get messages while replies are not read
        self.safe_to_read = gevent.event.Event()
        self.safe_to_read.set()

    @property
    def remote_pubkey(self):
        "if peer is responder, then the remote_pubkey will not be available"
        "before the first packet is received"
        return self.mux.remote_pubkey

    def __repr__(self):
        try:
            pn = self.connection.getpeername()
        except gevent.socket.error:
            pn = ('not ready',)
        try:
            cv = '/'.join(self.remote_client_version.split('/')[:2])
        except:
            cv = self.remote_client_version
        return '<Peer%r %s>' % (pn, cv)
        # return '<Peer%r>' % repr(pn)

    def report_error(self, reason):
        try:
            ip_port = self.ip_port
        except:
            ip_port = 'ip_port not available fixme'
        self.peermanager.errors.add(ip_port, reason, self.remote_client_version)

    @property
    def ip_port(self):
        try:
            return self.connection.getpeername()
        except Exception as e:
            log.debug('ip_port failed')
            raise e

    def connect_service(self, service):
        assert isinstance(service, WiredService)
        protocol_class = service.wire_protocol
        assert issubclass(protocol_class, BaseProtocol)
        # create protcol instance which connects peer with serivce
        protocol = protocol_class(self, service)
        # register protocol
        assert protocol_class not in self.protocols
        log.debug('registering protocol', protocol=protocol.name, peer=self)
        self.protocols[protocol_class] = protocol
        self.mux.add_protocol(protocol.protocol_id)
        protocol.start()

    def has_protocol(self, protocol):
        assert issubclass(protocol, BaseProtocol)
        return protocol in self.protocols

    def receive_hello(self, proto, version, client_version, capabilities, listen_port, nodeid):
        # register in common protocols
        log.info('received hello', version=version,
                 client_version=client_version, capabilities=capabilities)
        self.remote_client_version = client_version

        # call peermanager
        agree = self.peermanager.on_hello_received(
            proto, version, client_version, capabilities, listen_port, nodeid)
        if not agree:
            return

        log.info('connecting services', services=self.peermanager.wired_services)
        remote_services = dict((name, version) for name, version in capabilities)
        for service in sorted(self.peermanager.wired_services, key=operator.attrgetter('name')):
            proto = service.wire_protocol
            assert isinstance(service, WiredService)
            if proto.name in remote_services:
                if remote_services[proto.name] == proto.version:
                    if service != self.peermanager:  # p2p protcol already registered
                        self.connect_service(service)
                else:
                    log.info('wrong version', service=proto.name, local_version=proto.version,
                             remote_version=remote_services[proto.name])
                    self.report_error('wrong version')

    @property
    def capabilities(self):
        return [(s.wire_protocol.name, s.wire_protocol.version)
                for s in self.peermanager.wired_services]

    # sending p2p messages

    def send_packet(self, packet):
        # rewrite cmd id / future FIXME  to packet.protocol_id
        protocol = list(self.protocols.values())[packet.protocol_id]
        log.debug('send packet', cmd=protocol.cmd_by_id[packet.cmd_id], protcol=protocol.name,
                  peer=self)
        # rewrite cmd_id  # FIXME
        for i, protocol in enumerate(self.protocols.values()):
            if packet.protocol_id > i:
                packet.cmd_id += (0 if protocol.max_cmd_id == 0 else protocol.max_cmd_id + 1)
            if packet.protocol_id == protocol.protocol_id:
                break

        packet.protocol_id = 0
        # done rewrite
        self.mux.add_packet(packet)

    # receiving p2p messages

    def protocol_cmd_id_from_packet(self, packet):
        # packet.protocol_id not yet used. old adaptive cmd_ids instead
        # future FIXME  to packet.protocol_id

        # get protocol and protocol.cmd_id from packet.cmd_id
        max_id = 0
        assert packet.protocol_id == 0  # FIXME, should be used by other peers
        for protocol in self.protocols.values():
            if packet.cmd_id < max_id + protocol.max_cmd_id + 1:
                return protocol, packet.cmd_id - (0 if max_id == 0 else max_id + 1)
            max_id += protocol.max_cmd_id
        raise UnknownCommandError('no protocol for id %s' % packet.cmd_id)

    def _handle_packet(self, packet):
        assert isinstance(packet, multiplexer.Packet)
        try:
            protocol, cmd_id = self.protocol_cmd_id_from_packet(packet)
        except UnknownCommandError, e:
            log.error('received unknown cmd', error=e, packet=packet)
            return
        log.debug('recv packet', cmd=protocol.cmd_by_id[
                  cmd_id], protocol=protocol.name, orig_cmd_id=packet.cmd_id)
        packet.cmd_id = cmd_id  # rewrite
        protocol.receive_packet(packet)
Ejemplo n.º 6
0
class Peer(gevent.Greenlet):

    remote_client_version = ''
    wait_read_timeout = 0.001

    def __init__(self, peermanager, connection, remote_pubkey=None):  # FIXME node vs remote_pubkey
        super(Peer, self).__init__()
        self.is_stopped = False
        self.peermanager = peermanager
        self.connection = connection
        self.config = peermanager.config
        self.protocols = OrderedDict()
        log.debug('peer init', peer=self)

        # create multiplexed encrypted session
        privkey = self.config['node']['privkey_hex'].decode('hex')
        hello_packet = P2PProtocol.get_hello_packet(self)
        self.mux = MultiplexedSession(privkey, hello_packet,
                                      token_by_pubkey=dict(), remote_pubkey=remote_pubkey)

        # register p2p protocol
        assert issubclass(self.peermanager.wire_protocol, P2PProtocol)
        self.connect_service(self.peermanager)

        # assure, we don't get messages while replies are not read
        self.safe_to_read = gevent.event.Event()
        self.safe_to_read.set()

    @property
    def remote_pubkey(self):
        "if peer is responder, then the remote_pubkey will not be available"
        "before the first packet is received"
        return self.mux.remote_pubkey

    def __repr__(self):
        try:
            pn = self.connection.getpeername()
        except gevent.socket.error:
            pn = ('not ready',)
        try:
            cv = '/'.join(self.remote_client_version.split('/')[:2])
        except:
            cv = self.remote_client_version
        return '<Peer%r %s>' % (pn, cv)
        # return '<Peer%r>' % repr(pn)

    def report_error(self, reason):
        try:
            ip_port = self.ip_port
        except:
            ip_port = 'ip_port not available fixme'
        self.peermanager.errors.add(ip_port, reason, self.remote_client_version)

    @property
    def ip_port(self):
        try:
            return self.connection.getpeername()
        except Exception as e:
            log.debug('ip_port failed')
            raise e

    def connect_service(self, service):
        assert isinstance(service, WiredService)
        protocol_class = service.wire_protocol
        assert issubclass(protocol_class, BaseProtocol)
        # create protcol instance which connects peer with serivce
        protocol = protocol_class(self, service)
        # register protocol
        assert protocol_class not in self.protocols
        log.debug('registering protocol', protocol=protocol.name, peer=self)
        self.protocols[protocol_class] = protocol
        self.mux.add_protocol(protocol.protocol_id)
        protocol.start()

    def has_protocol(self, protocol):
        assert issubclass(protocol, BaseProtocol)
        return protocol in self.protocols

    def receive_hello(self, proto, version, client_version, capabilities, listen_port, nodeid):
        # register in common protocols
        log.info('received hello', version=version,
                 client_version=client_version, capabilities=capabilities)
        self.remote_client_version = client_version

        # call peermanager
        agree = self.peermanager.on_hello_received(
            proto, version, client_version, capabilities, listen_port, nodeid)
        if not agree:
            return

        log.info('connecting services', services=self.peermanager.wired_services)
        remote_services = dict((name, version) for name, version in capabilities)
        for service in sorted(self.peermanager.wired_services, key=operator.attrgetter('name')):
            proto = service.wire_protocol
            assert isinstance(service, WiredService)
            if proto.name in remote_services:
                if remote_services[proto.name] == proto.version:
                    if service != self.peermanager:  # p2p protcol already registered
                        self.connect_service(service)
                else:
                    log.info('wrong version', service=proto.name, local_version=proto.version,
                             remote_version=remote_services[proto.name])
                    self.report_error('wrong version')

    @property
    def capabilities(self):
        return [(s.wire_protocol.name, s.wire_protocol.version)
                for s in self.peermanager.wired_services]

    # sending p2p messages

    def send_packet(self, packet):
        # rewrite cmd id / future FIXME  to packet.protocol_id
        protocol = list(self.protocols.values())[packet.protocol_id]
        log.debug('send packet', cmd=protocol.cmd_by_id[packet.cmd_id], protcol=protocol.name,
                  peer=self)
        # rewrite cmd_id  # FIXME
        for i, protocol in enumerate(self.protocols.values()):
            if packet.protocol_id > i:
                packet.cmd_id += (0 if protocol.max_cmd_id == 0 else protocol.max_cmd_id + 1)
            if packet.protocol_id == protocol.protocol_id:
                break

        packet.protocol_id = 0
        # done rewrite
        self.mux.add_packet(packet)

    # receiving p2p messages

    def protocol_cmd_id_from_packet(self, packet):
        # packet.protocol_id not yet used. old adaptive cmd_ids instead
        # future FIXME  to packet.protocol_id

        # get protocol and protocol.cmd_id from packet.cmd_id
        max_id = 0
        assert packet.protocol_id == 0  # FIXME, should be used by other peers
        for protocol in self.protocols.values():
            if packet.cmd_id < max_id + protocol.max_cmd_id + 1:
                return protocol, packet.cmd_id - (0 if max_id == 0 else max_id + 1)
            max_id += protocol.max_cmd_id

        raise Exception('no protocol for id %s' % packet.cmd_id)

    def _handle_packet(self, packet):
        assert isinstance(packet, multiplexer.Packet)
        protocol, cmd_id = self.protocol_cmd_id_from_packet(packet)
        log.debug('recv packet', cmd=protocol.cmd_by_id[
                  cmd_id], protocol=protocol.name, orig_cmd_id=packet.cmd_id)
        packet.cmd_id = cmd_id  # rewrite
        protocol.receive_packet(packet)

    def send(self, data):
        if not data:
            return
        self.safe_to_read.clear()  # make sure we don't accept any data until message is sent
        try:
            self.connection.sendall(data)  # check if gevent chunkes and switches contexts
        except gevent.socket.error as e:
            log.info('write error', errno=e.errno, reason=e.strerror)
            self.report_error('write error %r' % e.strerror)
            self.stop()
        except gevent.socket.timeout:
            log.info('write timeout')
            self.report_error('write timeout')
            self.stop()
        self.safe_to_read.set()

    def _run_egress_message(self):
        while not self.is_stopped:
            self.send(self.mux.message_queue.get())

    def _run_decoded_packets(self):
        # handle decoded packets
        while not self.is_stopped:
            self._handle_packet(self.mux.packet_queue.get())  # get_packet blocks

    def _run_ingress_message(self):
        gevent.spawn(self._run_decoded_packets)
        gevent.spawn(self._run_egress_message)

        while not self.is_stopped:
            self.safe_to_read.wait()
            gevent.socket.wait_read(self.connection.fileno())
            try:
                imsg = self.connection.recv(4096)
            except gevent.socket.error as e:
                log.info('read error', errno=e.errno, reason=e.strerror, peer=self)
                self.report_error('network error %s' % e.strerror)
                if e.errno in(50, 54, 60, 65):
                    # (Network down, Connection reset by peer, timeout, nor route to host)
                    self.stop()
                else:
                    raise e
                    break
            if imsg:
                try:
                    self.mux.add_message(imsg)
                except rlpxcipher.RLPxSessionError as e:
                    log.debug('rlpx session error', peer=self, error=e)
                    self.report_error('rlpx session error')
                    self.stop()
                except multiplexer.MultiplexerError as e:
                    log.debug('multiplexer error', peer=self, error=e)
                    self.report_error('multiplexer error')
                    self.stop()
            else:
                log.debug('no data on socket', peer=self)
                self.report_error('no data on socket')
                self.stop()

    _run = _run_ingress_message

    def stop(self):
        if not self.is_stopped:
            self.is_stopped = True
            log.debug('stopped', peer=self)
            for p in self.protocols.values():
                p.stop()
            self.peermanager.peers.remove(self)
            self.kill()
Ejemplo n.º 7
0
class Peer(gevent.Greenlet):

    remote_client_version = ''
    wait_read_timeout = 0.001

    def __init__(self,
                 peermanager,
                 connection,
                 remote_pubkey=None):  # FIXME node vs remote_pubkey
        super(Peer, self).__init__()
        self.is_stopped = False
        self.peermanager = peermanager
        self.connection = connection
        self.config = peermanager.config
        self.protocols = OrderedDict()
        log.debug('peer init', peer=self)

        # create multiplexed encrypted session
        privkey = self.config['node']['privkey_hex'].decode('hex')
        hello_packet = P2PProtocol.get_hello_packet(self)
        self.mux = MultiplexedSession(privkey,
                                      hello_packet,
                                      token_by_pubkey=dict(),
                                      remote_pubkey=remote_pubkey)

        # register p2p protocol
        assert issubclass(self.peermanager.wire_protocol, P2PProtocol)
        self.connect_service(self.peermanager)

        # assure, we don't get messages while replies are not read
        self.safe_to_read = gevent.event.Event()
        self.safe_to_read.set()

    @property
    def remote_pubkey(self):
        "if peer is responder, then the remote_pubkey will not be available"
        "before the first packet is received"
        return self.mux.remote_pubkey

    def __repr__(self):
        try:
            pn = self.connection.getpeername()
        except gevent.socket.error:
            pn = ('not ready', )
        try:
            cv = '/'.join(self.remote_client_version.split('/')[:2])
        except:
            cv = self.remote_client_version
        return '<Peer%r %s>' % (pn, cv)
        # return '<Peer%r>' % repr(pn)

    def report_error(self, reason):
        try:
            ip_port = self.ip_port
        except:
            ip_port = 'ip_port not available fixme'
        self.peermanager.errors.add(ip_port, reason,
                                    self.remote_client_version)

    @property
    def ip_port(self):
        try:
            return self.connection.getpeername()
        except Exception as e:
            log.debug('ip_port failed')
            raise e

    def connect_service(self, service):
        assert isinstance(service, WiredService)
        protocol_class = service.wire_protocol
        assert issubclass(protocol_class, BaseProtocol)
        # create protcol instance which connects peer with serivce
        protocol = protocol_class(self, service)
        # register protocol
        assert protocol_class not in self.protocols
        log.debug('registering protocol', protocol=protocol.name, peer=self)
        self.protocols[protocol_class] = protocol
        self.mux.add_protocol(protocol.protocol_id)
        protocol.start()

    def has_protocol(self, protocol):
        assert issubclass(protocol, BaseProtocol)
        return protocol in self.protocols

    def receive_hello(self, proto, version, client_version, capabilities,
                      listen_port, nodeid):
        # register in common protocols
        log.info('received hello',
                 version=version,
                 client_version=client_version,
                 capabilities=capabilities)
        self.remote_client_version = client_version

        # call peermanager
        agree = self.peermanager.on_hello_received(proto, version,
                                                   client_version,
                                                   capabilities, listen_port,
                                                   nodeid)
        if not agree:
            return

        log.debug('connecting services',
                  services=self.peermanager.wired_services)
        remote_services = dict(
            (name, version) for name, version in capabilities)
        for service in sorted(self.peermanager.wired_services,
                              key=operator.attrgetter('name')):
            proto = service.wire_protocol
            assert isinstance(service, WiredService)
            if proto.name in remote_services:
                if remote_services[proto.name] == proto.version:
                    if service != self.peermanager:  # p2p protcol already registered
                        self.connect_service(service)
                else:
                    log.debug('wrong version',
                              service=proto.name,
                              local_version=proto.version,
                              remote_version=remote_services[proto.name])
                    self.report_error('wrong version')

    @property
    def capabilities(self):
        return [(s.wire_protocol.name, s.wire_protocol.version)
                for s in self.peermanager.wired_services]

    # sending p2p messages

    def send_packet(self, packet):
        # rewrite cmd id / future FIXME  to packet.protocol_id
        for i, protocol in enumerate(self.protocols.values()):
            if packet.protocol_id == protocol.protocol_id:
                break
        assert packet.protocol_id == protocol.protocol_id, 'no protocol found'
        log.debug('send packet',
                  cmd=protocol.cmd_by_id[packet.cmd_id],
                  protcol=protocol.name,
                  peer=self)
        # rewrite cmd_id  # FIXME
        for i, protocol in enumerate(self.protocols.values()):
            if packet.protocol_id > i:
                packet.cmd_id += (0 if protocol.max_cmd_id == 0 else
                                  protocol.max_cmd_id + 1)
            if packet.protocol_id == protocol.protocol_id:
                break

        packet.protocol_id = 0
        # done rewrite
        self.mux.add_packet(packet)

    # receiving p2p messages

    def protocol_cmd_id_from_packet(self, packet):
        # packet.protocol_id not yet used. old adaptive cmd_ids instead
        # future FIXME  to packet.protocol_id

        # get protocol and protocol.cmd_id from packet.cmd_id
        max_id = 0
        assert packet.protocol_id == 0  # FIXME, should be used by other peers
        for protocol in self.protocols.values():
            if packet.cmd_id < max_id + protocol.max_cmd_id + 1:
                return protocol, packet.cmd_id - (0 if max_id == 0 else
                                                  max_id + 1)
            max_id += protocol.max_cmd_id
        raise UnknownCommandError('no protocol for id %s' % packet.cmd_id)

    def _handle_packet(self, packet):
        assert isinstance(packet, multiplexer.Packet)
        try:
            protocol, cmd_id = self.protocol_cmd_id_from_packet(packet)
        except UnknownCommandError, e:
            log.error('received unknown cmd', error=e, packet=packet)
            return
        log.debug('recv packet',
                  cmd=protocol.cmd_by_id[cmd_id],
                  protocol=protocol.name,
                  orig_cmd_id=packet.cmd_id)
        packet.cmd_id = cmd_id  # rewrite
        protocol.receive_packet(packet)
Ejemplo n.º 8
0
class Peer(gevent.Greenlet):

    remote_client_version = ''
    wait_read_timeout = 0.001

    def __init__(self,
                 peermanager,
                 connection,
                 remote_pubkey=None):  # FIXME node vs remote_pubkey
        super(Peer, self).__init__()
        self.is_stopped = False
        self.peermanager = peermanager
        self.connection = connection
        self.config = peermanager.config
        self.protocols = OrderedDict()
        log.debug('peer init', peer=self)

        # create multiplexed encrypted session
        privkey = self.config['node']['privkey_hex'].decode('hex')
        hello_packet = P2PProtocol.get_hello_packet(self)
        self.mux = MultiplexedSession(privkey,
                                      hello_packet,
                                      token_by_pubkey=dict(),
                                      remote_pubkey=remote_pubkey)

        # register p2p protocol
        assert issubclass(self.peermanager.wire_protocol, P2PProtocol)
        self.connect_service(self.peermanager)

        # assure, we don't get messages while replies are not read
        self.safe_to_read = gevent.event.Event()
        self.safe_to_read.set()

    @property
    def remote_pubkey(self):
        "if peer is responder, then the remote_pubkey will not be available"
        "before the first packet is received"
        return self.mux.remote_pubkey

    def __repr__(self):
        try:
            pn = self.connection.getpeername()
        except gevent.socket.error:
            pn = ('not ready', )
        try:
            cv = '/'.join(self.remote_client_version.split('/')[:2])
        except:
            cv = self.remote_client_version
        return '<Peer%r %s>' % (pn, cv)
        # return '<Peer%r>' % repr(pn)

    def report_error(self, reason):
        try:
            ip_port = self.ip_port
        except:
            ip_port = 'ip_port not available fixme'
        self.peermanager.errors.add(ip_port, reason,
                                    self.remote_client_version)

    @property
    def ip_port(self):
        try:
            return self.connection.getpeername()
        except Exception as e:
            log.debug('ip_port failed')
            raise e

    def connect_service(self, service):
        assert isinstance(service, WiredService)
        protocol_class = service.wire_protocol
        assert issubclass(protocol_class, BaseProtocol)
        # create protcol instance which connects peer with serivce
        protocol = protocol_class(self, service)
        # register protocol
        assert protocol_class not in self.protocols
        log.debug('registering protocol', protocol=protocol.name, peer=self)
        self.protocols[protocol_class] = protocol
        self.mux.add_protocol(protocol.protocol_id)
        protocol.start()

    def has_protocol(self, protocol):
        assert issubclass(protocol, BaseProtocol)
        return protocol in self.protocols

    def receive_hello(self, proto, version, client_version, capabilities,
                      listen_port, nodeid):
        # register in common protocols
        log.info('received hello',
                 version=version,
                 client_version=client_version,
                 capabilities=capabilities)
        self.remote_client_version = client_version

        # call peermanager
        agree = self.peermanager.on_hello_received(proto, version,
                                                   client_version,
                                                   capabilities, listen_port,
                                                   nodeid)
        if not agree:
            return

        log.info('connecting services',
                 services=self.peermanager.wired_services)
        remote_services = dict(
            (name, version) for name, version in capabilities)
        for service in sorted(self.peermanager.wired_services,
                              key=operator.attrgetter('name')):
            proto = service.wire_protocol
            assert isinstance(service, WiredService)
            if proto.name in remote_services:
                if remote_services[proto.name] == proto.version:
                    if service != self.peermanager:  # p2p protcol already registered
                        self.connect_service(service)
                else:
                    log.info('wrong version',
                             service=proto.name,
                             local_version=proto.version,
                             remote_version=remote_services[proto.name])
                    self.report_error('wrong version')

    @property
    def capabilities(self):
        return [(s.wire_protocol.name, s.wire_protocol.version)
                for s in self.peermanager.wired_services]

    # sending p2p messages

    def send_packet(self, packet):
        # rewrite cmd id / future FIXME  to packet.protocol_id
        protocol = list(self.protocols.values())[packet.protocol_id]
        log.debug('send packet',
                  cmd=protocol.cmd_by_id[packet.cmd_id],
                  protcol=protocol.name,
                  peer=self)
        # rewrite cmd_id  # FIXME
        for i, protocol in enumerate(self.protocols.values()):
            if packet.protocol_id > i:
                packet.cmd_id += (0 if protocol.max_cmd_id == 0 else
                                  protocol.max_cmd_id + 1)
            if packet.protocol_id == protocol.protocol_id:
                break

        packet.protocol_id = 0
        # done rewrite
        self.mux.add_packet(packet)

    # receiving p2p messages

    def protocol_cmd_id_from_packet(self, packet):
        # packet.protocol_id not yet used. old adaptive cmd_ids instead
        # future FIXME  to packet.protocol_id

        # get protocol and protocol.cmd_id from packet.cmd_id
        max_id = 0
        assert packet.protocol_id == 0  # FIXME, should be used by other peers
        for protocol in self.protocols.values():
            if packet.cmd_id < max_id + protocol.max_cmd_id + 1:
                return protocol, packet.cmd_id - (0 if max_id == 0 else
                                                  max_id + 1)
            max_id += protocol.max_cmd_id

        raise Exception('no protocol for id %s' % packet.cmd_id)

    def _handle_packet(self, packet):
        assert isinstance(packet, multiplexer.Packet)
        protocol, cmd_id = self.protocol_cmd_id_from_packet(packet)
        log.debug('recv packet',
                  cmd=protocol.cmd_by_id[cmd_id],
                  protocol=protocol.name,
                  orig_cmd_id=packet.cmd_id)
        packet.cmd_id = cmd_id  # rewrite
        protocol.receive_packet(packet)

    def send(self, data):
        if not data:
            return
        self.safe_to_read.clear(
        )  # make sure we don't accept any data until message is sent
        try:
            self.connection.sendall(
                data)  # check if gevent chunkes and switches contexts
        except gevent.socket.error as e:
            log.info('write error', errno=e.errno, reason=e.strerror)
            self.report_error('write error %r' % e.strerror)
            self.stop()
        except gevent.socket.timeout:
            log.info('write timeout')
            self.report_error('write timeout')
            self.stop()
        self.safe_to_read.set()

    def _run_egress_message(self):
        while not self.is_stopped:
            self.send(self.mux.message_queue.get())

    def _run_decoded_packets(self):
        # handle decoded packets
        while not self.is_stopped:
            self._handle_packet(
                self.mux.packet_queue.get())  # get_packet blocks

    def _run_ingress_message(self):
        gevent.spawn(self._run_decoded_packets)
        gevent.spawn(self._run_egress_message)

        while not self.is_stopped:
            self.safe_to_read.wait()
            gevent.socket.wait_read(self.connection.fileno())
            try:
                imsg = self.connection.recv(4096)
            except gevent.socket.error as e:
                log.info('read error',
                         errno=e.errno,
                         reason=e.strerror,
                         peer=self)
                self.report_error('network error %s' % e.strerror)
                if e.errno in (50, 54, 60, 65):
                    # (Network down, Connection reset by peer, timeout, nor route to host)
                    self.stop()
                else:
                    raise e
                    break
            if imsg:
                try:
                    self.mux.add_message(imsg)
                except rlpxcipher.RLPxSessionError as e:
                    log.debug('rlpx session error', peer=self, error=e)
                    self.report_error('rlpx session error')
                    self.stop()
                except multiplexer.MultiplexerError as e:
                    log.debug('multiplexer error', peer=self, error=e)
                    self.report_error('multiplexer error')
                    self.stop()
            else:
                log.debug('no data on socket', peer=self)
                self.report_error('no data on socket')
                self.stop()

    _run = _run_ingress_message

    def stop(self):
        if not self.is_stopped:
            self.is_stopped = True
            log.debug('stopped', peer=self)
            for p in self.protocols.values():
                p.stop()
            self.peermanager.peers.remove(self)
            self.kill()