Skip to content

Commit 3b4da4a

Browse files
committed
fix(agent): query NIC by InterfaceIndex only; parse netsh DHCP for zh-CN colons
1 parent 615edff commit 3b4da4a

2 files changed

Lines changed: 121 additions & 37 deletions

File tree

agent/network_config.py

Lines changed: 99 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,8 @@ def _parse_netsh_dns_ipv4_list(text: str) -> List[str]:
598598

599599
def _netsh_ipv4_enrich_from_ifindex(if_idx: int) -> Dict[str, Any]:
600600
"""
601-
用「接口索引」调用 netsh,不依赖中文连接名;补缺子网掩码与 DNS(Win7/编码异常时尤其有效)。
601+
用「接口索引」调用 netsh,不依赖中文连接名;
602+
补缺子网掩码与 DNS(Win7/编码异常时尤其有效),并从 show addresses 解析 DHCP 是否启用。
602603
"""
603604
out: Dict[str, Any] = {}
604605
if sys.platform != "win32" or if_idx < 0:
@@ -614,6 +615,10 @@ def _netsh_ipv4_enrich_from_ifindex(if_idx: int) -> Dict[str, Any]:
614615
return out
615616
text = p.stdout or ""
616617

618+
ndhcp = _netsh_parse_dhcp_enabled_from_addresses(text)
619+
if ndhcp is not None:
620+
out["dhcp"] = ndhcp
621+
617622
mmask = re.search(
618623
r"\(\s*mask\s+(\d{1,3}(?:\.\d{1,3}){3})\s*\)",
619624
text,
@@ -924,32 +929,36 @@ def _wmic_interface_index_for_netconnection_id(target: str) -> Optional[int]:
924929

925930

926931
def _wmic_nic_configuration_by_interface_index(if_idx: int) -> Optional[Dict[str, str]]:
927-
"""Win7 上部分环境需用 Index= 而非 InterfaceIndex=,两种都试。"""
928-
for where in ("InterfaceIndex=%d" % if_idx, "Index=%d" % if_idx):
929-
try:
930-
p = _run_text_cmd(
931-
[
932-
"wmic",
933-
"path",
934-
"Win32_NetworkAdapterConfiguration",
935-
"where",
936-
where,
937-
"get",
938-
"IPAddress,IPSubnet,DefaultIPGateway,DNSServerSearchOrder,DHCPEnabled,IPEnabled",
939-
"/format:list",
940-
],
941-
timeout=30,
942-
)
943-
except (OSError, subprocess.TimeoutExpired, ValueError):
944-
continue
945-
if p.returncode != 0:
946-
continue
947-
rows = _parse_wmic_list(p.stdout or "")
948-
for row in rows:
949-
if row.get("ipenabled", "").strip().lower() in ("true", "1"):
950-
return row
951-
if rows:
952-
return rows[0]
932+
"""
933+
按 Win32_NetworkAdapterConfiguration.InterfaceIndex 查询。
934+
Win32_IP4RouteTable.InterfaceIndex、netsh/route print「接口」索引与此字段一致。
935+
勿再以 Index=N 回退:Win7 上常见 Index≠InterfaceIndex(如 Index=7、InterfaceIndex=11);
936+
用路由表得到的 11 去查 Index=11 会得到另一块网卡,DHCPEnabled 误判为假。
937+
"""
938+
try:
939+
p = _run_text_cmd(
940+
[
941+
"wmic",
942+
"path",
943+
"Win32_NetworkAdapterConfiguration",
944+
"where",
945+
"InterfaceIndex=%d" % if_idx,
946+
"get",
947+
"IPAddress,IPSubnet,DefaultIPGateway,DNSServerSearchOrder,DHCPEnabled,IPEnabled",
948+
"/format:list",
949+
],
950+
timeout=30,
951+
)
952+
except (OSError, subprocess.TimeoutExpired, ValueError):
953+
return None
954+
if p.returncode != 0:
955+
return None
956+
rows = _parse_wmic_list(p.stdout or "")
957+
for row in rows:
958+
if row.get("ipenabled", "").strip().lower() in ("true", "1"):
959+
return row
960+
if rows:
961+
return rows[0]
953962
return None
954963

955964

@@ -1018,6 +1027,59 @@ def _wmic_default_route_interface_index() -> Optional[int]:
10181027
return candidates[0][1]
10191028

10201029

1030+
def _dhcp_enabled_from_wmic_raw(raw: str) -> Optional[bool]:
1031+
"""
1032+
Win32_NetworkAdapterConfiguration.DHCPEnabled。
1033+
WMIC list 常为 TRUE/FALSE;缺省或非布尔时不应当成「静态」(否则 Win7 DHCP 易被误判)。
1034+
"""
1035+
s = _clean_wmic_value((raw or "").strip()).strip().lower()
1036+
if not s or s in ("(null)", "null", "none"):
1037+
return None
1038+
if s in ("true", "1", "yes", "y", "-1"):
1039+
return True
1040+
if s in ("false", "0", "no", "n"):
1041+
return False
1042+
return None
1043+
1044+
1045+
def _netsh_value_after_label_colon(line: str) -> str:
1046+
"""中英文 netsh 可能用半角 ':' 或全角 ':' 分隔标签与取值。"""
1047+
for sep in (":", "\uff1a"):
1048+
if sep in line:
1049+
return line.split(sep, 1)[-1].strip()
1050+
return ""
1051+
1052+
1053+
def _netsh_parse_dhcp_enabled_from_addresses(text: str) -> Optional[bool]:
1054+
"""解析 netsh interface ipv4 show addresses 输出里的 DHCP 启用行(中英界面)。"""
1055+
for raw in (text or "").replace("\r\n", "\n").split("\n"):
1056+
line = raw.strip()
1057+
if not line:
1058+
continue
1059+
low = line.lower()
1060+
if "dhcp" not in low:
1061+
continue
1062+
if ("enabled" not in low) and ("启用" not in line):
1063+
continue
1064+
tail = _netsh_value_after_label_colon(line)
1065+
tl = tail.lower()
1066+
if re.match(r"(?i)^(yes|true|y|1)\b", tl):
1067+
return True
1068+
if re.match(r"(?i)^(no|false|n|0)\b", tl):
1069+
return False
1070+
if tail in ("否", "否。", "NO", "no"):
1071+
return False
1072+
# 中文版常见:冒号后为「是」(半角冒号或非 ASCII 分隔已在上面的 tail 中取到)
1073+
if tail in ("是", "是。") or tail.startswith("是"):
1074+
return True
1075+
# 容错:极少数主题下取值与标签粘在同一侧
1076+
if "否" in tail and len(tail) <= 8:
1077+
return False
1078+
if "是" in tail and ("否" not in tail):
1079+
return True
1080+
return None
1081+
1082+
10211083
def _detail_row_to_dict(
10221084
row: Dict[str, str], preferred_ip: str
10231085
) -> Dict[str, Any]:
@@ -1039,16 +1101,17 @@ def _detail_row_to_dict(
10391101
t = t.strip()
10401102
if _is_dotted_ipv4(t):
10411103
dns_v4.append(t)
1042-
dhcp_raw = row.get("dhcpenabled", "").strip().lower()
1043-
dhcp = dhcp_raw in ("true", "1")
1044-
return {
1104+
dhcp_hint = _dhcp_enabled_from_wmic_raw(row.get("dhcpenabled", ""))
1105+
out_d: Dict[str, Any] = {
10451106
"ip": chosen_ip,
10461107
"mask": chosen_mask,
10471108
"gateway": gw,
10481109
"dns_primary": dns_v4[0] if dns_v4 else "",
10491110
"dns_secondary": dns_v4[1] if len(dns_v4) > 1 else "",
1050-
"dhcp": dhcp,
10511111
}
1112+
if dhcp_hint is not None:
1113+
out_d["dhcp"] = bool(dhcp_hint)
1114+
return out_d
10521115

10531116

10541117
def _powershell_ipv4_detail_by_default_route() -> Optional[Dict[str, Any]]:
@@ -1082,7 +1145,10 @@ def _powershell_ipv4_detail_by_default_route() -> Optional[Dict[str, Any]]:
10821145
"$d1 = ''; $d2 = ''; "
10831146
"if ($dns.Count -ge 1) { $d1 = [string]$dns[0] }; "
10841147
"if ($dns.Count -ge 2) { $d2 = [string]$dns[1] }; "
1085-
"$dhcp = ($pick.PrefixOrigin -eq 'Dhcp'); "
1148+
"$ifi = Get-NetIPInterface -InterfaceIndex $idx -AddressFamily IPv4 "
1149+
"-ErrorAction SilentlyContinue | Select-Object -First 1; "
1150+
"$dhcpIfc = ($ifi -ne $null -and $ifi.Dhcp -eq 'Enabled'); "
1151+
"$dhcp = ($dhcpIfc -or ($pick.PrefixOrigin -eq 'Dhcp')); "
10861152
"$pfx = [int]$pick.PrefixLength; "
10871153
"$o = @{ ok=$true; ip=$pick.IPAddress; prefix=$pfx; gateway=$gw; "
10881154
"dns_primary=$d1; dns_secondary=$d2; dhcp=$dhcp }; "
@@ -1240,6 +1306,7 @@ def get_default_ipv4_detail_snapshot(
12401306
if idx_netsh is not None and (
12411307
not str(out.get("mask") or "").strip()
12421308
or not str(out.get("dns_primary") or "").strip()
1309+
or not bool(out.get("dhcp"))
12431310
):
12441311
_merge_ipv4_detail_fields(out, _netsh_ipv4_enrich_from_ifindex(idx_netsh))
12451312

teacher/ui.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,11 @@ def ok() -> None:
565565
d.columnconfigure(0, weight=1)
566566
_dlg_lock_min_size(d)
567567

568+
@staticmethod
569+
def _volatile_indicates_dhcp(volatile: Dict[str, Any]) -> bool:
570+
"""学生端快照 / 会话中的 dhcp 布尔;缺省或非真视为静态。"""
571+
return bool(volatile.get("dhcp"))
572+
568573
def _snapshot_session_ipv4_into_dict(self, volatile: Dict[str, Any], sid: str) -> None:
569574
"""把会话缓存中的 ipv4_detail / reported_ipv4 写入 volatile(整块覆盖)。"""
570575
volatile.clear()
@@ -698,7 +703,12 @@ def handle_ipv4_detail_query_result(self, data: Dict[str, Any]) -> None:
698703
e.configure(state=tk.NORMAL)
699704
for r in radios:
700705
r.configure(state=tk.NORMAL)
701-
if mode.get() == "static":
706+
if self._volatile_indicates_dhcp(volatile):
707+
mode.set("dhcp")
708+
for e in entries:
709+
e.delete(0, tk.END)
710+
else:
711+
mode.set("static")
702712
self._fill_ipv4_entries_from_snapshot(entries, sid, volatile)
703713

704714
def _ipv4_live_query_timeout(self, cmd_id: str) -> None:
@@ -739,7 +749,12 @@ def _ipv4_live_query_timeout(self, cmd_id: str) -> None:
739749
for r in w.get("radios") or []:
740750
r.configure(state=tk.NORMAL)
741751
mode = w["mode"]
742-
if mode.get() == "static":
752+
if self._volatile_indicates_dhcp(volatile):
753+
mode.set("dhcp")
754+
for e in w["entries"]:
755+
e.delete(0, tk.END)
756+
else:
757+
mode.set("static")
743758
self._fill_ipv4_entries_from_snapshot(w["entries"], sid, volatile)
744759

745760
def _dlg_ipv4(self) -> None:
@@ -756,7 +771,10 @@ def _dlg_ipv4(self) -> None:
756771
d.transient(self)
757772
d.grab_set()
758773
volatile: Dict[str, Any] = {}
759-
mode = tk.StringVar(value="static")
774+
self._snapshot_session_ipv4_into_dict(volatile, sid)
775+
mode = tk.StringVar(
776+
value="dhcp" if self._volatile_indicates_dhcp(volatile) else "static"
777+
)
760778

761779
rb_static = ttk.Radiobutton(d, text="静态 IPv4", variable=mode, value="static")
762780
rb_static.grid(row=0, column=0, sticky=tk.W, padx=8, pady=(8, 2))
@@ -857,8 +875,7 @@ def cancel_ipv4_dlg() -> None:
857875
ttk.Button(bf, text="取消", command=cancel_ipv4_dlg).pack(side=tk.LEFT, padx=4)
858876
rb_static.configure(state=tk.DISABLED)
859877
rb_dhcp.configure(state=tk.DISABLED)
860-
frozen_fallback: Dict[str, Any] = {}
861-
self._snapshot_session_ipv4_into_dict(frozen_fallback, sid)
878+
frozen_fallback = dict(volatile)
862879
self._ipv4_live_waiters[q_cmd_id] = {
863880
"phase": "waiting",
864881
"sid": sid,

0 commit comments

Comments
 (0)