This commit is contained in:
2025-11-02 13:57:43 +08:00
parent a3c3d670e7
commit d693e15c96
9 changed files with 390 additions and 259 deletions
+112 -72
View File
@@ -1,87 +1,127 @@
import subprocess
import locale
import re
import tkinter as tk
from tkinter import scrolledtext, messagebox
import logging
class NetworkManager:
"""获取并设置本机网络适配器信息(Windows"""
"""获取本地网卡详细信息(含 DNS、DHCP"""
def __init__(self, result_box: scrolledtext.ScrolledText):
self.result_box = result_box
def get_all_adapters(self):
"""返回本机所有网卡信息列表,每个网卡是字典"""
result = subprocess.run("ipconfig /all", capture_output=True, text=True, shell=True)
lines = result.stdout.splitlines()
def get_network_info(self):
# 自动获取系统编码(例如 'cp936' 中文Windows
system_encoding = locale.getpreferredencoding(False)
# 调用 ipconfig /all
result = subprocess.run(
"ipconfig /all",
shell=True,
capture_output=True,
text=True,
encoding=system_encoding, # 自动根据系统语言选择
errors="ignore" # 忽略解码错误
)
output = result.stdout
if not output:
raise RuntimeError("无法获取 ipconfig 输出,请检查系统命令执行权限。")
# 按块拆分
import re
adapter_blocks = re.split(r"\r?\n(?=\S.*?:)", output)
adapters = []
adapter = None
for line in lines:
line = line.strip()
if not line:
for block in adapter_blocks:
if not re.search(r"适配器", block):
continue
# 匹配网卡名称
m = re.match(r'(?:以太网适配器|无线局域网适配器|未知适配器|Ethernet|Wi-Fi|Adapter)\s*(.+):', line)
if m:
if adapter:
adapters.append(adapter)
adapter = {
'name': m.group(1),
'ip': '',
'mask': '',
'gateway': '',
'dns1': '',
'dns2': '',
'dhcp': '',
'media': '',
'desc': ''
}
continue
info = {}
match = re.match(r"(.+?):", block)
if match:
info["name"] = match.group(1).strip()
if adapter is None:
continue
match = re.search(r"描述.*?:\s*(.+)", block)
if match:
info["description"] = match.group(1).strip()
# 解析字段
if "IPv4 地址" in line or "IPv4 Address" in line:
adapter['ip'] = line.split(':')[-1].strip().split('(')[0]
elif "子网掩码" in line or "Subnet Mask" in line:
adapter['mask'] = line.split(':')[-1].strip()
elif "默认网关" in line or "Default Gateway" in line:
if not adapter['gateway']:
adapter['gateway'] = line.split(':')[-1].strip()
elif "DHCP 已启用" in line or "DHCP Enabled" in line:
adapter['dhcp'] = line.split(':')[-1].strip()
elif "媒体状态" in line or "Media State" in line:
adapter['media'] = line.split(':')[-1].strip()
elif "描述" in line or "Description" in line:
adapter['desc'] = line.split(':')[-1].strip()
elif "DNS 服务器" in line or "DNS Servers" in line:
dns = line.split(':')[-1].strip()
if not adapter['dns1']:
adapter['dns1'] = dns
elif not adapter['dns2']:
adapter['dns2'] = dns
match = re.search(r"物理地址.*?:\s*(.+)", block)
if match:
info["mac"] = match.group(1).strip()
if adapter:
adapters.append(adapter)
match = re.search(r"DHCP 已启用.*?:\s*(.+)", block)
if match:
value = match.group(1).strip()
info["dhcp_enabled"] = "" if value == "" else ""
match = re.search(r"IPv4 地址.*?:\s*([0-9.]+)", block)
if match:
info["ipv4"] = match.group(1)
match = re.search(r"子网掩码.*?:\s*([0-9.]+)", block)
if match:
info["netmask"] = match.group(1)
gateway = ""
# 先找出 "默认网关" 所在行及后续可能的下一行
gw_match = re.search(r"默认网关[.\s:]*([^\r\n]*)\r?\n(?:\s*([^\r\n]+))?", block)
if gw_match:
# 合并两行文本
gw_text = " ".join(gw_match.groups(default=""))
# 提取 IPv4 地址
ipv4_match = re.search(r"\b\d{1,3}(?:\.\d{1,3}){3}\b", gw_text)
if ipv4_match:
gateway = ipv4_match.group(0)
info["gateway"] = gateway
# --- DNS 服务器提取(支持多行 + IPv4优先)---
dns1 = dns2 = ""
# 找出 “DNS 服务器” 开始的位置
dns_match = re.search(r"DNS 服务器[.\s:]*([^\r\n]*)((?:\r?\n\s+[^\r\n]+)*)", block)
if dns_match:
# 合并所有行
dns_text = dns_match.group(1) + dns_match.group(2)
# 提取所有 IPv4 地址(优先),如果没有,再取 IPv6
dns_ipv4 = re.findall(r"\b\d{1,3}(?:\.\d{1,3}){3}\b", dns_text)
if dns_ipv4:
dns1 = dns_ipv4[0]
if len(dns_ipv4) > 1:
dns2 = dns_ipv4[1]
else:
# 没有 IPv4,则尝试 IPv6
dns_ipv6 = re.findall(r"[a-fA-F0-9:]+(?:%[0-9]+)?", dns_text)
if dns_ipv6:
dns1 = dns_ipv6[0]
if len(dns_ipv6) > 1:
dns2 = dns_ipv6[1]
info["dns1"] = dns1
info["dns2"] = dns2
adapters.append(info)
return adapters
def set_network_info(self, settings):
"""设置指定网卡的网络配置"""
try:
self.result_box.insert(tk.END, f"尝试将网卡配置修改为:\n")
for key, value in settings.items():
self.result_box.insert(tk.END, f" {key}: {value}\n")
if settings['dhcp_enabled']:
# 启用 DHCP 自动获取 IP 地址
subprocess.run(
f'netsh interface ip set address name="{settings['name']}" source=dhcp',
shell=True, check=True
)
else:
subprocess.run(
f'netsh interface ip set address name="{settings['name']}" source=static addr={settings['ipv4']} mask={settings['netmask']} gateway={settings['gateway']}',
shell=True, check=True)
self.result_box.insert(tk.END, f"网络配置设置完成\n")
def set_static_ip(self, name, ip, mask, gateway):
"""设置静态 IP"""
cmd = f'netsh interface ip set address name="{name}" static {ip} {mask} {gateway} 1'
return subprocess.run(cmd, shell=True, capture_output=True, text=True, encoding="gbk").stdout
def set_dhcp(self, name):
"""切换 DHCP"""
cmd1 = f'netsh interface ip set address name="{name}" dhcp'
cmd2 = f'netsh interface ip set dns name="{name}" dhcp'
out1 = subprocess.run(cmd1, shell=True, capture_output=True, text=True, encoding="gbk").stdout
out2 = subprocess.run(cmd2, shell=True, capture_output=True, text=True, encoding="gbk").stdout
return out1 + "\n" + out2
def set_dns(self, name, dns1, dns2=None):
"""设置 DNS"""
cmd1 = f'netsh interface ip set dns name="{name}" static {dns1} primary'
out = subprocess.run(cmd1, shell=True, capture_output=True, text=True, encoding="gbk").stdout
if dns2:
cmd2 = f'netsh interface ip add dns name="{name}" {dns2} index=2'
out += subprocess.run(cmd2, shell=True, capture_output=True, text=True, encoding="gbk").stdout
return out
except subprocess.CalledProcessError as e:
self.result_box.insert(tk.END, f"设置{settings['name']}网络配置失败: {e}\n")
self.result_box.see(tk.END)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -88,7 +88,7 @@ class BasicUI:
def add_button(
self, parent, button_text,
row, col=0, command="",
label_width=8, width=5, colspan=1, sticky='w'
width=5, colspan=1, sticky='w'
):
"""
添加一个按钮,并返回 (StringVar, Frame) 以便后续控制。
@@ -96,7 +96,7 @@ class BasicUI:
- button_text: 按钮文本
- row, col: 放置在父容器的 grid 行列
- command: 调用的函数
- width: 输入框宽度
- width: 按钮宽度
- colspan: 该组控件在父容器上跨越的列数
- sticky: 对齐方式(默认左对齐)
"""
+113 -184
View File
@@ -1,8 +1,9 @@
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
import logging
import re
import logging
from core.ui.basic_ui import BasicUI
from core.Function.network_fun import NetworkManager
@@ -13,218 +14,146 @@ class NetworkTab(ttk.Frame, BasicUI):
def __init__(self, parent):
super().__init__(parent)
self.netmgr = NetworkManager()
self._build_ui()
threading.Thread(target=self.refresh_interfaces, daemon=True).start()
self.networkname_list = []
self.networkconfig = []
self.build_ui()
self.netmgr = NetworkManager(self.result_box)
self.refresh_allnetwork()
# ---------------- UI 构建 ----------------
def _build_ui(self):
def build_ui(self):
self.create_iface_section()
self.create_config_section()
self.create_action_section()
self.create_output_section()
self._set_button_state(enabled=True)
def refresh_allnetwork(self):
'''刷新网卡列表'''
self.networkname_list = []
try:
self.networkconfigs = self.netmgr.get_network_info()
for config in self.networkconfigs:
self.networkname_list.append(config.get("name", "未知网卡"))
self.iface_cb['combobox']['values'] = self.networkname_list
self.iface_cb['var'].set(self.networkname_list[0])
self.result_box.insert(tk.END, f"获取网卡信息完成,共:{len(self.networkname_list)}个启用网卡\n")
self.refresh_network_callback()
logger.info(f"获取网卡信息完成,共:{len(self.networkname_list)}个启用网卡")
logger.info(f"所有获取网卡信息:{self.networkconfigs}")
except Exception as e:
messagebox.showerror("错误", f"获取网卡信息失败: {e}")
logger.error(f"获取网卡信息失败: {e}")
self.networkconfigs = []
# ---------------- UI 构建 ----------------
def create_iface_section(self):
frame = ttk.LabelFrame(self, text="网卡选择", padding=8)
frame.pack(side='top', fill='x', padx=10, pady=6)
self.iface_var = tk.StringVar()
ttk.Label(frame, text="选择网卡:").grid(row=0, column=0, sticky='w')
self.iface_cb = ttk.Combobox(frame, textvariable=self.iface_var, state='readonly', width=40)
self.iface_cb.grid(row=0, column=1, padx=6, pady=2)
self.iface_cb.bind("<<ComboboxSelected>>", self.on_iface_selected)
self.refresh_btn = ttk.Button(frame, text="刷新", width=10, command=self.on_refresh_clicked)
self.refresh_btn.grid(row=0, column=2, padx=6)
self.iface_cb = self.add_combobox(frame, "选择网卡", row=0, col=0, listbox=[], width=40, inivar=-1)
self.iface_cb['combobox'].bind("<<ComboboxSelected>>", lambda e: self.refresh_network_callback())
self.refresh_btn = self.add_button(frame, "刷新网卡列表", row=0, col=1, width=15, command=self.refresh_allnetwork)
self.description_entry = self.add_input(frame, "网卡名称", row=1, col=0, inivar="", entry_width=40)
self.description_entry['entry'].config(state='disabled')
self.mac_entry = self.add_input(frame, "MAC地址", row=1, col=1, inivar="", entry_width=40)
self.mac_entry['entry'].config(state='disabled')
def create_config_section(self):
frame = ttk.LabelFrame(self, text="IP 配置(编辑后点击应用)", padding=8)
frame.pack(side='top', fill='x', padx=10, pady=6)
self.ip_entry = self.add_input(frame, "IPv4 地址", row=0, col=0, inivar="", entry_width=25)
self.ip_entry = self.add_input(frame, "IPv4 地址", row=0, col=0, inivar="", entry_width=20)
self.mask_entry = self.add_input(frame, "子网掩码", row=0, col=1, inivar="", entry_width=20)
self.gw_entry = self.add_input(frame, "默认网关", row=1, col=0, inivar="", entry_width=25)
self.dns1_entry = self.add_input(frame, "主 DNS", row=1, col=1, inivar="", entry_width=25)
self.dns2_entry = self.add_input(frame, "备选 DNS", row=1, col=2, inivar="", entry_width=25)
self.dhcp_var = tk.BooleanVar(value=False)
self.dhcp_chk = ttk.Checkbutton(frame, text="使用 DHCP(自动获取 IP", variable=self.dhcp_var,
command=self.on_dhcp_toggled)
self.dhcp_chk.grid(row=2, column=0, columnspan=2, sticky='w', pady=6)
self.gw_entry = self.add_input(frame, "默认网关", row=0, col=2, inivar="", entry_width=20)
#self.dns1_entry = self.add_input(frame, "主 DNS", row=1, col=0, inivar="", entry_width=20)
#self.dns2_entry = self.add_input(frame, "备选 DNS", row=1, col=1, inivar="", entry_width=20)
self.dhcp_cb = self.add_combobox(frame, "自动获取", row=2, col=0, listbox=["",""], width=5, inivar=-1)
self.dhcp_cb['combobox'].bind("<<ComboboxSelected>>", lambda e: self.dhcp_entry_state())
def create_action_section(self):
frame = ttk.Frame(self)
frame = ttk.LabelFrame(self, text="修改操作", padding=8)
frame.pack(side='top', fill='x', padx=10, pady=6)
self.apply_btn = self.add_button(frame, "应用修改", row=0, col=0, command=self.on_apply_clicked)
self.dhcp_btn = self.add_button(frame, "切换为 DHCP", row=0, col=1, command=self.on_set_dhcp)
self.refresh_info_btn = self.add_button(frame, "刷新当前信息", row=0, col=2, command=self.on_refresh_info)
self.apply_btn = self.add_button(frame, "应用修改", row=0, col=0, width=10, command=self.apply_btn_callback)
self.refresh_info_btn = self.add_button(frame, "刷新当前信息", row=0, col=2, width=15, command=self.refresh_network_callback)
def create_output_section(self):
frame = ttk.LabelFrame(self, text="操作日志", padding=6)
frame = ttk.LabelFrame(self, text="输出信息", padding=6)
frame.pack(side='top', fill='both', expand=True, padx=10, pady=6)
self.result_box = scrolledtext.ScrolledText(frame, width=100, height=12)
self.result_box.pack(fill='both', expand=True, padx=4, pady=4)
def _set_button_state(self, enabled: bool):
state = 'normal' if enabled else 'disabled'
for btn in [self.apply_btn, self.dhcp_btn, self.refresh_info_btn, self.refresh_btn]:
try:
btn.config(state=state)
except Exception:
pass
# ---------------- 事件回调 ----------------
def on_refresh_clicked(self):
self._append_log("刷新网卡列表中...\n")
threading.Thread(target=self.refresh_interfaces, daemon=True).start()
def refresh_network_callback(self):
'''刷新当前网卡信息'''
# 刷新所有所有网卡信息
self.refresh_allnetwork()
# 获取选中的网卡信息
iface_name = self.iface_cb['var'].get()
config = self.get_network_config(iface_name)
# 不可修改配置
self.description_entry['var'].set(config.get("description", ""))
self.mac_entry['var'].set(config.get("mac", ""))
# 可修改配置
self.ip_entry['var'].set(config.get("ipv4", ""))
self.mask_entry['var'].set(config.get("netmask", ""))
self.gw_entry['var'].set(config.get("gateway", ""))
#self.dns1_entry['var'].set(config.get("dns1", ""))
#self.dns2_entry['var'].set(config.get("dns2", ""))
if config.get("dhcp_enabled", "") == "":
self.dhcp_cb['var'].set("")
elif config.get("dhcp_enabled", "") == "":
self.dhcp_cb['var'].set("")
else:
self.dhcp_cb['var'].set("")
self.dhcp_entry_state()
self.get_network_settings()
self.output_network_settings()
def refresh_interfaces(self):
try:
adapters = self.netmgr.get_all_adapters()
except Exception as e:
self._append_log(f"获取网卡失败: {e}\n")
return
def get_network_config(self, iface_name):
'''根据网卡名称获取对应的配置信息'''
for config in self.networkconfigs:
if config.get("name") == iface_name:
return config
return None
names = [a.get("name") or a.get("desc") or str(i) for i, a in enumerate(adapters)]
def dhcp_entry_state(self):
'''根据 DHCP 选择框设置输入框状态'''
if self.dhcp_cb['var'].get() == "":
# 禁用手动输入
self.ip_entry['entry'].config(state='disabled')
self.mask_entry['entry'].config(state='disabled')
self.gw_entry['entry'].config(state='disabled')
else:
# 启用手动输入
self.ip_entry['entry'].config(state='normal')
self.mask_entry['entry'].config(state='normal')
self.gw_entry['entry'].config(state='normal')
def update_ui():
self._adapters = adapters
self.iface_cb['values'] = names
if names:
self.iface_var.set(names[0])
self.load_selected_adapter_info(names[0])
self.after(0, update_ui)
self._append_log("网卡列表刷新完成。\n")
def get_network_settings(self):
'''获取当前输入的网卡配置信息'''
self.networkconfig = {
"name": re.sub(r"^(.*?(适配器))\s*", "", self.iface_cb['var'].get()).strip(),
"ipv4": self.ip_entry['var'].get(),
"netmask": self.mask_entry['var'].get(),
"gateway": self.gw_entry['var'].get(),
#"dns1": self.dns1_entry['var'].get(),
#"dns2": self.dns2_entry['var'].get(),
"dhcp_enabled": self.dhcp_cb['var'].get() == ""
}
def on_iface_selected(self, event=None):
name = self.iface_var.get()
self.load_selected_adapter_info(name)
def output_network_settings(self):
'''输出当前网卡配置信息到日志框'''
self.result_box.insert(tk.END, f"当前网卡配置:\n")
for key, value in self.networkconfig.items():
self.result_box.insert(tk.END, f" {key}: {value}\n")
self.result_box.see(tk.END)
def load_selected_adapter_info(self, name):
adapter = next((a for a in getattr(self, "_adapters", []) if a.get("name")==name or a.get("desc")==name), None)
if not adapter:
self._append_log(f"未找到网卡信息:{name}\n")
return
try:
self.ip_entry['var'].set(adapter.get("ip", ""))
self.mask_entry['var'].set(adapter.get("mask", ""))
self.gw_entry['var'].set(adapter.get("gateway", ""))
self.dns1_entry['var'].set(adapter.get("dns1", ""))
self.dns2_entry['var'].set(adapter.get("dns2", ""))
except Exception:
pass
self.dhcp_var.set(True if str(adapter.get("dhcp", "")).lower() in ("yes","true","") else False)
self._append_log(f"已加载网卡:{name} IP={adapter.get('ip')} 掩码={adapter.get('mask')} 网关={adapter.get('gateway')} DHCP={adapter.get('dhcp')} MAC={adapter.get('mac')}\n")
def on_dhcp_toggled(self):
use_dhcp = self.dhcp_var.get()
state = 'disabled' if use_dhcp else 'normal'
for entry in [self.ip_entry, self.mask_entry, self.gw_entry]:
try:
entry['entry'].config(state=state)
except Exception:
try:
entry.config(state=state)
except Exception:
pass
def on_refresh_info(self):
name = self.iface_var.get()
if not name:
messagebox.showwarning("提示", "请先选择一个网卡")
return
self._append_log(f"读取 {name} 的当前配置信息...\n")
threading.Thread(target=self._refresh_info_thread, args=(name,), daemon=True).start()
def _refresh_info_thread(self, name):
try:
adapters = self.netmgr.get_all_adapters()
self._adapters = adapters
for a in adapters:
if a.get("name")==name or a.get("desc")==name:
self.after(0, lambda ad=a: self.load_selected_adapter_info(ad.get("name")))
break
self._append_log("刷新完成。\n")
except Exception as e:
self._append_log(f"读取配置失败: {e}\n")
def on_apply_clicked(self):
name = self.iface_var.get()
if not name:
messagebox.showwarning("提示", "请选择一个网卡")
return
def read_field(field):
try: return field['var'].get().strip()
except Exception:
try: return field.get().strip()
except Exception: return ""
ip = read_field(self.ip_entry)
mask = read_field(self.mask_entry)
gw = read_field(self.gw_entry)
dns1 = read_field(self.dns1_entry)
dns2 = read_field(self.dns2_entry)
if self.dhcp_var.get():
if not messagebox.askyesno("确认", f"确定将网卡 [{name}] 切换为 DHCP 模式吗?"):
return
self._set_button_state(False)
threading.Thread(target=self._set_dhcp_thread, args=(name,), daemon=True).start()
return
if not ip or not mask:
messagebox.showwarning("输入错误", "IP 与掩码不能为空(或勾选 DHCP)")
return
self._set_button_state(False)
threading.Thread(target=self._set_static_thread, args=(name, ip, mask, gw, dns1, dns2), daemon=True).start()
def _set_dhcp_thread(self, name):
try:
res = self.netmgr.set_dhcp(name)
self._append_log(f"{res}\n")
messagebox.showinfo("完成", res)
threading.Thread(target=self.refresh_interfaces, daemon=True).start()
except Exception as e:
self._append_log(f"设置 DHCP 失败: {e}\n")
messagebox.showerror("失败", f"设置 DHCP 失败: {e}")
finally:
self._set_button_state(True)
def _set_static_thread(self, name, ip, mask, gw, dns1, dns2):
try:
res = self.netmgr.set_static_ip(name, ip, mask, gw)
self._append_log(f"{res}\n")
if dns1:
try:
res_dns = self.netmgr.set_dns(name, dns1, dns2)
self._append_log(f"{res_dns}\n")
except Exception as e:
self._append_log(f"设置 DNS 失败: {e}\n")
messagebox.showinfo("完成", "配置已应用(请注意可能需要管理员权限)")
threading.Thread(target=self.refresh_interfaces, daemon=True).start()
except Exception as e:
self._append_log(f"应用静态 IP 失败: {e}\n")
messagebox.showerror("失败", f"应用静态 IP 失败: {e}")
finally:
self._set_button_state(True)
def on_set_dhcp(self):
name = self.iface_var.get()
if not name:
messagebox.showwarning("提示", "请选择一个网卡")
return
if not messagebox.askyesno("确认", f"确定将网卡 [{name}] 切换为 DHCP 模式吗?"):
return
self._set_button_state(False)
threading.Thread(target=self._set_dhcp_thread, args=(name,), daemon=True).start()
def _append_log(self, text: str):
try:
self.result_box.after(0, lambda: (self.result_box.insert(tk.END, text), self.result_box.see(tk.END)))
except Exception:
pass
def apply_btn_callback(self):
'''应用当前网卡配置信息'''
self.get_network_settings()
self.netmgr.set_network_info(self.networkconfig)
#self.refresh_allnetwork()
+1 -1
View File
@@ -20,7 +20,7 @@ class MainUI(BasicUI):
self.tab_control.pack(expand=1, fill="both")
# 实例化各个功能页
self.tabs = {
"网卡": NetworkTab(self.tab_control),
"网卡": NetworkTab(self.tab_control),
"Ping测试": PingTab(self.tab_control),
"端口扫描服务端": TelnetTab(self.tab_control),
}