mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-07-30 17:59:55 +03:00
Replace PySocks with aiosocks due to bugs (see #1192)
related to blocking behaviour of PySocks on socket connection. Also small fixes and additional checks on local_addr parameter. Updated documentation.
This commit is contained in:
parent
9c833b601a
commit
e4eaecb1cd
|
@ -1,4 +1,3 @@
|
|||
cryptg
|
||||
pysocks
|
||||
hachoir
|
||||
pillow
|
||||
|
|
|
@ -117,7 +117,7 @@ Signing In behind a Proxy
|
|||
=========================
|
||||
|
||||
If you need to use a proxy to access Telegram,
|
||||
you will need to `install PySocks`__ and then change:
|
||||
you will need to `install aiosocks`__ and then change:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -127,15 +127,36 @@ with
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
TelegramClient('anon', api_id, api_hash, proxy=(socks.SOCKS5, '127.0.0.1', 4444))
|
||||
TelegramClient('anon', api_id, api_hash, proxy=("socks5", '127.0.0.1', 4444))
|
||||
|
||||
(of course, replacing the IP and port with the IP and port of the proxy).
|
||||
|
||||
The ``proxy=`` argument should be a tuple, a list or a dict,
|
||||
consisting of parameters described `in PySocks usage`__.
|
||||
consisting of parameters described `in aiosocks usage`__.
|
||||
|
||||
.. __: https://github.com/Anorov/PySocks#installation
|
||||
.. __: https://github.com/Anorov/PySocks#usage-1
|
||||
Example:
|
||||
|
||||
* .. code-block:: python
|
||||
|
||||
proxy = ('socks5', '1.1.1.1', 5555, 'foo', 'bar', True)
|
||||
|
||||
* .. code-block:: python
|
||||
|
||||
proxy = ['socks5', '1.1.1.1', 5555, 'foo', 'bar', True]
|
||||
|
||||
* .. code-block:: python
|
||||
|
||||
proxy = {
|
||||
'protocol': 'socks5', # (mandatory) protocol to use, default socks5, allowed values: 'socks5' (or 2), 'socks4' (or 1)
|
||||
'host': '1.1.1.1', # (mandatory) proxy IP address
|
||||
'port': 5555, # (mandatory) proxy port number
|
||||
'username': 'foo', # (optional) username if the proxy requires auth
|
||||
'password': 'bar', # (optional) password if the proxy requires auth
|
||||
'remote_resolve': True # (optional) whether to use remote or local resolve, default remote
|
||||
}
|
||||
|
||||
.. __: https://github.com/nibrag/aiosocks#installation
|
||||
.. __: https://github.com/nibrag/aiosocks#usage
|
||||
|
||||
|
||||
Using MTProto Proxies
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
aiosocks
|
||||
pyaes
|
||||
rsa
|
||||
|
|
|
@ -107,11 +107,11 @@ class TelegramBaseClient(abc.ABC):
|
|||
An iterable consisting of the proxy info. If `connection` is
|
||||
one of `MTProxy`, then it should contain MTProxy credentials:
|
||||
``('hostname', port, 'secret')``. Otherwise, it's meant to store
|
||||
function parameters for PySocks, like ``(type, 'hostname', port)``.
|
||||
See https://github.com/Anorov/PySocks#usage-1 for more.
|
||||
function parameters for AioSocks, like ``(type, 'hostname', port)``.
|
||||
See https://github.com/nibrag/aiosocks for more.
|
||||
|
||||
local_addr (`str`, optional):
|
||||
Local host address used to bind the socket to locally.
|
||||
local_addr (`str` | `tuple`, optional):
|
||||
Local host address (and port, optionally) used to bind the socket to locally.
|
||||
You only need to use this if you have multiple network cards and
|
||||
want to use a specific one.
|
||||
|
||||
|
@ -220,10 +220,10 @@ class TelegramBaseClient(abc.ABC):
|
|||
connection: 'typing.Type[Connection]' = ConnectionTcpFull,
|
||||
use_ipv6: bool = False,
|
||||
proxy: typing.Union[tuple, dict] = None,
|
||||
local_addr=None,
|
||||
local_addr: typing.Union[tuple, str] = None,
|
||||
timeout: int = 10,
|
||||
request_retries: int = 5,
|
||||
connection_retries: int =5,
|
||||
connection_retries: int = 5,
|
||||
retry_delay: int = 1,
|
||||
auto_reconnect: bool = True,
|
||||
sequential_updates: bool = False,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import abc
|
||||
import asyncio
|
||||
import socket
|
||||
import sys
|
||||
|
||||
try:
|
||||
|
@ -11,6 +10,20 @@ except ImportError:
|
|||
from ...errors import InvalidChecksumError
|
||||
from ... import helpers
|
||||
|
||||
import aiosocks
|
||||
|
||||
# For some reason, `aiosocks` internal errors are not inherited from
|
||||
# builtin IOError (just from Exception). Instead of adding those
|
||||
# in exceptions clauses everywhere through the code, we
|
||||
# rather monkey-patch them in place.
|
||||
|
||||
aiosocks.errors.SocksError = ConnectionError
|
||||
aiosocks.errors.NoAcceptableAuthMethods = ConnectionError
|
||||
aiosocks.errors.LoginAuthenticationFailed = ConnectionError
|
||||
aiosocks.errors.InvalidServerVersion = ConnectionError
|
||||
aiosocks.errors.InvalidServerReply = ConnectionError
|
||||
aiosocks.errors.SocksConnectionError = ConnectionError
|
||||
|
||||
|
||||
class Connection(abc.ABC):
|
||||
"""
|
||||
|
@ -46,58 +59,76 @@ class Connection(abc.ABC):
|
|||
self._recv_queue = asyncio.Queue(1)
|
||||
|
||||
async def _connect(self, timeout=None, ssl=None):
|
||||
if not self._proxy:
|
||||
if self._local_addr is not None:
|
||||
local_addr = (self._local_addr, None)
|
||||
else:
|
||||
local_addr = None
|
||||
|
||||
self._reader, self._writer = await asyncio.wait_for(
|
||||
asyncio.open_connection(self._ip, self._port, ssl=ssl, local_addr=local_addr),
|
||||
timeout=timeout
|
||||
)
|
||||
if self._local_addr is not None:
|
||||
|
||||
# NOTE: If port is not specified, we use 0 port
|
||||
# to notify the OS that port should be chosen randomly
|
||||
# from the available ones.
|
||||
|
||||
if isinstance(self._local_addr, tuple) and len(self._local_addr) == 2:
|
||||
local_addr = self._local_addr
|
||||
elif isinstance(self._local_addr, str):
|
||||
local_addr = (self._local_addr, 0)
|
||||
else:
|
||||
raise ValueError("Unknown local address format: {}".format(self._local_addr))
|
||||
else:
|
||||
import socks
|
||||
if ':' in self._ip:
|
||||
mode, address = socket.AF_INET6, (self._ip, self._port, 0, 0)
|
||||
local_addr = None
|
||||
|
||||
if not self._proxy:
|
||||
connect_coroutine = asyncio.open_connection(
|
||||
host=self._ip,
|
||||
port=self._port,
|
||||
ssl=ssl,
|
||||
local_addr=local_addr)
|
||||
else:
|
||||
|
||||
if isinstance(self._proxy, (tuple, list)):
|
||||
proxy, auth, remote_resolve = self._parse_proxy(*self._proxy)
|
||||
elif isinstance(self._proxy, dict):
|
||||
proxy, auth, remote_resolve = self._parse_proxy(**self._proxy)
|
||||
else:
|
||||
mode, address = socket.AF_INET, (self._ip, self._port)
|
||||
raise ValueError("Unknown proxy format: {}".format(self._proxy.__class__.__name__))
|
||||
|
||||
s = socks.socksocket(mode, socket.SOCK_STREAM)
|
||||
if isinstance(self._proxy, dict):
|
||||
s.set_proxy(**self._proxy)
|
||||
else:
|
||||
s.set_proxy(*self._proxy)
|
||||
|
||||
s.settimeout(timeout)
|
||||
if self._local_addr is not None:
|
||||
s.bind((self._local_addr, None))
|
||||
await asyncio.wait_for(
|
||||
asyncio.get_event_loop().sock_connect(s, address),
|
||||
timeout=timeout
|
||||
)
|
||||
if ssl:
|
||||
if ssl_mod is None:
|
||||
raise RuntimeError(
|
||||
'Cannot use proxy that requires SSL'
|
||||
'without the SSL module being available'
|
||||
)
|
||||
|
||||
s = ssl_mod.wrap_socket(
|
||||
s,
|
||||
do_handshake_on_connect=True,
|
||||
ssl_version=ssl_mod.PROTOCOL_SSLv23,
|
||||
ciphers='ADH-AES256-SHA'
|
||||
)
|
||||
|
||||
s.setblocking(False)
|
||||
|
||||
self._reader, self._writer = await asyncio.open_connection(sock=s)
|
||||
connect_coroutine = aiosocks.open_connection(
|
||||
proxy=proxy,
|
||||
proxy_auth=auth,
|
||||
dst=(self._ip, self._port),
|
||||
remote_resolve=remote_resolve,
|
||||
ssl=ssl,
|
||||
local_addr=local_addr)
|
||||
|
||||
self._reader, self._writer = await asyncio.wait_for(connect_coroutine, timeout=timeout)
|
||||
self._codec = self.packet_codec(self)
|
||||
self._init_conn()
|
||||
await self._writer.drain()
|
||||
|
||||
@staticmethod
|
||||
def _parse_proxy(protocol, host, port, username=None, password=None, remote_resolve=True):
|
||||
|
||||
proxy, auth = None, None
|
||||
|
||||
if isinstance(protocol, str):
|
||||
protocol = protocol.lower()
|
||||
|
||||
# We do the check for numerical values here
|
||||
# to be backwards compatible with PySocks proxy format,
|
||||
# (since socks.SOCKS5 = 2 and socks.SOCKS4 = 1)
|
||||
|
||||
if protocol == 'socks5' or protocol == 2:
|
||||
proxy = aiosocks.Socks5Addr(host, port)
|
||||
if username and password:
|
||||
auth = aiosocks.Socks5Auth(username, password)
|
||||
|
||||
elif protocol == 'socks4' or protocol == 1:
|
||||
proxy = aiosocks.Socks4Addr(host, port)
|
||||
if username:
|
||||
auth = aiosocks.Socks4Auth(username)
|
||||
else:
|
||||
raise ValueError('Unsupported proxy protocol {}'.format(protocol))
|
||||
|
||||
return proxy, auth, remote_resolve
|
||||
|
||||
async def connect(self, timeout=None, ssl=None):
|
||||
"""
|
||||
Establishes a connection with the server.
|
||||
|
|
|
@ -22,7 +22,7 @@ def get_env(name, message, cast=str):
|
|||
session = os.environ.get('TG_SESSION', 'printer')
|
||||
api_id = get_env('TG_API_ID', 'Enter your API ID: ', int)
|
||||
api_hash = get_env('TG_API_HASH', 'Enter your API hash: ')
|
||||
proxy = None # https://github.com/Anorov/PySocks
|
||||
proxy = None # https://docs.telethon.dev/en/latest/basic/signing-in.html#signing-in-behind-a-proxy
|
||||
|
||||
# Create and start the client so we can make requests (we don't here)
|
||||
client = TelegramClient(session, api_id, api_hash, proxy=proxy).start()
|
||||
|
|
|
@ -27,7 +27,7 @@ def get_env(name, message, cast=str):
|
|||
session = os.environ.get('TG_SESSION', 'printer')
|
||||
api_id = get_env('TG_API_ID', 'Enter your API ID: ', int)
|
||||
api_hash = get_env('TG_API_HASH', 'Enter your API hash: ')
|
||||
proxy = None # https://github.com/Anorov/PySocks
|
||||
proxy = None # https://docs.telethon.dev/en/latest/basic/signing-in.html#signing-in-behind-a-proxy
|
||||
|
||||
|
||||
# This is our update handler. It is called when a new update arrives.
|
||||
|
|
Loading…
Reference in New Issue
Block a user