fix: 포트 충돌 회피 — note_bridge 8098, intent_service 8099
Jellyfin(8096), OrbStack(8097) 포트 충돌으로 변경. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,453 @@
|
||||
"""
|
||||
This module contains provisional support for SOCKS proxies from within
|
||||
urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and
|
||||
SOCKS5. To enable its functionality, either install python-socks or install this
|
||||
module with the ``socks`` extra.
|
||||
|
||||
The SOCKS implementation supports the full range of urllib3 features. It also
|
||||
supports the following SOCKS features:
|
||||
|
||||
- SOCKS4A (``proxy_url='socks4a://...``)
|
||||
- SOCKS4 (``proxy_url='socks4://...``)
|
||||
- SOCKS5 with remote DNS (``proxy_url='socks5h://...``)
|
||||
- SOCKS5 with local DNS (``proxy_url='socks5://...``)
|
||||
- Usernames and passwords for the SOCKS proxy
|
||||
|
||||
.. note::
|
||||
It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in
|
||||
your ``proxy_url`` to ensure that DNS resolution is done from the remote
|
||||
server instead of client-side when connecting to a domain name.
|
||||
|
||||
SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5
|
||||
supports IPv4, IPv6, and domain names.
|
||||
|
||||
When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url``
|
||||
will be sent as the ``userid`` section of the SOCKS request:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
proxy_url="socks4a://<userid>@proxy-host"
|
||||
|
||||
When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion
|
||||
of the ``proxy_url`` will be sent as the username/password to authenticate
|
||||
with the proxy:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
proxy_url="socks5h://<username>:<password>@proxy-host"
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
|
||||
#: We purposely want to support PySocks[...] due to our shadowing of the legacy "urllib3". "Dot not disturb" policy.
|
||||
BYPASS_SOCKS_LEGACY: bool = False
|
||||
|
||||
try:
|
||||
from python_socks import (
|
||||
ProxyConnectionError,
|
||||
ProxyError,
|
||||
ProxyTimeoutError,
|
||||
ProxyType,
|
||||
)
|
||||
from python_socks.sync import Proxy
|
||||
|
||||
from ._socks_override import AsyncioProxy
|
||||
except ImportError:
|
||||
from ..exceptions import DependencyWarning
|
||||
|
||||
try:
|
||||
import socks # noqa
|
||||
except ImportError:
|
||||
warnings.warn(
|
||||
(
|
||||
"SOCKS support in urllib3.future requires the installation of an optional "
|
||||
"dependency: python-socks. For more information, see "
|
||||
"https://urllib3future.readthedocs.io/en/latest/contrib.html#socks-proxies"
|
||||
),
|
||||
DependencyWarning,
|
||||
)
|
||||
else:
|
||||
from ._socks_legacy import (
|
||||
SOCKSConnection,
|
||||
SOCKSHTTPConnectionPool,
|
||||
SOCKSHTTPSConnection,
|
||||
SOCKSHTTPSConnectionPool,
|
||||
SOCKSProxyManager,
|
||||
)
|
||||
|
||||
BYPASS_SOCKS_LEGACY = True
|
||||
|
||||
if not BYPASS_SOCKS_LEGACY:
|
||||
raise
|
||||
|
||||
if not BYPASS_SOCKS_LEGACY:
|
||||
import typing
|
||||
from socket import socket
|
||||
from socket import timeout as SocketTimeout
|
||||
|
||||
# asynchronous part
|
||||
from .._async.connection import AsyncHTTPConnection, AsyncHTTPSConnection
|
||||
from .._async.connectionpool import (
|
||||
AsyncHTTPConnectionPool,
|
||||
AsyncHTTPSConnectionPool,
|
||||
)
|
||||
from .._async.poolmanager import AsyncPoolManager
|
||||
from .._typing import _TYPE_SOCKS_OPTIONS
|
||||
from ..backend import HttpVersion
|
||||
|
||||
# synchronous part
|
||||
from ..connection import HTTPConnection, HTTPSConnection
|
||||
from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||
from ..contrib.ssa import AsyncSocket
|
||||
from ..exceptions import ConnectTimeoutError, NewConnectionError
|
||||
from ..poolmanager import PoolManager
|
||||
from ..util.url import parse_url
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
ssl = None # type: ignore[assignment]
|
||||
|
||||
class SOCKSConnection(HTTPConnection): # type: ignore[no-redef]
|
||||
"""
|
||||
A plain-text HTTP connection that connects via a SOCKS proxy.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
_socks_options: _TYPE_SOCKS_OPTIONS,
|
||||
*args: typing.Any,
|
||||
**kwargs: typing.Any,
|
||||
) -> None:
|
||||
self._socks_options = _socks_options
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _new_conn(self) -> socket:
|
||||
"""
|
||||
Establish a new connection via the SOCKS proxy.
|
||||
"""
|
||||
extra_kw: dict[str, typing.Any] = {}
|
||||
if self.source_address:
|
||||
extra_kw["source_address"] = self.source_address
|
||||
|
||||
if self.socket_options:
|
||||
only_tcp_options = []
|
||||
|
||||
for opt in self.socket_options:
|
||||
if len(opt) == 3:
|
||||
only_tcp_options.append(opt)
|
||||
elif len(opt) == 4:
|
||||
protocol: str = opt[3].lower()
|
||||
if protocol == "udp":
|
||||
continue
|
||||
only_tcp_options.append(opt[:3])
|
||||
|
||||
extra_kw["socket_options"] = only_tcp_options
|
||||
|
||||
try:
|
||||
assert self._socks_options["proxy_host"] is not None
|
||||
assert self._socks_options["proxy_port"] is not None
|
||||
|
||||
p = Proxy(
|
||||
proxy_type=self._socks_options["socks_version"], # type: ignore[arg-type]
|
||||
host=self._socks_options["proxy_host"],
|
||||
port=int(self._socks_options["proxy_port"]),
|
||||
username=self._socks_options["username"],
|
||||
password=self._socks_options["password"],
|
||||
rdns=self._socks_options["rdns"],
|
||||
)
|
||||
|
||||
_socket = self._resolver.create_connection(
|
||||
(
|
||||
self._socks_options["proxy_host"],
|
||||
int(self._socks_options["proxy_port"]),
|
||||
),
|
||||
timeout=self.timeout,
|
||||
source_address=self.source_address,
|
||||
socket_options=extra_kw["socket_options"],
|
||||
quic_upgrade_via_dns_rr=False,
|
||||
timing_hook=lambda _: setattr(self, "_connect_timings", _),
|
||||
)
|
||||
|
||||
# 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 p.connect(
|
||||
self.host,
|
||||
self.port,
|
||||
self.timeout,
|
||||
_socket=_socket,
|
||||
)
|
||||
except (SocketTimeout, ProxyTimeoutError) as e:
|
||||
raise ConnectTimeoutError(
|
||||
self,
|
||||
f"Connection to {self.host} timed out. (connect timeout={self.timeout})",
|
||||
) from e
|
||||
|
||||
except (ProxyConnectionError, ProxyError) as e:
|
||||
raise NewConnectionError(
|
||||
self, f"Failed to establish a new connection: {e}"
|
||||
) from e
|
||||
|
||||
except OSError as e: # Defensive: PySocks should catch all these.
|
||||
raise NewConnectionError(
|
||||
self, f"Failed to establish a new connection: {e}"
|
||||
) from e
|
||||
|
||||
# We don't need to duplicate the Verified/Unverified distinction from
|
||||
# urllib3/connection.py here because the HTTPSConnection will already have been
|
||||
# correctly set to either the Verified or Unverified form by that module. This
|
||||
# means the SOCKSHTTPSConnection will automatically be the correct type.
|
||||
class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): # type: ignore[no-redef]
|
||||
pass
|
||||
|
||||
class SOCKSHTTPConnectionPool(HTTPConnectionPool): # type: ignore[no-redef]
|
||||
ConnectionCls = SOCKSConnection
|
||||
|
||||
class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): # type: ignore[no-redef]
|
||||
ConnectionCls = SOCKSHTTPSConnection
|
||||
|
||||
class SOCKSProxyManager(PoolManager): # type: ignore[no-redef]
|
||||
"""
|
||||
A version of the urllib3 ProxyManager that routes connections via the
|
||||
defined SOCKS proxy.
|
||||
"""
|
||||
|
||||
pool_classes_by_scheme = {
|
||||
"http": SOCKSHTTPConnectionPool,
|
||||
"https": SOCKSHTTPSConnectionPool,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
proxy_url: str,
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
num_pools: int = 10,
|
||||
headers: typing.Mapping[str, str] | None = None,
|
||||
**connection_pool_kw: typing.Any,
|
||||
):
|
||||
parsed = parse_url(proxy_url)
|
||||
|
||||
if username is None and password is None and parsed.auth is not None:
|
||||
split = parsed.auth.split(":")
|
||||
if len(split) == 2:
|
||||
username, password = split
|
||||
if parsed.scheme == "socks5":
|
||||
socks_version = ProxyType.SOCKS5
|
||||
rdns = False
|
||||
elif parsed.scheme == "socks5h":
|
||||
socks_version = ProxyType.SOCKS5
|
||||
rdns = True
|
||||
elif parsed.scheme == "socks4":
|
||||
socks_version = ProxyType.SOCKS4
|
||||
rdns = False
|
||||
elif parsed.scheme == "socks4a":
|
||||
socks_version = ProxyType.SOCKS4
|
||||
rdns = True
|
||||
else:
|
||||
raise ValueError(f"Unable to determine SOCKS version from {proxy_url}")
|
||||
|
||||
self.proxy_url = proxy_url
|
||||
|
||||
socks_options = {
|
||||
"socks_version": socks_version,
|
||||
"proxy_host": parsed.host,
|
||||
"proxy_port": parsed.port,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"rdns": rdns,
|
||||
}
|
||||
connection_pool_kw["_socks_options"] = socks_options
|
||||
|
||||
if "disabled_svn" not in connection_pool_kw:
|
||||
connection_pool_kw["disabled_svn"] = set()
|
||||
|
||||
connection_pool_kw["disabled_svn"].add(HttpVersion.h3)
|
||||
|
||||
super().__init__(num_pools, headers, **connection_pool_kw)
|
||||
|
||||
self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme
|
||||
|
||||
class AsyncSOCKSConnection(AsyncHTTPConnection):
|
||||
"""
|
||||
A plain-text HTTP connection that connects via a SOCKS proxy.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
_socks_options: _TYPE_SOCKS_OPTIONS,
|
||||
*args: typing.Any,
|
||||
**kwargs: typing.Any,
|
||||
) -> None:
|
||||
self._socks_options = _socks_options
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def _new_conn(self) -> AsyncSocket: # type: ignore[override]
|
||||
"""
|
||||
Establish a new connection via the SOCKS proxy.
|
||||
"""
|
||||
extra_kw: dict[str, typing.Any] = {}
|
||||
if self.source_address:
|
||||
extra_kw["source_address"] = self.source_address
|
||||
|
||||
if self.socket_options:
|
||||
only_tcp_options = []
|
||||
|
||||
for opt in self.socket_options:
|
||||
if len(opt) == 3:
|
||||
only_tcp_options.append(opt)
|
||||
elif len(opt) == 4:
|
||||
protocol: str = opt[3].lower()
|
||||
if protocol == "udp":
|
||||
continue
|
||||
only_tcp_options.append(opt[:3])
|
||||
|
||||
extra_kw["socket_options"] = only_tcp_options
|
||||
|
||||
try:
|
||||
assert self._socks_options["proxy_host"] is not None
|
||||
assert self._socks_options["proxy_port"] is not None
|
||||
|
||||
p = AsyncioProxy(
|
||||
proxy_type=self._socks_options["socks_version"], # type: ignore[arg-type]
|
||||
host=self._socks_options["proxy_host"],
|
||||
port=int(self._socks_options["proxy_port"]),
|
||||
username=self._socks_options["username"],
|
||||
password=self._socks_options["password"],
|
||||
rdns=self._socks_options["rdns"],
|
||||
)
|
||||
|
||||
_socket = await self._resolver.create_connection(
|
||||
(
|
||||
self._socks_options["proxy_host"],
|
||||
int(self._socks_options["proxy_port"]),
|
||||
),
|
||||
timeout=self.timeout,
|
||||
source_address=self.source_address,
|
||||
socket_options=extra_kw["socket_options"],
|
||||
quic_upgrade_via_dns_rr=False,
|
||||
timing_hook=lambda _: setattr(self, "_connect_timings", _),
|
||||
)
|
||||
|
||||
return await p.connect(
|
||||
self.host,
|
||||
self.port,
|
||||
self.timeout,
|
||||
_socket,
|
||||
)
|
||||
except (SocketTimeout, ProxyTimeoutError) as e:
|
||||
raise ConnectTimeoutError(
|
||||
self,
|
||||
f"Connection to {self.host} timed out. (connect timeout={self.timeout})",
|
||||
) from e
|
||||
|
||||
except (ProxyConnectionError, ProxyError) as e:
|
||||
raise NewConnectionError(
|
||||
self, f"Failed to establish a new connection: {e}"
|
||||
) from e
|
||||
|
||||
except OSError as e: # Defensive: PySocks should catch all these.
|
||||
raise NewConnectionError(
|
||||
self, f"Failed to establish a new connection: {e}"
|
||||
) from e
|
||||
|
||||
# We don't need to duplicate the Verified/Unverified distinction from
|
||||
# urllib3/connection.py here because the HTTPSConnection will already have been
|
||||
# correctly set to either the Verified or Unverified form by that module. This
|
||||
# means the SOCKSHTTPSConnection will automatically be the correct type.
|
||||
class AsyncSOCKSHTTPSConnection(AsyncSOCKSConnection, AsyncHTTPSConnection):
|
||||
pass
|
||||
|
||||
class AsyncSOCKSHTTPConnectionPool(AsyncHTTPConnectionPool):
|
||||
ConnectionCls = AsyncSOCKSConnection
|
||||
|
||||
class AsyncSOCKSHTTPSConnectionPool(AsyncHTTPSConnectionPool):
|
||||
ConnectionCls = AsyncSOCKSHTTPSConnection
|
||||
|
||||
class AsyncSOCKSProxyManager(AsyncPoolManager):
|
||||
"""
|
||||
A version of the urllib3 ProxyManager that routes connections via the
|
||||
defined SOCKS proxy.
|
||||
"""
|
||||
|
||||
pool_classes_by_scheme = {
|
||||
"http": AsyncSOCKSHTTPConnectionPool,
|
||||
"https": AsyncSOCKSHTTPSConnectionPool,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
proxy_url: str,
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
num_pools: int = 10,
|
||||
headers: typing.Mapping[str, str] | None = None,
|
||||
**connection_pool_kw: typing.Any,
|
||||
):
|
||||
parsed = parse_url(proxy_url)
|
||||
|
||||
if username is None and password is None and parsed.auth is not None:
|
||||
split = parsed.auth.split(":")
|
||||
if len(split) == 2:
|
||||
username, password = split
|
||||
if parsed.scheme == "socks5":
|
||||
socks_version = ProxyType.SOCKS5
|
||||
rdns = False
|
||||
elif parsed.scheme == "socks5h":
|
||||
socks_version = ProxyType.SOCKS5
|
||||
rdns = True
|
||||
elif parsed.scheme == "socks4":
|
||||
socks_version = ProxyType.SOCKS4
|
||||
rdns = False
|
||||
elif parsed.scheme == "socks4a":
|
||||
socks_version = ProxyType.SOCKS4
|
||||
rdns = True
|
||||
else:
|
||||
raise ValueError(f"Unable to determine SOCKS version from {proxy_url}")
|
||||
|
||||
self.proxy_url = proxy_url
|
||||
|
||||
socks_options = {
|
||||
"socks_version": socks_version,
|
||||
"proxy_host": parsed.host,
|
||||
"proxy_port": parsed.port,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"rdns": rdns,
|
||||
}
|
||||
connection_pool_kw["_socks_options"] = socks_options
|
||||
|
||||
if "disabled_svn" not in connection_pool_kw:
|
||||
connection_pool_kw["disabled_svn"] = set()
|
||||
|
||||
connection_pool_kw["disabled_svn"].add(HttpVersion.h3)
|
||||
|
||||
super().__init__(num_pools, headers, **connection_pool_kw)
|
||||
|
||||
self.pool_classes_by_scheme = AsyncSOCKSProxyManager.pool_classes_by_scheme
|
||||
|
||||
|
||||
__all__ = [
|
||||
"SOCKSConnection",
|
||||
"SOCKSProxyManager",
|
||||
"SOCKSHTTPSConnection",
|
||||
"SOCKSHTTPSConnectionPool",
|
||||
"SOCKSHTTPConnectionPool",
|
||||
]
|
||||
|
||||
if not BYPASS_SOCKS_LEGACY:
|
||||
__all__ += [
|
||||
"AsyncSOCKSConnection",
|
||||
"AsyncSOCKSHTTPSConnection",
|
||||
"AsyncSOCKSHTTPConnectionPool",
|
||||
"AsyncSOCKSHTTPSConnectionPool",
|
||||
"AsyncSOCKSProxyManager",
|
||||
]
|
||||
Reference in New Issue
Block a user