Skip to content

Commit 34a3fcd

Browse files
committed
fix(teacher): presend IPv4 command result before netsh; suppress benign TCP hangup errors on teacher
1 parent 5482158 commit 34a3fcd

3 files changed

Lines changed: 69 additions & 12 deletions

File tree

agent/main.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
start_clash_if_config_exists,
5252
)
5353

54-
AGENT_VERSION = "1.0.3"
54+
AGENT_VERSION = "1.0.0"
5555

5656

5757
def _debug(msg: str) -> None:
@@ -243,11 +243,32 @@ def _run() -> None:
243243

244244
threading.Thread(target=_run, daemon=True).start()
245245

246+
def _try_presend_ipv4_result(self, cmd_id: str) -> bool:
247+
"""Notify teacher before netsh; TCP often dies after IP change (WinError 10038)."""
248+
if not cmd_id:
249+
return False
250+
try:
251+
self._send(
252+
{
253+
"type": MSG_RESULT,
254+
"cmd_id": cmd_id,
255+
"ok": True,
256+
"message": (
257+
"正在通过 netsh 应用 IPv4;成功后本会话通常会断开并重连。"
258+
"若未再收到本条命令的第二条结果,一般以本机网络配置为准。"
259+
),
260+
}
261+
)
262+
return True
263+
except (ConnectionError, OSError, ValueError):
264+
return False
265+
246266
def _handle_command(self, cmd: Dict[str, Any]) -> None:
247267
cmd_type = get_message_type(cmd)
248268
cmd_id = str(cmd.get("cmd_id") or "")
249269
ok = False
250270
msg = "unsupported command"
271+
skip_final_result = False
251272

252273
if cmd_type == MSG_COMMAND_RENAME_HOST:
253274
new_name = str(cmd.get("new_hostname") or "").strip()
@@ -262,7 +283,12 @@ def _handle_command(self, cmd: Dict[str, Any]) -> None:
262283
else:
263284
mode = str(cmd.get("mode") or "").strip().lower()
264285
if mode == "dhcp":
286+
presend = (
287+
sys.platform == "win32" and self._try_presend_ipv4_result(cmd_id)
288+
)
265289
ok, msg = apply_ipv4_dhcp(name)
290+
if sys.platform == "win32" and ok and presend:
291+
skip_final_result = True
266292
elif mode == "static":
267293
ip = str(cmd.get("ip") or "").strip()
268294
mask = str(cmd.get("mask") or "").strip()
@@ -272,7 +298,13 @@ def _handle_command(self, cmd: Dict[str, Any]) -> None:
272298
if not ip or not mask:
273299
ok, msg = False, "missing ip/mask"
274300
else:
301+
presend = (
302+
sys.platform == "win32"
303+
and self._try_presend_ipv4_result(cmd_id)
304+
)
275305
ok, msg = apply_ipv4_static(name, ip, mask, gw, dns1, dns2)
306+
if sys.platform == "win32" and ok and presend:
307+
skip_final_result = True
276308
else:
277309
ok, msg = False, "unknown mode"
278310
elif cmd_type == MSG_COMMAND_POWER:
@@ -320,6 +352,9 @@ def _handle_command(self, cmd: Dict[str, Any]) -> None:
320352
ok = False
321353
msg = "unknown mode: %s" % mode
322354

355+
if skip_final_result:
356+
return
357+
323358
try:
324359
self._send(
325360
{

teacher/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def enqueue_network_restrict(session_id: str, cmd_id: str, payload: Dict[str, An
9191
if os.path.exists(yaml_file_path):
9292
app.log_line(f"配置文件同步已启用 yaml={yaml_file_path}")
9393
else:
94-
app.log_line(f"警告: yaml配置文件不存在 {yaml_file_path}")
94+
app.log_line(f"警告: yaml 配置文件不存在 {yaml_file_path}")
9595

9696
def poll_queue() -> None:
9797
try:
@@ -100,7 +100,7 @@ def poll_queue() -> None:
100100
if kind == "client_registered":
101101
hn = data.get("hostname") or "-"
102102
app.log_line(
103-
"上线: pc_name=%s [%s] %s (%s)"
103+
"上线: %s [%s] %s (%s)"
104104
% (
105105
hn,
106106
(data.get("machine_id") or "")[:12] or "-",
@@ -146,4 +146,4 @@ def poll_queue() -> None:
146146

147147

148148
if __name__ == "__main__":
149-
main()
149+
main()

teacher/server.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import collections
6+
import errno
67
import json
78
import os
89
import socket
@@ -28,6 +29,26 @@
2829
)
2930

3031

32+
def _socket_exc_likely_peer_hangup(exc: BaseException) -> bool:
33+
"""True when the client side probably closed or reset TCP (e.g. after IPv4 change)."""
34+
if isinstance(exc, ConnectionError):
35+
return True
36+
if isinstance(exc, OSError):
37+
w = getattr(exc, "winerror", None)
38+
if w in (10038, 10053, 10054, 10057):
39+
return True
40+
errno = exc.errno
41+
if errno is not None:
42+
if errno in (
43+
errno.ECONNRESET,
44+
errno.EPIPE,
45+
errno.ENOTCONN,
46+
errno.ECONNABORTED,
47+
):
48+
return True
49+
return False
50+
51+
3152
@dataclass
3253
class ClientSession:
3354
session_id: str
@@ -302,14 +323,15 @@ def _client_loop(self, conn: socket.socket, peer_ip: str) -> None:
302323
s_err = self._sessions.get(session_id)
303324
if s_err:
304325
err_host = str(s_err.hostname or "")
305-
self._emit(
306-
"client_error",
307-
{
308-
"session_id": session_id or "",
309-
"hostname": err_host,
310-
"message": str(e),
311-
},
312-
)
326+
if not _socket_exc_likely_peer_hangup(e):
327+
self._emit(
328+
"client_error",
329+
{
330+
"session_id": session_id or "",
331+
"hostname": err_host,
332+
"message": str(e),
333+
},
334+
)
313335
finally:
314336
if session_id:
315337
disc_host = ""

0 commit comments

Comments
 (0)