测试原理
udevadm info /sys/class/net/ifconfig
读取所有网卡程序
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import subprocess
def run_cmd(cmd):
try:
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if result.returncode == 0:
return result.stdout.strip()
err = result.stderr.strip()
return f"Error: {err}" if err else "Error: command failed"
except Exception as e:
return f"Exception occurred: {str(e)}"
def get_all_ifconfig():
return run_cmd(["ifconfig", "-a"])
def get_iface_udevadm_info(ifname):
return run_cmd(["udevadm", "info", f"/sys/class/net/{ifname}"])
def list_ifaces():
try:
ifaces = os.listdir("/sys/class/net")
ifaces.sort()
return ifaces
except Exception as e:
print(f"读取网卡列表失败: {e}")
return []
if __name__ == "__main__":
print("=" * 80)
print("ifconfig -a 输出")
print("=" * 80)
print(get_all_ifconfig())
print()
ifaces = list_ifaces()
if not ifaces:
print("没有读取到网卡")
else:
for ifname in ifaces:
print("=" * 80)
print(f"网卡: {ifname}")
print("=" * 80)
print(get_iface_udevadm_info(ifname))
print()
UI 网页程序
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import socket
import fcntl
import struct
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import subprocess
import html
import time
PORT = 12315
def get_iface_ip(ifname):
s = None
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(
fcntl.ioctl(
s.fileno(),
0x8915,
struct.pack("256s", ifname[:15].encode("utf-8"))
)[20:24]
)
except Exception:
return ""
finally:
if s:
s.close()
def get_iface_netmask(ifname):
s = None
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(
fcntl.ioctl(
s.fileno(),
0x891b,
struct.pack("256s", ifname[:15].encode("utf-8"))
)[20:24]
)
except Exception:
return ""
finally:
if s:
s.close()
def read_file(path):
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
return f.read().strip()
except Exception:
return ""
def run_cmd(cmd):
try:
r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
return r.stdout.strip()
except Exception as e:
return f"命令执行失败: {e}"
def list_ifaces():
try:
names = os.listdir("/sys/class/net")
names.sort()
return names
except Exception:
return []
def get_iface_mac(ifname):
return read_file(f"/sys/class/net/{ifname}/address")
def get_iface_state(ifname):
return read_file(f"/sys/class/net/{ifname}/operstate")
def get_iface_mtu(ifname):
return read_file(f"/sys/class/net/{ifname}/mtu")
def get_iface_type(ifname):
t = read_file(f"/sys/class/net/{ifname}/type")
type_map = {
"1": "Ethernet",
"24": "Loopback",
"32": "IPIP",
"512": "PPP",
"768": "Tunnel",
"772": "Loopback",
"801": "IEEE802.11",
}
return type_map.get(t, t if t else "未知")
def get_iface_speed(ifname):
v = read_file(f"/sys/class/net/{ifname}/speed")
return v if v else ""
def get_iface_duplex(ifname):
return read_file(f"/sys/class/net/{ifname}/duplex")
def get_iface_carrier(ifname):
return read_file(f"/sys/class/net/{ifname}/carrier")
def get_iface_ipv6_list(ifname):
result = []
try:
with open("/proc/net/if_inet6", "r", encoding="utf-8", errors="ignore") as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 6 and parts[5] == ifname:
raw = parts[0]
addr = socket.inet_ntop(socket.AF_INET6, bytes.fromhex(raw))
prefix = int(parts[2], 16)
result.append(f"{addr}/{prefix}")
except Exception:
pass
return result
def get_iface_ipv4_list(ifname):
result = []
out = run_cmd(["ip", "-o", "-4", "addr", "show", "dev", ifname])
if out.startswith("命令执行失败") or not out:
ip = get_iface_ip(ifname)
if ip:
mask = get_iface_netmask(ifname)
if mask:
result.append(f"{ip} / {mask}")
else:
result.append(ip)
return result
for line in out.splitlines():
parts = line.split()
if "inet" in parts:
idx = parts.index("inet")
if idx + 1 < len(parts):
result.append(parts[idx + 1])
return result
def classify_udev_key(key):
basic_keys = {
"DEVPATH", "DEVNAME", "DEVTYPE", "SUBSYSTEM", "IFINDEX", "INTERFACE",
"MAJOR", "MINOR", "ACTION", "SEQNUM", "USEC_INITIALIZED"
}
if key in basic_keys:
return "基础信息"
if key.startswith("ID_NET_NAME") or key.startswith("ID_NET_LABEL") or key in {"ID_RENAMING", "ID_NET_DRIVER"}:
return "命名信息"
if (
key.startswith("ID_USB") or
key.startswith("ID_VENDOR") or
key.startswith("ID_MODEL") or
key.startswith("ID_PATH") or
key.startswith("ID_PCI") or
key.startswith("PCI_") or
key in {"ID_BUS", "DRIVER", "MODALIAS"}
):
return "总线硬件"
if (
key.startswith("ID_NET_") or
key in {
"ADDR_ASSIGN_TYPE", "ADDR_LEN", "BROADCAST", "CARRIER", "CARRIER_CHANGES",
"CARRIER_UP_COUNT", "CARRIER_DOWN_COUNT", "SPEED", "DUPLEX", "MTU",
"TX_QUEUE_LEN", "LINK_FILE"
}
):
return "网络属性"
return "其他属性"
def get_udevadm_grouped(ifname):
raw = run_cmd(["udevadm", "info", f"/sys/class/net/{ifname}"])
groups = {
"基础信息": [],
"系统路径": [],
"命名信息": [],
"总线硬件": [],
"网络属性": [],
"其他属性": [],
"符号链接": [],
}
if raw.startswith("命令执行失败"):
groups["其他属性"].append(("错误", raw))
return groups
for line in raw.splitlines():
line = line.strip()
if not line or len(line) < 2 or line[1] != ":":
continue
prefix = line[:2]
value = line[2:].strip()
if prefix == "P:":
groups["系统路径"].append(("DEVPATH", value))
elif prefix == "M:":
groups["基础信息"].append(("内核名", value))
elif prefix == "R:":
groups["基础信息"].append(("设备号", value))
elif prefix == "U:":
groups["基础信息"].append(("子系统", value))
elif prefix == "T:":
groups["基础信息"].append(("类型", value))
elif prefix == "D:":
groups["基础信息"].append(("设备节点", value))
elif prefix == "N:":
groups["命名信息"].append(("当前网卡名", value))
elif prefix == "L:":
groups["符号链接"].append(("优先级", value))
elif prefix == "S:":
groups["符号链接"].append(("别名路径", value))
elif prefix == "E:":
if "=" in value:
k, v = value.split("=", 1)
groups[classify_udev_key(k)].append((k, v))
else:
groups["其他属性"].append(("属性", value))
return groups
def get_host_ips():
result = []
for ifname in list_ifaces():
ip = get_iface_ip(ifname)
if ip:
result.append((ifname, ip))
return result
def render_kv_table(items):
if not items:
return '<div class="empty">无</div>'
rows = []
for k, v in items:
rows.append(
f"<tr><td>{html.escape(str(k))}</td><td>{html.escape(str(v))}</td></tr>"
)
return '<table class="kv">' + "".join(rows) + "</table>"
def render_udev_sections(groups, ifname):
order = ["基础信息", "系统路径", "命名信息", "总线硬件", "网络属性", "其他属性", "符号链接"]
blocks = []
for name in order:
blocks.append(f"""
<details class="subbox" open>
<summary>{html.escape(name)}</summary>
{render_kv_table(groups.get(name, []))}
</details>
""")
return f"""
<details class="udev-box" open>
<summary>查看整理后的 udevadm 信息:/sys/class/net/{html.escape(ifname)}</summary>
{''.join(blocks)}
</details>
"""
def build_iface_card(ifname):
ip4_list = get_iface_ipv4_list(ifname)
ipv6_list = get_iface_ipv6_list(ifname)
mac = get_iface_mac(ifname)
state = get_iface_state(ifname)
mtu = get_iface_mtu(ifname)
speed = get_iface_speed(ifname)
duplex = get_iface_duplex(ifname)
carrier = get_iface_carrier(ifname)
iftype = get_iface_type(ifname)
udev_groups = get_udevadm_grouped(ifname)
ip4_html = "<br>".join(html.escape(x) for x in ip4_list) if ip4_list else "无"
ip6_html = "<br>".join(html.escape(x) for x in ipv6_list) if ipv6_list else "无"
return f"""
<div class="card">
<div class="title">{html.escape(ifname)}</div>
<table class="main">
<tr><td>类型</td><td>{html.escape(iftype)}</td></tr>
<tr><td>状态</td><td>{html.escape(state) if state else '未知'}</td></tr>
<tr><td>载波</td><td>{'已连接' if carrier == '1' else ('未连接' if carrier == '0' else '未知')}</td></tr>
<tr><td>MAC</td><td>{html.escape(mac) if mac else '无'}</td></tr>
<tr><td>MTU</td><td>{html.escape(mtu) if mtu else '未知'}</td></tr>
<tr><td>IPv4</td><td>{ip4_html}</td></tr>
<tr><td>IPv6</td><td>{ip6_html}</td></tr>
<tr><td>Speed</td><td>{html.escape(speed) + ' Mb/s' if speed else '未知'}</td></tr>
<tr><td>Duplex</td><td>{html.escape(duplex) if duplex else '未知'}</td></tr>
</table>
{render_udev_sections(udev_groups, ifname)}
</div>
"""
def build_page():
iface_names = list_ifaces()
cards = "".join(build_iface_card(name) for name in iface_names)
ip_links = []
for ifname, ip in get_host_ips():
ip_links.append(
f'<a href="http://{html.escape(ip)}:{PORT}" target="_blank">{html.escape(ifname)}: {html.escape(ip)}</a>'
)
ip_links_html = "<br>".join(ip_links) if ip_links else "未检测到可访问 IPv4"
now = time.strftime("%Y-%m-%d %H:%M:%S")
return f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网卡信息</title>
<style>
body {{
margin: 0;
padding: 20px;
font-family: Arial, "Microsoft YaHei", sans-serif;
background: #f5f7fb;
color: #222;
}}
.wrap {{
max-width: 1280px;
margin: 0 auto;
}}
.top {{
background: #fff;
border-radius: 14px;
padding: 18px;
box-shadow: 0 2px 12px rgba(0,0,0,.08);
margin-bottom: 18px;
}}
h1 {{
margin: 0 0 10px 0;
font-size: 28px;
}}
.muted {{
color: #666;
font-size: 14px;
}}
.grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
gap: 16px;
}}
.card {{
background: #fff;
border-radius: 14px;
padding: 16px;
box-shadow: 0 2px 12px rgba(0,0,0,.08);
}}
.title {{
font-size: 22px;
font-weight: bold;
margin-bottom: 12px;
color: #0b57d0;
}}
table {{
width: 100%;
border-collapse: collapse;
}}
.main td {{
border-bottom: 1px solid #eee;
padding: 8px 6px;
vertical-align: top;
word-break: break-all;
}}
.main td:first-child {{
width: 90px;
color: #666;
}}
.kv td {{
border-bottom: 1px solid #f0f0f0;
padding: 7px 6px;
vertical-align: top;
word-break: break-all;
}}
.kv td:first-child {{
width: 160px;
color: #666;
}}
details {{
margin-top: 10px;
}}
summary {{
cursor: pointer;
color: #0b57d0;
user-select: none;
}}
.udev-box {{
margin-top: 12px;
padding-top: 6px;
}}
.subbox {{
background: #fafbff;
border: 1px solid #edf1f7;
border-radius: 10px;
padding: 8px 10px;
margin-top: 10px;
}}
.empty {{
color: #888;
padding: 6px 2px;
}}
a {{
color: #0b57d0;
text-decoration: none;
}}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<h1>所有网卡信息</h1>
<div class="muted">刷新时间:{html.escape(now)}</div>
<div style="margin-top:10px;"><b>访问地址</b><br>{ip_links_html}</div>
</div>
<div class="grid">
{cards if cards else '<div class="card">没有读取到网卡</div>'}
</div>
</div>
</body>
</html>"""
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = build_page()
data = body.encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def log_message(self, format, *args):
return
if __name__ == "__main__":
server = ThreadingHTTPServer(("0.0.0.0", PORT), Handler)
print(f"访问地址: http://0.0.0.0:{PORT}")
for ifname, ip in get_host_ips():
print(f"http://{ip}:{PORT} [{ifname}]")
server.serve_forever()


