新增 auto-check 工具:网段扫描+SSH登录+压力测试永久循环工作流

- pkg/discovery: TCP端口探测存活主机,ARP表解析MAC作为设备唯一标识
- pkg/sshclient: SSH连接(密码/密钥认证)与远程命令执行
- pkg/stress: stress-ng/stressapptest 压测(cpu/memory/disk/memnative/full)+温度与dmesg监控
- pkg/report: 检测报告生成(文本/JSON)
- pkg/workflow: 永久循环工作流(间隔可配),MAC唯一标识设备,增量测试+状态变化打印
- pkg/config: YAML分类配置(scan/ssh/stress/report/workflow)
- cmd: cobra入口,仅 --config 指定配置文件
This commit is contained in:
张威33321
2026-08-10 20:30:23 +08:00
parent c6d83a4a94
commit 19f6050147
18 changed files with 2077 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
package discovery
import (
"fmt"
"net"
"os"
"os/exec"
"runtime"
"strings"
"sync"
"time"
)
// Host 存活主机信息
type Host struct {
IP net.IP
MAC string // 物理地址(设备唯一标识)
Alive bool
OpenPort int
}
// Scanner 网段扫描器
type Scanner struct {
CIDR string
Port int
Timeout time.Duration
Concurrency int
}
// NewScanner 创建扫描器
func NewScanner(cidr string, port int, timeout time.Duration, concurrency int) *Scanner {
return &Scanner{
CIDR: cidr,
Port: port,
Timeout: timeout,
Concurrency: concurrency,
}
}
// Scan 扫描网段,返回存活主机列表
func (s *Scanner) Scan() []Host {
ip, ipnet, err := net.ParseCIDR(s.CIDR)
if err != nil {
fmt.Printf("[扫描] 网段解析失败: %v\n", err)
return nil
}
var ips []net.IP
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
dst := make(net.IP, len(ip))
copy(dst, ip)
ips = append(ips, dst)
}
// 排除网络地址和广播地址
if len(ips) > 2 {
ips = ips[1 : len(ips)-1]
}
fmt.Printf("[扫描] 网段 %s共 %d 个IP开始探测...\n", s.CIDR, len(ips))
// 并发探测
var mu sync.Mutex
var wg sync.WaitGroup
var results []Host
sem := make(chan struct{}, s.Concurrency)
for _, ip := range ips {
wg.Add(1)
sem <- struct{}{}
go func(target net.IP) {
defer wg.Done()
defer func() { <-sem }()
h := s.probe(target)
if h.Alive {
// 获取 MAC 地址TCP 探测已触发 ARP 解析)
h.MAC = getMAC(target.String())
mu.Lock()
results = append(results, h)
mu.Unlock()
desc := "无MAC"
if h.MAC != "" {
desc = h.MAC
}
fmt.Printf(" [+] %s 存活 (MAC: %s, 端口 %d 开放)\n", target, desc, h.OpenPort)
}
}(ip)
}
wg.Wait()
fmt.Printf("[扫描] 完成,发现 %d 台存活主机\n", len(results))
return results
}
// probe 探测单个 IP
func (s *Scanner) probe(ip net.IP) Host {
h := Host{IP: ip}
// TCP 端口探测(连接成功会触发本地 ARP 解析)
addr := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", s.Port))
conn, err := net.DialTimeout("tcp", addr, s.Timeout)
if err == nil {
conn.Close()
h.Alive = true
h.OpenPort = s.Port
return h
}
// 备选:尝试常见端口
for _, p := range []int{22, 80, 443, 8080, 3306} {
addr = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", p))
conn, err = net.DialTimeout("tcp", addr, s.Timeout)
if err == nil {
conn.Close()
h.Alive = true
h.OpenPort = p
return h
}
}
return h
}
// getMAC 从 ARP 表获取 IP 对应的 MAC 地址(跨平台)
// 返回空字符串表示无法获取
func getMAC(ip string) string {
switch runtime.GOOS {
case "linux":
return macFromProcNetArp(ip)
case "windows":
return macFromWindowsArp(ip)
case "darwin":
return macFromUnixArp(ip)
default:
return macFromUnixArp(ip)
}
}
// macFromProcNetArp 解析 Linux /proc/net/arp
func macFromProcNetArp(ip string) string {
data, err := os.ReadFile("/proc/net/arp")
if err != nil {
return ""
}
for _, line := range strings.Split(string(data), "\n")[1:] {
fields := strings.Fields(line)
if len(fields) >= 4 && fields[0] == ip {
mac := fields[3]
if mac != "" && mac != "00:00:00:00:00:00" {
return mac
}
}
}
return ""
}
// macFromWindowsArp 解析 Windows `arp -a` 输出
func macFromWindowsArp(ip string) string {
out, err := exec.Command("arp", "-a").Output()
if err != nil {
return ""
}
for _, line := range strings.Split(string(out), "\n") {
fields := strings.Fields(line)
// Windows 格式: IP MAC Type
if len(fields) >= 2 && fields[0] == ip {
mac := strings.ReplaceAll(fields[1], "-", ":")
if mac != "" && mac != "00:00:00:00:00:00" {
return strings.ToLower(mac)
}
}
}
return ""
}
// macFromUnixArp 解析 macOS/Linux `arp -n` 输出
func macFromUnixArp(ip string) string {
out, err := exec.Command("arp", "-n", ip).Output()
if err != nil {
return ""
}
for _, line := range strings.Split(string(out), "\n") {
fields := strings.Fields(line)
// macOS 格式: ? (IP) at MAC on en0 ifscope [ethernet]
for i, f := range fields {
if f == "("+ip+")" && i+2 < len(fields) {
mac := fields[i+2]
if mac != "" && mac != "ff:ff:ff:ff:ff:ff" {
return strings.ToLower(mac)
}
}
}
}
return ""
}
// inc IP 递增
func inc(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}