コード例 #1
0
    def _select_server(self, config):
        server = config['server']
        if server.startswith('http'):
            return server
        elif server != 'auto':
            raise TransportError(
                'Invalid matrix server specified (valid values: "auto" or a URL)'
            )

        def _get_rtt(server_name):
            return server_name, get_http_rtt(server_name)

        get_rtt_jobs = [
            gevent.spawn(_get_rtt, server_name)
            for server_name in config['available_servers']
        ]
        gevent.joinall(get_rtt_jobs)
        sorted_servers = sorted(
            (job.value for job in get_rtt_jobs if job.value[1] is not None),
            key=itemgetter(1),
        )
        self.log.debug('Matrix homeserver RTT times', rtt_times=sorted_servers)
        if not sorted_servers:
            raise TransportError(
                'Could not select a Matrix server. No candidates remaining. '
                'Please check your network connectivity.', )
        best_server, rtt = sorted_servers[0]
        self.log.info(
            'Automatically selecting matrix homeserver based on RTT',
            homeserver=best_server,
            rtt=rtt,
        )
        return best_server
コード例 #2
0
def make_client(
    handle_messages_callback: Callable[[MatrixSyncMessages], bool],
    servers: List[str],
    *args: Any,
    **kwargs: Any,
) -> GMatrixClient:
    """Given a list of possible servers, chooses the closest available and create a GMatrixClient

    Params:
        servers: list of servers urls, with scheme (http or https)
        Rest of args and kwargs are forwarded to GMatrixClient constructor
    Returns:
        GMatrixClient instance for one of the available servers
    """
    if len(servers) > 1:
        sorted_servers = sort_servers_closest(servers)
        log.debug("Selecting best matrix server",
                  sorted_servers=sorted_servers)
    elif len(servers) == 1:
        sorted_servers = {servers[0]: 0}
    else:
        raise TransportError("No valid servers list given")

    last_ex = None
    for server_url, rtt in sorted_servers.items():
        client = GMatrixClient(handle_messages_callback, server_url, *args,
                               **kwargs)

        retries = 3
        while retries:
            retries -= 1
            try:
                client.api._send("GET",
                                 "/versions",
                                 api_path="/_matrix/client")
            except MatrixRequestError as ex:
                log.warning(
                    "Matrix server returned an error, retrying",
                    server_url=server_url,
                    _exception=ex,
                )
                last_ex = ex
            except MatrixError as ex:
                log.warning("Selected server not usable",
                            server_url=server_url,
                            _exception=ex)
                last_ex = ex
                retries = 0
            else:
                log.info(
                    "Using Matrix server",
                    server_url=server_url,
                    server_ident=client.api.server_ident,
                    average_rtt=rtt,
                )
                return client

    raise TransportError(
        "Unable to find a reachable Matrix server. Please check your network connectivity."
    ) from last_ex
コード例 #3
0
    def __init__(self, config: dict):
        super().__init__()
        self._config = config
        self._raiden_service: RaidenService = None

        def _http_retry_delay() -> Iterable[float]:
            # below constants are defined in raiden.app.App.DEFAULT_CONFIG
            return udp_utils.timeout_exponential_backoff(
                self._config['retries_before_backoff'],
                self._config['retry_interval'] / 5,
                self._config['retry_interval'],
            )

        while True:
            self._server_url: str = self._select_server(config)
            self._server_name = config.get('server_name', urlparse(self._server_url).hostname)
            client_class = config.get('client_class', GMatrixClient)
            self._client: GMatrixClient = client_class(
                self._server_url,
                http_pool_maxsize=4,
                http_retry_timeout=40,
                http_retry_delay=_http_retry_delay,
            )
            try:
                self._client.api._send('GET', '/versions', api_path='/_matrix/client')
                break
            except MatrixError as ex:
                if config['server'] != 'auto':
                    raise TransportError(
                        f"Could not connect to requested server '{config['server']}'",
                    ) from ex

                config['available_servers'].remove(self._server_url)
                if len(config['available_servers']) == 0:
                    raise TransportError(
                        f"Unable to find a reachable Matrix server. "
                        f"Please check your network connectivity.",
                    ) from ex
                log.warning(f"Selected server '{self._server_url}' not usable. Retrying.")

        self.greenlets = list()

        self._discovery_room: Room = None

        # partner need to be in this dict to be listened on
        self._address_to_userids: Dict[Address, Set[str]] = defaultdict(set)
        self._address_to_presence: Dict[Address, UserPresence] = dict()
        self._userid_to_presence: Dict[str, UserPresence] = dict()

        self._discovery_room_alias = None

        self._stop_event = gevent.event.Event()
        self._stop_event.set()

        self._client.add_invite_listener(self._handle_invite)
        self._client.add_presence_listener(self._handle_presence_change)

        self._messages_cache = TTLCache(32, 4)
        self._health_lock = Semaphore()
        self._getroom_lock = Semaphore()
コード例 #4
0
    def __init__(self, config: dict):
        self._bound_logger = None
        self._raiden_service: RaidenService = None
        while True:
            self._server_url: str = self._select_server(config)
            self._server_name = config.get('server_name',
                                           urlparse(self._server_url).hostname)
            client_class = config.get('client_class', GMatrixClient)
            self._client: GMatrixClient = client_class(
                self._server_url,
                max_retries=5,
                pool_maxsize=4,
            )
            try:
                self._client.api._send('GET',
                                       '/versions',
                                       api_path='/_matrix/client')
                break
            except MatrixError as ex:
                if config['server'] != 'auto':
                    raise TransportError(
                        f"Could not connect to requested server '{config['server']}'",
                    ) from ex

                config['available_servers'].remove(self._server_url)
                if len(config['available_servers']) == 0:
                    raise TransportError(
                        f"Unable to find a reachable Matrix server. "
                        f"Please check your network connectivity.", ) from ex
                log.warning(
                    f"Selected server '{self._server_url}' not usable. Retrying."
                )

        self.greenlets = list()

        self._discovery_room: Room = None

        self._messageids_to_asyncresult: Dict[Address, AsyncResult] = dict()
        # partner need to be in this dict to be listened on
        self._address_to_userids: Dict[Address, Set[str]] = defaultdict(set)
        self._address_to_presence: Dict[Address, UserPresence] = dict()
        self._userid_to_presence: Dict[str, UserPresence] = dict()

        self._discovery_room_alias = None
        self._discovery_room_alias_full = None
        self._login_retry_wait = config.get('login_retry_wait', 0.5)
        self._logout_timeout = config.get('logout_timeout', 10)

        self._running = False
        self._health_semaphore = gevent.lock.Semaphore()

        self._client.add_invite_listener(self._handle_invite)
        self._client.add_presence_listener(self._handle_presence_change)
コード例 #5
0
ファイル: utils.py プロジェクト: pinklite/raiden
def sort_servers_closest(
    servers: Sequence[str],
    max_timeout: float = 3.0,
    samples_per_server: int = 3,
    sample_delay: float = 0.125,
) -> Dict[str, float]:
    """Sorts a list of servers by http round-trip time

    Params:
        servers: sequence of http server urls
    Returns:
        sequence of pairs of url,rtt in seconds, sorted by rtt, excluding failed and excessively
        slow servers (possibly empty)

    The default timeout was chosen after measuring the long tail of the development matrix servers.
    Under no stress, servers will have a very long tail of up to 2.5 seconds (measured 15/01/2020),
    which can lead to failure during startup if the timeout is too low.
    This increases the timeout so that the network hiccups won't cause Raiden startup failures.
    """
    if not {urlparse(url).scheme for url in servers}.issubset({"http", "https"}):
        raise TransportError("Invalid server urls")

    rtt_greenlets = set(
        spawn_named(
            "get_average_http_response_time",
            get_average_http_response_time,
            url=server_url,
            samples=samples_per_server,
            sample_delay=sample_delay,
        )
        for server_url in servers
    )

    total_timeout = samples_per_server * (max_timeout + sample_delay)

    results = []
    for greenlet in gevent.iwait(rtt_greenlets, timeout=total_timeout):
        result = greenlet.get()
        if result is not None:
            results.append(result)

    gevent.killall(rtt_greenlets)

    if not results:
        raise TransportError(
            f"No Matrix server available with good latency, requests takes more "
            f"than {max_timeout} seconds."
        )

    server_url_to_rtt = dict(sorted(results, key=itemgetter(1)))
    log.debug("Available Matrix homeservers", servers=server_url_to_rtt)
    return server_url_to_rtt
コード例 #6
0
def sort_servers_closest(
        servers: Sequence[str]) -> Sequence[Tuple[str, float]]:
    """Sorts a list of servers by http round-trip time

    Params:
        servers: sequence of http server urls
    Returns:
        sequence of pairs of url,rtt in seconds, sorted by rtt, excluding failed servers
        (possibly empty)
    """
    if not {urlparse(url).scheme
            for url in servers}.issubset({'http', 'https'}):
        raise TransportError('Invalid server urls')

    get_rtt_jobs = [
        gevent.spawn(lambda url: (url, get_http_rtt(url)), server_url)
        for server_url in servers
    ]
    # these tasks should never raise, returns None on errors
    gevent.joinall(get_rtt_jobs, raise_error=False)  # block and wait tasks
    sorted_servers: List[Tuple[str, float]] = sorted(
        (job.value for job in get_rtt_jobs if job.value[1] is not None),
        key=itemgetter(1),
    )
    log.debug('Matrix homeserver RTT times', rtt_times=sorted_servers)
    return sorted_servers
コード例 #7
0
ファイル: utils.py プロジェクト: weilbith/raiden
    def warm_users(self, users: List[User]) -> None:
        for user in users:
            user_id = user.user_id
            cached_displayname = self.userid_to_displayname.get(user_id)

            if cached_displayname is None:
                # The cache is cold, query and warm it.
                if not user.displayname:
                    # Handles an edge case where the Matrix federation does not
                    # have the profile for a given userid. The server response
                    # is roughly:
                    #
                    #   {"errcode":"M_NOT_FOUND","error":"Profile was not found"}
                    try:
                        user.get_display_name()
                    except MatrixRequestError:
                        raise TransportError(
                            "Could not get 'display_name' for user")

                if user.displayname is not None:
                    self.userid_to_displayname[user.user_id] = user.displayname

            elif user.displayname is None:
                user.displayname = cached_displayname

            elif user.displayname != cached_displayname:
                log.debug(
                    "User displayname changed!",
                    cached=cached_displayname,
                    current=user.displayname,
                )
                self.userid_to_displayname[user.user_id] = user.displayname
コード例 #8
0
 def _client_exception_handler(self, greenlet):
     self._running = False
     try:
         greenlet.get()
     except MatrixError as ex:
         gevent.get_hub().handle_system_error(
             TransportError,
             TransportError(
                 f'Unexpected error while communicating with Matrix homeserver: {ex}',
             ),
         )
コード例 #9
0
    def join_global_rooms(
        self, client: GMatrixClient,
        available_servers: Sequence[str] = ()) -> None:
        """Join or create a global public room with given name on all available servers.
        If global rooms are not found, create a public room with the name on each server.

        Params:
            client: matrix-python-sdk client instance
            servers: optional: sequence of known/available servers to try to find the room in
        """
        suffix = self.service_room_suffix
        room_alias_prefix = make_room_alias(self.chain_id, suffix)

        parsed_servers = [
            urlparse(s).netloc for s in available_servers
            if urlparse(s).netloc not in {None, ""}
        ]

        for server in parsed_servers:
            room_alias_full = f"#{room_alias_prefix}:{server}"
            log.debug(f"Trying to join {suffix} room",
                      room_alias_full=room_alias_full)
            try:
                broadcast_room = client.join_room(room_alias_full)
                log.debug(f"Joined {suffix} room", room=broadcast_room)
                self.broadcast_rooms.append(broadcast_room)
            except MatrixRequestError as ex:
                if ex.code != 404:
                    log.debug(
                        f"Could not join {suffix} room, trying to create one",
                        room_alias_full=room_alias_full,
                    )
                    try:
                        broadcast_room = client.create_room(room_alias_full,
                                                            is_public=True)
                        log.debug(f"Created {suffix} room",
                                  room=broadcast_room)
                        self.broadcast_rooms.append(broadcast_room)
                    except MatrixRequestError:
                        log.debug(
                            f"Could neither join nor create a {suffix} room",
                            room_alias_full=room_alias_full,
                        )
                        raise TransportError(
                            f"Could neither join nor create a {suffix} room")

                else:
                    log.debug(
                        f"Could not join {suffix} room",
                        room_alias_full=room_alias_full,
                        _exception=ex,
                    )
                    raise
コード例 #10
0
def make_client(servers: Sequence[str], *args, **kwargs) -> GMatrixClient:
    """Given a list of possible servers, chooses the closest available and create a GMatrixClient

    Params:
        servers: list of servers urls, with scheme (http or https)
        Rest of args and kwargs are forwarded to GMatrixClient constructor
    Returns:
        GMatrixClient instance for one of the available servers
    """
    if len(servers) > 1:
        sorted_servers = [
            server_url for (server_url, _) in sort_servers_closest(servers)
        ]
        log.info(
            'Automatically selecting matrix homeserver based on RTT',
            sorted_servers=sorted_servers,
        )
    elif len(servers) == 1:
        sorted_servers = servers
    else:
        raise TransportError('No valid servers list given')

    last_ex = None
    for server_url in sorted_servers:
        server_url: str = server_url
        client = GMatrixClient(server_url, *args, **kwargs)
        try:
            client.api._send('GET', '/versions', api_path='/_matrix/client')
        except MatrixError as ex:
            log.warning('Selected server not usable',
                        server_url=server_url,
                        _exception=ex)
            last_ex = ex
        else:
            break
    else:
        raise TransportError(
            'Unable to find a reachable Matrix server. Please check your network connectivity.',
        ) from last_ex
    return client
コード例 #11
0
ファイル: utils.py プロジェクト: weilbith/raiden
def make_message_batches(
    message_texts: Iterable[str],
    _max_batch_size: int = MATRIX_MAX_BATCH_SIZE
) -> Generator[str, None, None]:
    """ Group messages into newline separated batches not exceeding ``_max_batch_size``. """
    current_batch: List[str] = []
    size = 0
    for message_text in message_texts:
        if size + len(message_text) > _max_batch_size:
            if size == 0:
                # A single message exceeds the maximum batch size. This should not happen.
                raise TransportError(
                    f"Message exceeds batch size. Size: {len(message_text)}, "
                    f"Max: {MATRIX_MAX_BATCH_SIZE}, Message: {message_text}")
            yield "\n".join(current_batch)
            current_batch = []
            size = 0
        current_batch.append(message_text)
        size += len(message_text)
    if current_batch:
        yield "\n".join(current_batch)
コード例 #12
0
    def _start_client(self, server_url: str) -> GMatrixClient:
        assert self.user_manager
        if self.stop_event.is_set():
            raise TransportError()

        if server_url == self.main_client.api.base_url:
            client = self.main_client
        else:
            # Also handle messages on the other clients,
            # since to-device communication to the PFS only happens via the local user
            # on each homeserver
            client = make_client(
                handle_messages_callback=self.main_client.
                handle_messages_callback,
                servers=[server_url],
                http_pool_maxsize=4,
                http_retry_timeout=40,
                http_retry_delay=matrix_http_retry_delay,
            )

            self.server_url_to_other_clients[server_url] = client
            log.debug("Created client for other server", server_url=server_url)
        try:
            login(client, signer=self.local_signer, device_id=self.device_id)
            log.debug("Matrix login successful", server_url=server_url)

        except (MatrixRequestError, ValueError):
            raise ConnectionError("Could not login/register to matrix.")

        client.start_listener_thread(
            DEFAULT_TRANSPORT_MATRIX_SYNC_TIMEOUT,
            DEFAULT_TRANSPORT_MATRIX_SYNC_LATENCY,
        )

        # main client is already added upon MultiClientUserAddressManager.start()
        if server_url != self.main_client.api.base_url:
            self.user_manager.add_client(client)
        return client
コード例 #13
0
def join_global_room(client: GMatrixClient,
                     name: str,
                     servers: Sequence[str] = ()) -> Room:
    """Join or create a global public room with given name

    First, try to join room on own server (client-configured one)
    If can't, try to join on each one of servers, and if able, alias it in our server
    If still can't, create a public room with name in our server

    Params:
        client: matrix-python-sdk client instance
        name: name or alias of the room (without #-prefix or server name suffix)
        servers: optional: sequence of known/available servers to try to find the room in
    Returns:
        matrix's Room instance linked to client
    """
    our_server_name = urlparse(client.api.base_url).netloc
    assert our_server_name, 'Invalid client\'s homeserver url'
    servers = [our_server_name] + [  # client's own server first
        urlparse(s).netloc for s in servers
        if urlparse(s).netloc not in {None, '', our_server_name}
    ]

    our_server_global_room_alias_full = f'#{name}:{servers[0]}'

    # try joining a global room on any of the available servers, starting with ours
    for server in servers:
        global_room_alias_full = f'#{name}:{server}'
        try:
            global_room = client.join_room(global_room_alias_full)
        except MatrixRequestError as ex:
            if ex.code not in (403, 404, 500):
                raise
            log.debug(
                'Could not join global room',
                room_alias_full=global_room_alias_full,
                _exception=ex,
            )
        else:
            if our_server_global_room_alias_full not in global_room.aliases:
                # we managed to join a global room, but it's not aliased in our server
                global_room.add_room_alias(our_server_global_room_alias_full)
                global_room.aliases.append(our_server_global_room_alias_full)
            break
    else:
        log.debug('Could not join any global room, trying to create one')
        for _ in range(JOIN_RETRIES):
            try:
                global_room = client.create_room(name, is_public=True)
            except MatrixRequestError as ex:
                if ex.code not in (400, 409):
                    raise
                try:
                    global_room = client.join_room(
                        our_server_global_room_alias_full, )
                except MatrixRequestError as ex:
                    if ex.code not in (404, 403):
                        raise
                else:
                    break
            else:
                break
        else:
            raise TransportError('Could neither join nor create a global room')

    return global_room
コード例 #14
0
ファイル: client.py プロジェクト: zointblackbriar/raiden
    def create_sync_filter(
        self,
        rooms: Optional[Iterable[Room]] = None,
        not_rooms: Optional[Iterable[Room]] = None,
        limit: Optional[int] = None,
    ) -> Optional[int]:
        """ Create a matrix sync filter

        A whitelist and blacklist of rooms can be supplied optionally. If
        no whitelist ist given, all rooms are whitelisted. The blacklist is
        applied on top of the whitelist.

        Ref. https://matrix.org/docs/spec/client_server/r0.6.0#api-endpoints

        Args:
            rooms: whitelist of rooms, if not given all rooms are whitelisted
            not_rooms: blacklist of rooms, applied after the whitelist
            limit: maximum number of messages to return

        """
        if not_rooms is None and rooms is None and limit is None:
            return None

        broadcast_room_filter: Dict[str, Dict] = {
            # Get all presence updates
            "presence": {
                "types": ["m.presence"]
            },
            # filter account data
            "account_data": {
                "not_types": ["*"]
            },
            # Ignore "message receipts" from all rooms
            "room": {
                "ephemeral": {
                    "not_types": ["m.receipt"]
                }
            },
        }
        if not_rooms:
            negative_rooms = [room.room_id for room in not_rooms]
            broadcast_room_filter["room"].update({
                # Filter out all unwanted rooms
                "not_rooms": negative_rooms
            })
        if rooms:
            positive_rooms = [room.room_id for room in rooms]
            broadcast_room_filter["room"].update({
                # Set all wanted rooms
                "rooms": positive_rooms
            })

        limit_filter: Dict[str, Any] = {}
        if limit is not None:
            limit_filter = {"room": {"timeline": {"limit": limit}}}

        final_filter = broadcast_room_filter
        merge_dict(final_filter, limit_filter)

        try:
            # 0 is a valid filter ID
            filter_response = self.api.create_filter(self.user_id,
                                                     final_filter)
            filter_id = filter_response.get("filter_id")
            log.debug("Sync filter created",
                      filter_id=filter_id,
                      filter=final_filter)

        except MatrixRequestError as ex:
            raise TransportError(
                f"Failed to create filter: {final_filter} for user {self.user_id}"
            ) from ex

        return filter_id