✨ feat(agent): 添加 token 鉴权和流程文档
* 默认监听调整为 0.0.0.0:8080,并要求查询参数 token * 支持首次 run 自动生成含随机 token 的配置文件 * 补充 HTTP 鉴权、配置校验和相关测试 * 完善代码注释,覆盖缓存、上报、服务和平台采集路径 * 新增架构与开发流程文档,补充一键构建脚本
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"mfd-monitor-agent/internal/status"
|
||||
)
|
||||
|
||||
// Windows API 常量保持与 Win32 头文件一致,用于 syscall 调用和结果解析。
|
||||
const (
|
||||
windowsAFInet = 2
|
||||
windowsAFInet6 = 23
|
||||
@@ -42,6 +43,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// 下列 LazyDLL/Proc 是 Windows 原生 API 入口,按需加载以保持单二进制无外部包装依赖。
|
||||
windowsKernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
windowsGlobalMemoryStatusExProc = windowsKernel32.NewProc("GlobalMemoryStatusEx")
|
||||
windowsGetTickCount64Proc = windowsKernel32.NewProc("GetTickCount64")
|
||||
@@ -63,9 +65,13 @@ var (
|
||||
)
|
||||
|
||||
// windowsMemoryStatus 镜像 GlobalMemoryStatusEx 使用的 Windows MEMORYSTATUSEX 结构。
|
||||
// 字段顺序和宽度必须匹配 Win32 ABI,不应随 Go 风格重排。
|
||||
type windowsMemoryStatus struct {
|
||||
Length uint32
|
||||
MemoryLoad uint32
|
||||
// Length 必须在调用前设置为结构体大小。
|
||||
Length uint32
|
||||
// MemoryLoad 是 Windows 返回的内存使用率百分比。
|
||||
MemoryLoad uint32
|
||||
// TotalPhys 和 AvailPhys 是物理内存总量与可用量,单位为字节。
|
||||
TotalPhys uint64
|
||||
AvailPhys uint64
|
||||
TotalPageFile uint64
|
||||
@@ -76,8 +82,10 @@ type windowsMemoryStatus struct {
|
||||
}
|
||||
|
||||
// windowsMIBTCPRowOwnerPID 镜像 GetExtendedTcpTable 返回的 MIB_TCPROW_OWNER_PID 行。
|
||||
// 字段布局必须与 iphlpapi 的 IPv4 TCP 表行一致。
|
||||
type windowsMIBTCPRowOwnerPID struct {
|
||||
State uint32
|
||||
State uint32
|
||||
// LocalAddr 和 LocalPort 使用 Windows API 的原始网络字节序表示。
|
||||
LocalAddr uint32
|
||||
LocalPort uint32
|
||||
RemoteAddr uint32
|
||||
@@ -86,8 +94,10 @@ type windowsMIBTCPRowOwnerPID struct {
|
||||
}
|
||||
|
||||
// windowsMIBTCP6RowOwnerPID 镜像 GetExtendedTcpTable 返回的 MIB_TCP6ROW_OWNER_PID 行。
|
||||
// 字段布局必须与 iphlpapi 的 IPv6 TCP 表行一致。
|
||||
type windowsMIBTCP6RowOwnerPID struct {
|
||||
State uint32
|
||||
State uint32
|
||||
// LocalAddr 是网络序 IPv6 地址字节。
|
||||
LocalAddr [16]byte
|
||||
LocalScopeID uint32
|
||||
LocalPort uint32
|
||||
@@ -98,6 +108,7 @@ type windowsMIBTCP6RowOwnerPID struct {
|
||||
}
|
||||
|
||||
// windowsSystemInfo 镜像 CPU 封装规模计算所需的 SYSTEM_INFO 子集。
|
||||
// 字段顺序必须匹配 Win32 ABI,当前仅使用处理器数量与掩码字段。
|
||||
type windowsSystemInfo struct {
|
||||
ProcessorArchitecture uint16
|
||||
Reserved uint16
|
||||
@@ -120,6 +131,7 @@ type windowsSystemTimes struct {
|
||||
}
|
||||
|
||||
// windowsLogicalProcessorInformation 镜像用于统计核心的 SYSTEM_LOGICAL_PROCESSOR_INFORMATION。
|
||||
// Data 只作为占位保留 ABI 大小,当前仅读取 Relationship。
|
||||
type windowsLogicalProcessorInformation struct {
|
||||
ProcessorMask uintptr
|
||||
Relationship uint32
|
||||
@@ -127,6 +139,7 @@ type windowsLogicalProcessorInformation struct {
|
||||
}
|
||||
|
||||
// windowsProcessMemoryCounters 镜像 PROCESS_MEMORY_COUNTERS。
|
||||
// 字段宽度依赖平台指针大小,必须保持与 psapi 返回结构一致。
|
||||
type windowsProcessMemoryCounters struct {
|
||||
CB uint32
|
||||
PageFaultCount uint32
|
||||
@@ -142,14 +155,20 @@ type windowsProcessMemoryCounters struct {
|
||||
|
||||
// windowsProcessDetails 包含需额外句柄或命令才能获取的尽力进程字段。
|
||||
type windowsProcessDetails struct {
|
||||
Exe string
|
||||
Cmdline string
|
||||
Username string
|
||||
Status string
|
||||
StartTime int64
|
||||
// Exe 是完整映像路径;权限不足时为空。
|
||||
Exe string
|
||||
// Cmdline 是命令行文本;WMIC 不可用或超时时为空。
|
||||
Cmdline string
|
||||
// Username 是 DOMAIN\user;令牌读取失败时为空。
|
||||
Username string
|
||||
Status string
|
||||
// StartTime 是 Unix 秒级时间戳。
|
||||
StartTime int64
|
||||
// CPUPercent 是生命周期平均 CPU 占用百分比。
|
||||
CPUPercent float64
|
||||
MemoryRSS uint64
|
||||
MemoryVMS uint64
|
||||
// MemoryRSS 和 MemoryVMS 单位为字节。
|
||||
MemoryRSS uint64
|
||||
MemoryVMS uint64
|
||||
}
|
||||
|
||||
// collectSystem 填充 Windows 主机标识、操作系统与架构字段。
|
||||
@@ -204,12 +223,14 @@ func collectResource(ctx context.Context, out *status.HostStatus) error {
|
||||
out.CPU.UsagePercent = windowsReadCPUUsagePercent(ctx)
|
||||
memory, err := windowsReadMemory()
|
||||
if err != nil {
|
||||
// 内存失败不阻止 CPU/磁盘字段返回,交由 snapshot 标记 partial。
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
out.Memory = memory
|
||||
}
|
||||
disks, err := windowsReadDisks()
|
||||
if err != nil {
|
||||
// 单个资源子项失败通过 joined error 上报,保留其他成功资源。
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
out.Disks = disks
|
||||
@@ -234,6 +255,7 @@ func collectSockets(_ context.Context, out *status.HostStatus) error {
|
||||
processes, processErr := windowsProcessMap()
|
||||
var errs []error
|
||||
if processErr != nil {
|
||||
// 进程元数据失败时仍可返回 TCP 表,只是进程名/路径映射可能为空。
|
||||
errs = append(errs, processErr)
|
||||
}
|
||||
listeners4, err := windowsTCPListeners(windowsAFInet, "ipv4", processes)
|
||||
@@ -290,6 +312,7 @@ func windowsUptimeSeconds() uint64 {
|
||||
// windowsPhysicalCores 从 Windows 处理器拓扑返回物理 CPU 核心数。
|
||||
func windowsPhysicalCores() int {
|
||||
var size uint32
|
||||
// Windows 典型模式:先查询所需缓冲区大小,再用正确大小重新调用。
|
||||
result, _, _ := windowsGetLogicalProcessorInfoProc.Call(0, uintptr(unsafe.Pointer(&size)))
|
||||
if result == 0 && size > 0 {
|
||||
buf := make([]byte, size)
|
||||
@@ -308,6 +331,7 @@ func windowsPhysicalCores() int {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 老系统或 API 失败时退回到 SYSTEM_INFO,再退回 runtime.NumCPU。
|
||||
var info windowsSystemInfo
|
||||
windowsGetNativeSystemInfoProc.Call(uintptr(unsafe.Pointer(&info)))
|
||||
if info.NumberOfProcessors > 0 {
|
||||
@@ -412,6 +436,7 @@ func windowsReadDisks() ([]status.DiskInfo, error) {
|
||||
rootPtr, _ := syscall.UTF16PtrFromString(root)
|
||||
driveType, _, _ := windowsGetDriveTypeWProc.Call(uintptr(unsafe.Pointer(rootPtr)))
|
||||
if driveType != windowsDriveFixed && driveType != windowsDriveRemote {
|
||||
// 首版只暴露固定盘和网络盘,跳过光驱、可移动盘等不稳定介质。
|
||||
continue
|
||||
}
|
||||
var freeAvailable, total, free uint64
|
||||
@@ -422,6 +447,7 @@ func windowsReadDisks() ([]status.DiskInfo, error) {
|
||||
uintptr(unsafe.Pointer(&free)),
|
||||
)
|
||||
if ok == 0 {
|
||||
// 个别盘符失败不影响其他磁盘,错误汇总后让快照降级为 partial。
|
||||
errs = append(errs, fmt.Errorf("%s: %w", root, err))
|
||||
continue
|
||||
}
|
||||
@@ -493,6 +519,7 @@ func windowsTCPListeners(family int, familyName string, processes map[int]status
|
||||
// windowsExtendedTCPTable 调用 GetExtendedTcpTable 并返回原始表字节。
|
||||
func windowsExtendedTCPTable(family int) ([]byte, error) {
|
||||
var size uint32
|
||||
// 第一次调用用于获取所需缓冲区大小,ERROR_INSUFFICIENT_BUFFER 是预期结果。
|
||||
result, _, _ := windowsGetExtendedTCPTableProc.Call(
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
@@ -508,6 +535,7 @@ func windowsExtendedTCPTable(family int) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
table := make([]byte, size)
|
||||
// 第二次调用用已分配缓冲区读取包含行数和行数据的原始表。
|
||||
result, _, _ = windowsGetExtendedTCPTableProc.Call(
|
||||
uintptr(unsafe.Pointer(&table[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
@@ -529,6 +557,7 @@ func windowsParseTCP4Rows(rows []byte, count uint32, processes map[int]status.Pr
|
||||
for i := 0; i < int(count); i++ {
|
||||
offset := i * rowSize
|
||||
if offset+rowSize > len(rows) {
|
||||
// 防御损坏或截断的 API 返回,避免 unsafe 读取越界。
|
||||
break
|
||||
}
|
||||
row := *(*windowsMIBTCPRowOwnerPID)(unsafe.Pointer(&rows[offset]))
|
||||
@@ -536,6 +565,7 @@ func windowsParseTCP4Rows(rows []byte, count uint32, processes map[int]status.Pr
|
||||
continue
|
||||
}
|
||||
process := processes[int(row.OwningPID)]
|
||||
// PID 映射缺失时 process 为零值,仍返回端口本身供调用方排障。
|
||||
addr := row.LocalAddr
|
||||
items = append(items, status.TCPListenerInfo{
|
||||
IP: net.IPv4(byte(addr), byte(addr>>8), byte(addr>>16), byte(addr>>24)).String(),
|
||||
@@ -553,6 +583,7 @@ func windowsParseTCP6Rows(rows []byte, count uint32, processes map[int]status.Pr
|
||||
for i := 0; i < int(count); i++ {
|
||||
offset := i * rowSize
|
||||
if offset+rowSize > len(rows) {
|
||||
// 防御损坏或截断的 API 返回,避免 unsafe 读取越界。
|
||||
break
|
||||
}
|
||||
row := *(*windowsMIBTCP6RowOwnerPID)(unsafe.Pointer(&rows[offset]))
|
||||
@@ -560,6 +591,7 @@ func windowsParseTCP6Rows(rows []byte, count uint32, processes map[int]status.Pr
|
||||
continue
|
||||
}
|
||||
process := processes[int(row.OwningPID)]
|
||||
// PID 映射缺失时 process 为零值,仍返回端口本身供调用方排障。
|
||||
items = append(items, status.TCPListenerInfo{
|
||||
IP: net.IP(row.LocalAddr[:]).String(), Port: windowsTCPPortFromNetworkOrder(row.LocalPort),
|
||||
PID: int(row.OwningPID), ProcessName: process.Name, ProcessExe: process.Exe, Family: "ipv6",
|
||||
@@ -623,6 +655,7 @@ func windowsProcessInfoFromEntry(entry syscall.ProcessEntry32, details windowsPr
|
||||
func windowsReadProcessDetails(pid uint32) windowsProcessDetails {
|
||||
handle, err := syscall.OpenProcess(windowsProcessQueryLimitedInfo|windowsProcessQueryInformation|windowsProcessVMRead, false, pid)
|
||||
if err != nil {
|
||||
// 系统进程或权限不足时无法打开句柄,调用方仍保留 Toolhelp 的基础进程记录。
|
||||
return windowsProcessDetails{Status: "unknown"}
|
||||
}
|
||||
defer syscall.CloseHandle(handle)
|
||||
@@ -739,6 +772,7 @@ func windowsProcessCommandLines() map[int]string {
|
||||
defer cancel()
|
||||
output, err := exec.CommandContext(ctx, "wmic", "process", "get", "ProcessId,CommandLine", "/format:csv").Output()
|
||||
if err != nil {
|
||||
// WMIC 在新系统上可能不可用;命令行字段按空值降级。
|
||||
return out
|
||||
}
|
||||
reader := csv.NewReader(bytes.NewReader(output))
|
||||
|
||||
Reference in New Issue
Block a user