Files
membank/auto-check/pkg/discovery/discovery.go
张威33321 19f6050147 新增 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 指定配置文件
2026-08-10 20:30:23 +08:00

206 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}
}