fix(pymsg): add recv_timeout to MsgServer to prevent Slowloris DoS
- Add configurable recv_timeout (default 10s) to MsgServer - _handle_client now uses recv(s, timeout=...) instead of infinite blocking recv(s) - Catch TimeoutError and close the slow client connection - This prevents a single stalled connection from blocking the entire main select loop - Update README with recv_timeout parameter and Slowloris mitigation docs - Backward compatible: keyword-only arg with sensible default
This commit is contained in:
1 parent
ae42ab8a91
commit
9708fbc87e
2 files changed
+11
-3
No files matched your search
@@ -142,6 +142,7 @@ server.start(on_message) # 阻塞当前线程
|
|||||||
| `addr` | 监听地址 |
|
| `addr` | 监听地址 |
|
||||||
| `port` | 监听端口 |
|
| `port` | 监听端口 |
|
||||||
| `workers` | 工作线程数(关键字参数,默认 `0`);`>0` 时回调在线程池中并发执行 |
|
| `workers` | 工作线程数(关键字参数,默认 `0`);`>0` 时回调在线程池中并发执行 |
|
||||||
|
| `recv_timeout` | 接收单条客户端消息的超时秒数(关键字参数,默认 `10.0`);超时未收到完整消息则主动断开连接,防御 Slowloris 式半开连接攻击 |
|
||||||
|
|
||||||
回调签名为 `on_message(sock, addr, text)`:
|
回调签名为 `on_message(sock, addr, text)`:
|
||||||
- `sock` — 客户端 socket,可用 `reply()` 回复
|
- `sock` — 客户端 socket,可用 `reply()` 回复
|
||||||
@@ -160,6 +161,8 @@ server.start(on_message) # 阻塞当前线程
|
|||||||
|
|
||||||
服务端内部管理客户端列表,透明处理断连和管道破裂,使用 `select` 实现非阻塞 I/O,轮询间隔 100ms。
|
服务端内部管理客户端列表,透明处理断连和管道破裂,使用 `select` 实现非阻塞 I/O,轮询间隔 100ms。
|
||||||
|
|
||||||
|
**Slowloris 防护**:每个客户端连接在 `recv_timeout`(默认 10 秒)内必须发送完一条完整消息(4 字节头 + 载荷),超时未完成则服务端主动断开该连接,防止慢速发送攻击阻塞主事件循环。
|
||||||
|
|
||||||
**并发模式(v0.2.0)**:
|
**并发模式(v0.2.0)**:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|||||||
@@ -296,10 +296,14 @@ class MsgServer:
|
|||||||
经每连接发送锁串行化,不会发生帧交错。
|
经每连接发送锁串行化,不会发生帧交错。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, addr: str, port: int, *, workers: int = 0) -> None:
|
_RECV_TIMEOUT = 10.0 # 服务端接收单条消息的超时(秒),防止 Slowloris 式半开连接卡死主线程
|
||||||
|
|
||||||
|
def __init__(self, addr: str, port: int, *, workers: int = 0,
|
||||||
|
recv_timeout: float = _RECV_TIMEOUT) -> None:
|
||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.port = port
|
self.port = port
|
||||||
self._workers = workers
|
self._workers = workers
|
||||||
|
self._recv_timeout = recv_timeout
|
||||||
self._running = False
|
self._running = False
|
||||||
self._server_sock: "_socket.socket | None" = None
|
self._server_sock: "_socket.socket | None" = None
|
||||||
self._clients: "dict" = {} # socket -> (ip, port)
|
self._clients: "dict" = {} # socket -> (ip, port)
|
||||||
@@ -429,8 +433,9 @@ class MsgServer:
|
|||||||
|
|
||||||
def _handle_client(self, s, socketlist, on_message):
|
def _handle_client(self, s, socketlist, on_message):
|
||||||
try:
|
try:
|
||||||
text = recv(s)
|
text = recv(s, timeout=self._recv_timeout)
|
||||||
except (ConnectionError, ConnectionResetError, OSError):
|
except (ConnectionError, ConnectionResetError, OSError, TimeoutError):
|
||||||
|
# TimeoutError: 客户端在超时时间内未发送完整消息(Slowloris 防护),关闭连接
|
||||||
self._remove_client(s, socketlist)
|
self._remove_client(s, socketlist)
|
||||||
return
|
return
|
||||||
addr = self._clients.get(s, ("unknown", 0))
|
addr = self._clients.get(s, ("unknown", 0))
|
||||||
|
|||||||
Reference in new issue
Block a user