Jellyfin(8096), OrbStack(8097) 포트 충돌으로 변경. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
174 lines
5.4 KiB
Python
174 lines
5.4 KiB
Python
"""
|
|
This is hazmat. It can blow up anytime.
|
|
Use it with precautions!
|
|
|
|
Reasoning behind this:
|
|
|
|
1) python-socks requires another dependency, namely asyncio-timeout, that is one too much for us.
|
|
2) it does not support our AsyncSocket wrapper (it has his own internally)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import socket
|
|
import typing
|
|
import warnings
|
|
|
|
from python_socks import _abc as abc
|
|
|
|
# look the other way if unpleasant. No choice for now.
|
|
# will start discussions once we have a solid traffic.
|
|
from python_socks._connectors.abc import AsyncConnector
|
|
from python_socks._connectors.socks4_async import Socks4AsyncConnector
|
|
from python_socks._connectors.socks5_async import Socks5AsyncConnector
|
|
from python_socks._errors import ProxyError, ProxyTimeoutError
|
|
from python_socks._helpers import parse_proxy_url
|
|
from python_socks._protocols.errors import ReplyError
|
|
from python_socks._types import ProxyType
|
|
|
|
from .ssa import AsyncSocket
|
|
from .ssa._timeout import timeout as timeout_
|
|
|
|
|
|
class Resolver(abc.AsyncResolver):
|
|
def __init__(self, loop: asyncio.AbstractEventLoop):
|
|
self._loop = loop
|
|
|
|
async def resolve(
|
|
self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_UNSPEC
|
|
) -> tuple[socket.AddressFamily, str]:
|
|
infos = await self._loop.getaddrinfo(
|
|
host=host,
|
|
port=port,
|
|
family=family,
|
|
type=socket.SOCK_STREAM,
|
|
)
|
|
|
|
if not infos: # Defensive:
|
|
raise OSError(f"Can`t resolve address {host}:{port} [{family}]")
|
|
|
|
infos = sorted(infos, key=lambda info: info[0])
|
|
|
|
family, _, _, _, address = infos[0]
|
|
return family, address[0]
|
|
|
|
|
|
def create_connector(
|
|
proxy_type: ProxyType,
|
|
username: str | None,
|
|
password: str | None,
|
|
rdns: bool,
|
|
resolver: abc.AsyncResolver,
|
|
) -> AsyncConnector:
|
|
if proxy_type == ProxyType.SOCKS4:
|
|
return Socks4AsyncConnector(
|
|
user_id=username,
|
|
rdns=rdns,
|
|
resolver=resolver,
|
|
)
|
|
|
|
if proxy_type == ProxyType.SOCKS5:
|
|
return Socks5AsyncConnector(
|
|
username=username,
|
|
password=password,
|
|
rdns=rdns,
|
|
resolver=resolver,
|
|
)
|
|
|
|
raise ValueError(f"Invalid proxy type: {proxy_type}")
|
|
|
|
|
|
class AsyncioProxy:
|
|
def __init__(
|
|
self,
|
|
proxy_type: ProxyType,
|
|
host: str,
|
|
port: int,
|
|
username: str | None = None,
|
|
password: str | None = None,
|
|
rdns: bool = False,
|
|
):
|
|
self._loop = asyncio.get_event_loop()
|
|
|
|
self._proxy_type = proxy_type
|
|
self._proxy_host = host
|
|
self._proxy_port = port
|
|
self._password = password
|
|
self._username = username
|
|
self._rdns = rdns
|
|
|
|
self._resolver = Resolver(loop=self._loop)
|
|
|
|
async def connect(
|
|
self,
|
|
dest_host: str,
|
|
dest_port: int,
|
|
timeout: float | None = None,
|
|
_socket: AsyncSocket | None = None,
|
|
) -> AsyncSocket:
|
|
if timeout is None:
|
|
timeout = 60
|
|
|
|
try:
|
|
async with timeout_(timeout):
|
|
# our dependency started to deprecate passing "_socket"
|
|
# which is ... vital for our integration. We'll start by silencing the warning.
|
|
# then we'll think on how to proceed.
|
|
# A) the maintainer agrees to revert https://github.com/romis2012/python-socks/commit/173a7390469c06aa033f8dca67c827854b462bc3#diff-e4086fa970d1c98b1eb341e58cb70e9ceffe7391b2feecc4b66c7e92ea2de76fR64
|
|
# B) the maintainer pursue the removal -> do we vendor our copy of python-socks? is there an alternative?
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("ignore", DeprecationWarning)
|
|
return await self._connect(
|
|
dest_host=dest_host,
|
|
dest_port=dest_port,
|
|
_socket=_socket, # type: ignore[arg-type]
|
|
)
|
|
except asyncio.TimeoutError as e:
|
|
raise ProxyTimeoutError(f"Proxy connection timed out: {timeout}") from e
|
|
|
|
async def _connect(
|
|
self, dest_host: str, dest_port: int, _socket: AsyncSocket
|
|
) -> AsyncSocket:
|
|
try:
|
|
connector = create_connector(
|
|
proxy_type=self._proxy_type,
|
|
username=self._username,
|
|
password=self._password,
|
|
rdns=self._rdns,
|
|
resolver=self._resolver,
|
|
)
|
|
await connector.connect(
|
|
stream=_socket, # type: ignore[arg-type]
|
|
host=dest_host,
|
|
port=dest_port,
|
|
)
|
|
|
|
return _socket
|
|
except asyncio.CancelledError: # Defensive:
|
|
_socket.close()
|
|
raise
|
|
except ReplyError as e:
|
|
_socket.close()
|
|
raise ProxyError(e, error_code=e.error_code) # type: ignore[no-untyped-call]
|
|
except Exception: # Defensive:
|
|
_socket.close()
|
|
raise
|
|
|
|
@property
|
|
def proxy_host(self) -> str:
|
|
return self._proxy_host
|
|
|
|
@property
|
|
def proxy_port(self) -> int:
|
|
return self._proxy_port
|
|
|
|
@classmethod
|
|
def create(cls, *args: typing.Any, **kwargs: typing.Any) -> AsyncioProxy:
|
|
return cls(*args, **kwargs)
|
|
|
|
@classmethod
|
|
def from_url(cls, url: str, **kwargs: typing.Any) -> AsyncioProxy:
|
|
url_args = parse_proxy_url(url)
|
|
return cls(*url_args, **kwargs)
|