Skip to content

Commit f5a995d

Browse files
mbeijendrshvik
andcommitted
Propagate timeout through SOCKS5 handshake
During SOCKS5 connection setup, all reads and writes to the proxy socket were called without a timeout. A non-responsive proxy would block the calling thread/task indefinitely regardless of any `timeout` configured on the request. Thread the `connect` timeout (already used for the initial TCP connect) through `_init_socks5_connection()` to every `stream.read()` and `stream.write()` call. Ported from encode/httpcore#1055 (drshvik), via codeberg.org/httpxyz/httpcorexyz@0387930. Co-Authored-By: drshvik <ai23btech11004@iith.ac.in>
1 parent 04f3804 commit f5a995d

2 files changed

Lines changed: 16 additions & 12 deletions

File tree

‎src/httpcore2/httpcore2/_async/socks_proxy.py‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ async def _init_socks5_connection(
4545
host: bytes,
4646
port: int,
4747
auth: tuple[bytes, bytes] | None = None,
48+
timeout: float | None = None,
4849
) -> None:
4950
conn = socksio.socks5.SOCKS5Connection()
5051

@@ -56,10 +57,10 @@ async def _init_socks5_connection(
5657
)
5758
conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
5859
outgoing_bytes = conn.data_to_send()
59-
await stream.write(outgoing_bytes)
60+
await stream.write(outgoing_bytes, timeout=timeout)
6061

6162
# Auth method response
62-
incoming_bytes = await stream.read(max_bytes=4096)
63+
incoming_bytes = await stream.read(max_bytes=4096, timeout=timeout)
6364
response = conn.receive_data(incoming_bytes)
6465
assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
6566
if response.method != auth_method:
@@ -73,10 +74,10 @@ async def _init_socks5_connection(
7374
username, password = auth
7475
conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
7576
outgoing_bytes = conn.data_to_send()
76-
await stream.write(outgoing_bytes)
77+
await stream.write(outgoing_bytes, timeout=timeout)
7778

7879
# Username/password response
79-
incoming_bytes = await stream.read(max_bytes=4096)
80+
incoming_bytes = await stream.read(max_bytes=4096, timeout=timeout)
8081
response = conn.receive_data(incoming_bytes)
8182
assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
8283
if not response.success:
@@ -85,10 +86,10 @@ async def _init_socks5_connection(
8586
# Connect request
8687
conn.send(socksio.socks5.SOCKS5CommandRequest.from_address(socksio.socks5.SOCKS5Command.CONNECT, (host, port)))
8788
outgoing_bytes = conn.data_to_send()
88-
await stream.write(outgoing_bytes)
89+
await stream.write(outgoing_bytes, timeout=timeout)
8990

9091
# Connect response
91-
incoming_bytes = await stream.read(max_bytes=4096)
92+
incoming_bytes = await stream.read(max_bytes=4096, timeout=timeout)
9293
response = conn.receive_data(incoming_bytes)
9394
assert isinstance(response, socksio.socks5.SOCKS5Reply)
9495
if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
@@ -229,6 +230,7 @@ async def handle_async_request(self, request: Request) -> Response:
229230
"host": self._remote_origin.host.decode("ascii"),
230231
"port": self._remote_origin.port,
231232
"auth": self._proxy_auth,
233+
"timeout": timeout,
232234
}
233235
async with Trace("setup_socks5_connection", logger, request, kwargs) as trace:
234236
await _init_socks5_connection(**kwargs)

‎src/httpcore2/httpcore2/_sync/socks_proxy.py‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def _init_socks5_connection(
4545
host: bytes,
4646
port: int,
4747
auth: tuple[bytes, bytes] | None = None,
48+
timeout: float | None = None,
4849
) -> None:
4950
conn = socksio.socks5.SOCKS5Connection()
5051

@@ -56,10 +57,10 @@ def _init_socks5_connection(
5657
)
5758
conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
5859
outgoing_bytes = conn.data_to_send()
59-
stream.write(outgoing_bytes)
60+
stream.write(outgoing_bytes, timeout=timeout)
6061

6162
# Auth method response
62-
incoming_bytes = stream.read(max_bytes=4096)
63+
incoming_bytes = stream.read(max_bytes=4096, timeout=timeout)
6364
response = conn.receive_data(incoming_bytes)
6465
assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
6566
if response.method != auth_method:
@@ -73,10 +74,10 @@ def _init_socks5_connection(
7374
username, password = auth
7475
conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
7576
outgoing_bytes = conn.data_to_send()
76-
stream.write(outgoing_bytes)
77+
stream.write(outgoing_bytes, timeout=timeout)
7778

7879
# Username/password response
79-
incoming_bytes = stream.read(max_bytes=4096)
80+
incoming_bytes = stream.read(max_bytes=4096, timeout=timeout)
8081
response = conn.receive_data(incoming_bytes)
8182
assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
8283
if not response.success:
@@ -85,10 +86,10 @@ def _init_socks5_connection(
8586
# Connect request
8687
conn.send(socksio.socks5.SOCKS5CommandRequest.from_address(socksio.socks5.SOCKS5Command.CONNECT, (host, port)))
8788
outgoing_bytes = conn.data_to_send()
88-
stream.write(outgoing_bytes)
89+
stream.write(outgoing_bytes, timeout=timeout)
8990

9091
# Connect response
91-
incoming_bytes = stream.read(max_bytes=4096)
92+
incoming_bytes = stream.read(max_bytes=4096, timeout=timeout)
9293
response = conn.receive_data(incoming_bytes)
9394
assert isinstance(response, socksio.socks5.SOCKS5Reply)
9495
if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
@@ -229,6 +230,7 @@ def handle_request(self, request: Request) -> Response:
229230
"host": self._remote_origin.host.decode("ascii"),
230231
"port": self._remote_origin.port,
231232
"auth": self._proxy_auth,
233+
"timeout": timeout,
232234
}
233235
with Trace("setup_socks5_connection", logger, request, kwargs) as trace:
234236
_init_socks5_connection(**kwargs)

0 commit comments

Comments
 (0)