Skip to content

Commit 4fac746

Browse files
committed
fix(agent,teacher): query live IPv4 snapshot on dialog open and harden Win7 WMI/route fallbacks
1 parent d3fb494 commit 4fac746

6 files changed

Lines changed: 390 additions & 72 deletions

File tree

agent/main.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
MSG_ACK,
2222
MSG_COMMAND_NETWORK_RESTRICT,
2323
MSG_COMMAND_POWER,
24+
MSG_COMMAND_QUERY_IPV4_DETAIL,
2425
MSG_COMMAND_RENAME_HOST,
2526
MSG_COMMAND_SET_IPV4,
2627
MSG_CONFIG_RESPONSE,
@@ -278,6 +279,24 @@ def _handle_command(self, cmd: Dict[str, Any]) -> None:
278279
if ok:
279280
msg = "%s; auto reboot in 1s" % msg
280281
self._schedule_reboot()
282+
elif cmd_type == MSG_COMMAND_QUERY_IPV4_DETAIL:
283+
snap = get_default_ipv4_detail_snapshot(ttl_sec=0.0)
284+
ok = True
285+
msg = "ipv4_detail"
286+
detail_payload = snap
287+
try:
288+
self._send(
289+
{
290+
"type": MSG_RESULT,
291+
"cmd_id": cmd_id,
292+
"ok": bool(ok),
293+
"message": str(msg),
294+
"ipv4_detail": detail_payload if isinstance(detail_payload, dict) else {},
295+
}
296+
)
297+
except (ConnectionError, OSError, ValueError):
298+
self._stop.set()
299+
return
281300
elif cmd_type == MSG_COMMAND_SET_IPV4:
282301
name, diag = get_default_ipv4_interface_name()
283302
if not name:

agent/network_config.py

Lines changed: 129 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,13 @@ def _interface_from_route_print(interface_ip: str) -> Tuple[Optional[str], str]:
485485
return None, "no WMI row for interface IP %s" % interface_ip
486486

487487

488-
def _parse_route_print_default_interface_ip(route_text: str) -> Optional[str]:
489-
"""解析 route print -4 中 0.0.0.0/0.0.0.0 默认路由行,取「接口」列 IPv4(中英界面均多为数字列)。"""
488+
def _parse_route_print_default_route_row(route_text: str) -> Optional[Tuple[int, str, str]]:
489+
"""
490+
解析 route print -4 中默认 IPv4 路由行(0.0.0.0 0.0.0.0 …)。
491+
返回 (metric, gateway_ipv4, interface_ipv4);无则 None。
492+
"""
490493
lines = route_text.replace("\r\n", "\n").split("\n")
491-
best: Optional[Tuple[int, str]] = None # (metric, interface_ip)
494+
best: Optional[Tuple[int, str, str]] = None # (metric, gw, iface_ip)
492495
for line in lines:
493496
line_stripped = line.strip()
494497
if not line_stripped.startswith("0.0.0.0"):
@@ -499,16 +502,91 @@ def _parse_route_print_default_interface_ip(route_text: str) -> Optional[str]:
499502
continue
500503
if parts[1] != "0.0.0.0":
501504
continue
505+
gw_ip = parts[2]
502506
iface_ip = parts[3]
503507
if not re.match(r"^\d{1,3}(\.\d{1,3}){3}$", iface_ip):
504508
continue
509+
if not re.match(r"^\d{1,3}(\.\d{1,3}){3}$", gw_ip):
510+
continue
505511
try:
506512
metric = int(parts[4])
507513
except ValueError:
508514
continue
509515
if best is None or metric < best[0]:
510-
best = (metric, iface_ip)
511-
return best[1] if best else None
516+
best = (metric, gw_ip, iface_ip)
517+
return best
518+
519+
520+
def _parse_route_print_default_interface_ip(route_text: str) -> Optional[str]:
521+
"""解析 route print -4 中 0.0.0.0/0.0.0.0 默认路由行,取「接口」列 IPv4(中英界面均多为数字列)。"""
522+
row = _parse_route_print_default_route_row(route_text)
523+
return row[2] if row else None
524+
525+
526+
def _parse_route_print_default_gateway(route_text: str) -> Optional[str]:
527+
"""默认路由行中的 IPv4 网关。"""
528+
row = _parse_route_print_default_route_row(route_text)
529+
return row[1] if row else None
530+
531+
532+
def _route_print_ipv4_default_gateway() -> str:
533+
try:
534+
p = subprocess.run(
535+
["route", "print", "-4"],
536+
capture_output=True,
537+
text=True,
538+
creationflags=_subprocess_flags(),
539+
timeout=30,
540+
)
541+
except (OSError, subprocess.TimeoutExpired):
542+
return ""
543+
if p.returncode != 0:
544+
return ""
545+
g = _parse_route_print_default_gateway(p.stdout or "")
546+
return g if g else ""
547+
548+
549+
def _wmic_nic_configuration_row_for_ipv4(target_ipv4: str) -> Optional[Dict[str, str]]:
550+
"""
551+
Win7 等环境下路由表 InterfaceIndex 与 NICConfiguration.InterfaceIndex 可能不一致;
552+
按本机 IPv4 在 WMI 全部已启用配置中反查对应行。
553+
"""
554+
target = (target_ipv4 or "").strip().lower()
555+
if not target or not _is_dotted_ipv4(target):
556+
return None
557+
try:
558+
p = subprocess.run(
559+
[
560+
"wmic",
561+
"path",
562+
"Win32_NetworkAdapterConfiguration",
563+
"where",
564+
"IPEnabled=true",
565+
"get",
566+
"InterfaceIndex,IPAddress,IPSubnet,DefaultIPGateway,DNSServerSearchOrder,DHCPEnabled,IPEnabled",
567+
"/format:list",
568+
],
569+
capture_output=True,
570+
text=True,
571+
creationflags=_subprocess_flags(),
572+
timeout=45,
573+
)
574+
except (OSError, subprocess.TimeoutExpired):
575+
return None
576+
if p.returncode != 0:
577+
return None
578+
for row in _parse_wmic_list(p.stdout or ""):
579+
if row.get("ipenabled", "").strip().lower() not in ("true", "1"):
580+
continue
581+
raw_ip = row.get("ipaddress", "")
582+
tokens = [t.strip().lower() for t in _wmic_multivalue_tokens(raw_ip)]
583+
if not tokens:
584+
t0 = _clean_wmic_value(raw_ip).strip().lower()
585+
if t0:
586+
tokens = [t0]
587+
if target in tokens:
588+
return row
589+
return None
512590

513591

514592
def _interface_from_route_print_only() -> Tuple[Optional[str], str]:
@@ -744,32 +822,36 @@ def _wmic_interface_index_for_netconnection_id(target: str) -> Optional[int]:
744822

745823

746824
def _wmic_nic_configuration_by_interface_index(if_idx: int) -> Optional[Dict[str, str]]:
747-
try:
748-
p = subprocess.run(
749-
[
750-
"wmic",
751-
"path",
752-
"Win32_NetworkAdapterConfiguration",
753-
"where",
754-
"InterfaceIndex=%d" % if_idx,
755-
"get",
756-
"IPAddress,IPSubnet,DefaultIPGateway,DNSServerSearchOrder,DHCPEnabled,IPEnabled",
757-
"/format:list",
758-
],
759-
capture_output=True,
760-
text=True,
761-
creationflags=_subprocess_flags(),
762-
timeout=30,
763-
)
764-
except (OSError, subprocess.TimeoutExpired):
765-
return None
766-
if p.returncode != 0:
767-
return None
768-
rows = _parse_wmic_list(p.stdout or "")
769-
for row in rows:
770-
if row.get("ipenabled", "").strip().lower() in ("true", "1"):
771-
return row
772-
return rows[0] if rows else None
825+
"""Win7 上部分环境需用 Index= 而非 InterfaceIndex=,两种都试。"""
826+
for where in ("InterfaceIndex=%d" % if_idx, "Index=%d" % if_idx):
827+
try:
828+
p = subprocess.run(
829+
[
830+
"wmic",
831+
"path",
832+
"Win32_NetworkAdapterConfiguration",
833+
"where",
834+
where,
835+
"get",
836+
"IPAddress,IPSubnet,DefaultIPGateway,DNSServerSearchOrder,DHCPEnabled,IPEnabled",
837+
"/format:list",
838+
],
839+
capture_output=True,
840+
text=True,
841+
creationflags=_subprocess_flags(),
842+
timeout=30,
843+
)
844+
except (OSError, subprocess.TimeoutExpired):
845+
continue
846+
if p.returncode != 0:
847+
continue
848+
rows = _parse_wmic_list(p.stdout or "")
849+
for row in rows:
850+
if row.get("ipenabled", "").strip().lower() in ("true", "1"):
851+
return row
852+
if rows:
853+
return rows[0]
854+
return None
773855

774856

775857
def _ipv4_subnet_pairs_from_wmi_row(row: Dict[str, str]) -> List[Tuple[str, str]]:
@@ -1017,6 +1099,22 @@ def get_default_ipv4_detail_snapshot(ttl_sec: float = 20.0) -> Dict[str, Any]:
10171099
if not str(out.get("ip") or "").strip() and pref:
10181100
out["ip"] = pref
10191101

1102+
hip = str(out.get("ip") or pref).strip()
1103+
if hip:
1104+
need_more = (
1105+
not str(out.get("mask") or "").strip()
1106+
or not str(out.get("gateway") or "").strip()
1107+
or not str(out.get("dns_primary") or "").strip()
1108+
)
1109+
if need_more:
1110+
row_fix = _wmic_nic_configuration_row_for_ipv4(hip)
1111+
if row_fix:
1112+
_merge_ipv4_detail_fields(out, _detail_row_to_dict(row_fix, hip))
1113+
if not str(out.get("gateway") or "").strip():
1114+
gw_rp = _route_print_ipv4_default_gateway()
1115+
if gw_rp:
1116+
out["gateway"] = gw_rp
1117+
10201118
_detail_mono = now
10211119
_detail_payload = dict(out)
10221120
return dict(out)

common/protocol.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
MSG_ACK = "ack"
1616
MSG_COMMAND_RENAME_HOST = "command_rename_host"
1717
MSG_COMMAND_SET_IPV4 = "command_set_ipv4"
18+
MSG_COMMAND_QUERY_IPV4_DETAIL = "command_query_ipv4_detail"
1819
MSG_COMMAND_POWER = "command_power"
1920
MSG_COMMAND_NETWORK_RESTRICT = "command_network_restrict"
2021
MSG_RESULT = "result"

teacher/main.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from common.protocol import (
1818
MSG_COMMAND_NETWORK_RESTRICT,
1919
MSG_COMMAND_POWER,
20+
MSG_COMMAND_QUERY_IPV4_DETAIL,
2021
MSG_COMMAND_RENAME_HOST,
2122
MSG_COMMAND_SET_IPV4,
2223
)
@@ -68,6 +69,15 @@ def enqueue_set_ipv4(session_id: str, cmd_id: str, payload: Dict[str, Any]) -> b
6869
body.update(payload)
6970
return server.enqueue_command(session_id, body)
7071

72+
def enqueue_query_ipv4_detail(session_id: str, cmd_id: str) -> bool:
73+
return server.enqueue_command(
74+
session_id,
75+
{
76+
"type": MSG_COMMAND_QUERY_IPV4_DETAIL,
77+
"cmd_id": cmd_id,
78+
},
79+
)
80+
7181
def enqueue_power(session_id: str, cmd_id: str, action: str) -> bool:
7282
return server.enqueue_command(
7383
session_id,
@@ -83,7 +93,14 @@ def enqueue_network_restrict(session_id: str, cmd_id: str, payload: Dict[str, An
8393
body.update(payload)
8494
return server.enqueue_command(session_id, body)
8595

86-
app = TeacherApp(server, enqueue_rename, enqueue_set_ipv4, enqueue_power, enqueue_network_restrict)
96+
app = TeacherApp(
97+
server,
98+
enqueue_rename,
99+
enqueue_set_ipv4,
100+
enqueue_query_ipv4_detail,
101+
enqueue_power,
102+
enqueue_network_restrict,
103+
)
87104
app.log_line(
88105
"教师端已启动 监听 %s:%s token=%s"
89106
% (listen_host, listen_port, "已启用" if token else "未启用")
@@ -114,6 +131,7 @@ def poll_queue() -> None:
114131
% (data.get("hostname") or "-", data.get("session_id"))
115132
)
116133
elif kind == "command_result":
134+
app.handle_ipv4_detail_query_result(data)
117135
app.log_line(
118136
"结果 pc_name=%s cmd_id=%s ok=%s %s"
119137
% (

teacher/server.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,17 @@ def _client_loop(self, conn: socket.socket, peer_ip: str) -> None:
293293
s_res = self._sessions.get(session_id)
294294
if s_res:
295295
res_host = str(s_res.hostname or "")
296-
self._emit(
297-
"command_result",
298-
{
299-
"session_id": session_id,
300-
"hostname": res_host,
301-
"cmd_id": msg.get("cmd_id"),
302-
"ok": bool(msg.get("ok")),
303-
"message": str(msg.get("message") or ""),
304-
},
305-
)
296+
payload_cr: Dict[str, Any] = {
297+
"session_id": session_id,
298+
"hostname": res_host,
299+
"cmd_id": msg.get("cmd_id"),
300+
"ok": bool(msg.get("ok")),
301+
"message": str(msg.get("message") or ""),
302+
}
303+
det_r = msg.get("ipv4_detail")
304+
if isinstance(det_r, dict):
305+
payload_cr["ipv4_detail"] = det_r
306+
self._emit("command_result", payload_cr)
306307
elif mtype == MSG_REQUEST_CONFIG:
307308
config_content = ""
308309
if self._yaml_file_path and os.path.exists(self._yaml_file_path):

0 commit comments

Comments
 (0)