重构 auto-check:状态数据下沉各包,workflow 精简为纯调度
- discovery: ping 探测存活 + ARP 解析 MAC,包级 Devices map,串行扫描 - stress: 脚本生成模式(BuildScript/ParseResults),ToolSet 工具检测,包级 Results map - report: 包级 Printed map,SaveAndPrintDeviceReport 保存+打印 - sshclient: 包级 RunCommand - workflow: 仅持有 config,Start() 固定循环,直接调用各包方法 - config: 移除 ScanConfig.Concurrency(串行扫描无需并发数)
This commit is contained in:
77
auto-check/pkg/discovery/arp.go
Normal file
77
auto-check/pkg/discovery/arp.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// lookupMAC 从 ARP 表获取 IP 对应的 MAC 地址(跨平台)
|
||||
// 返回空字符串表示无法获取
|
||||
func lookupMAC(ip string) string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return macFromProcNetArp(ip)
|
||||
case "windows":
|
||||
return macFromWindowsArp(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)
|
||||
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)
|
||||
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 ""
|
||||
}
|
||||
23
auto-check/pkg/discovery/discover.go
Normal file
23
auto-check/pkg/discovery/discover.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"auto-check/pkg/model"
|
||||
)
|
||||
|
||||
// Devices 设备表(IP → 设备),每轮 Discover() 后更新,包级公开
|
||||
var Devices = make(map[string]model.Device)
|
||||
|
||||
// Discover 扫描并更新包级 Devices map(过滤无 MAC 设备,保留上次状态延续)
|
||||
func (s *Scanner) Discover() {
|
||||
scanned := s.Scan()
|
||||
Devices = make(map[string]model.Device, len(scanned))
|
||||
for _, dev := range scanned {
|
||||
if dev.MAC == "" {
|
||||
fmt.Printf(" [!] %s 无 MAC(ARP 未解析),下轮重试\n", dev.IP)
|
||||
continue
|
||||
}
|
||||
Devices[dev.IP] = dev
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"auto-check/pkg/model"
|
||||
)
|
||||
|
||||
// Devices 设备表(IP → 设备),每轮 Discover() 后更新,包级公开
|
||||
var Devices = make(map[string]model.Device)
|
||||
|
||||
// Scanner 网段扫描器
|
||||
type Scanner struct {
|
||||
CIDR string
|
||||
Timeout time.Duration
|
||||
Concurrency int
|
||||
}
|
||||
|
||||
// NewScanner 创建扫描器
|
||||
func NewScanner(cidr string, timeout time.Duration, concurrency int) *Scanner {
|
||||
return &Scanner{
|
||||
CIDR: cidr,
|
||||
Timeout: timeout,
|
||||
Concurrency: concurrency,
|
||||
}
|
||||
}
|
||||
|
||||
// Scan 扫描网段,返回存活设备列表
|
||||
func (s *Scanner) Scan() []model.Device {
|
||||
ips, ok := s.candidateIPs()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("[扫描] 网段 %s,共 %d 个IP,开始探测...\n", s.CIDR, len(ips))
|
||||
|
||||
results := s.probeAll(ips)
|
||||
|
||||
fmt.Printf("[扫描] 完成,发现 %d 台存活设备\n", len(results))
|
||||
return results
|
||||
}
|
||||
|
||||
// candidateIPs 解析网段并生成候选 IP 列表(排除网络地址和广播地址)
|
||||
// 网段解析失败时返回 false
|
||||
func (s *Scanner) candidateIPs() ([]net.IP, bool) {
|
||||
ip, ipnet, err := net.ParseCIDR(s.CIDR)
|
||||
if err != nil {
|
||||
fmt.Printf("[扫描] 网段解析失败: %v\n", err)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
return ips, true
|
||||
}
|
||||
|
||||
// probeAll 并发探测所有候选 IP,返回存活设备列表
|
||||
func (s *Scanner) probeAll(ips []net.IP) []model.Device {
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
var results []model.Device
|
||||
|
||||
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 }()
|
||||
|
||||
dev, ok := s.probeDevice(target)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
results = append(results, dev)
|
||||
mu.Unlock()
|
||||
desc := "无MAC"
|
||||
if dev.MAC != "" {
|
||||
desc = dev.MAC
|
||||
}
|
||||
fmt.Printf(" [+] %s 存活 (MAC: %s)\n", target, desc)
|
||||
}(ip)
|
||||
}
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
// probeDevice 探测单个 IP,存活则返回设备信息
|
||||
func (s *Scanner) probeDevice(ip net.IP) (model.Device, bool) {
|
||||
if !s.probe(ip) {
|
||||
return model.Device{}, false
|
||||
}
|
||||
// 获取 MAC 地址(ping 探测已触发 ARP 解析)
|
||||
dev := model.Device{
|
||||
IP: ip.String(),
|
||||
MAC: getMAC(ip.String()),
|
||||
}
|
||||
return dev, true
|
||||
}
|
||||
|
||||
// probe 用 ping 探测单个 IP 是否存活,返回 bool
|
||||
func (s *Scanner) probe(ip net.IP) bool {
|
||||
waitMs := s.Timeout.Milliseconds()
|
||||
if waitMs < 1000 {
|
||||
waitMs = 1000 // ping 等待至少 1 秒
|
||||
}
|
||||
// 各平台 ping 参数不同:Linux -W 秒,macOS/Windows -W/-w 毫秒
|
||||
var args []string
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
args = []string{"-n", "1", "-w", fmt.Sprintf("%d", waitMs), ip.String()}
|
||||
case "darwin":
|
||||
args = []string{"-c", "1", "-W", fmt.Sprintf("%d", waitMs), ip.String()}
|
||||
default:
|
||||
args = []string{"-c", "1", "-W", fmt.Sprintf("%d", waitMs/1000), ip.String()}
|
||||
}
|
||||
return exec.Command("ping", args...).Run() == nil
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
}
|
||||
|
||||
// Discover 扫描并更新包级 Devices map(过滤无 MAC 设备)
|
||||
func (s *Scanner) Discover() {
|
||||
devices := s.Scan()
|
||||
Devices = make(map[string]model.Device, len(devices))
|
||||
for _, dev := range devices {
|
||||
if dev.MAC == "" {
|
||||
fmt.Printf(" [!] %s 无 MAC(ARP 未解析),下轮重试\n", dev.IP)
|
||||
continue
|
||||
}
|
||||
Devices[dev.IP] = dev
|
||||
}
|
||||
}
|
||||
|
||||
// inc IP 递增
|
||||
func inc(ip net.IP) {
|
||||
for j := len(ip) - 1; j >= 0; j-- {
|
||||
ip[j]++
|
||||
if ip[j] > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
106
auto-check/pkg/discovery/scanner.go
Normal file
106
auto-check/pkg/discovery/scanner.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"auto-check/pkg/model"
|
||||
)
|
||||
|
||||
// Scanner 网段扫描器(串行,按顺序逐个探测)
|
||||
type Scanner struct {
|
||||
CIDR string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// NewScanner 创建扫描器
|
||||
func NewScanner(cidr string, timeout time.Duration) *Scanner {
|
||||
return &Scanner{
|
||||
CIDR: cidr,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// Scan 扫描网段,返回存活设备列表(含 MAC 地址)
|
||||
// 串行逐个探测,每个 IP 有超时保护
|
||||
func (s *Scanner) Scan() []model.Device {
|
||||
ips, ok := s.candidateIPs()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("[扫描] 网段 %s,共 %d 个IP,开始探测...\n", s.CIDR, len(ips))
|
||||
|
||||
var results []model.Device
|
||||
for _, ip := range ips {
|
||||
if dev, ok := s.probe(ip); ok {
|
||||
results = append(results, dev)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[扫描] 完成,发现 %d 台存活设备\n", len(results))
|
||||
return results
|
||||
}
|
||||
|
||||
// candidateIPs 解析网段并生成候选 IP 列表(排除网络地址和广播地址)
|
||||
func (s *Scanner) candidateIPs() ([]net.IP, bool) {
|
||||
ip, ipnet, err := net.ParseCIDR(s.CIDR)
|
||||
if err != nil {
|
||||
fmt.Printf("[扫描] 网段解析失败: %v\n", err)
|
||||
return nil, false
|
||||
}
|
||||
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]
|
||||
}
|
||||
return ips, true
|
||||
}
|
||||
|
||||
// probe 探测单个 IP,存活则返回设备信息(MAC 通过 ARP 表查询)
|
||||
func (s *Scanner) probe(ip net.IP) (model.Device, bool) {
|
||||
if !s.isAlive(ip) {
|
||||
return model.Device{}, false
|
||||
}
|
||||
mac := lookupMAC(ip.String())
|
||||
desc := "无MAC"
|
||||
if mac != "" {
|
||||
desc = mac
|
||||
}
|
||||
fmt.Printf(" [+] %s 存活 (MAC: %s)\n", ip, desc)
|
||||
return model.Device{IP: ip.String(), MAC: mac}, true
|
||||
}
|
||||
|
||||
// isAlive 用 ping 探测单个 IP 是否存活(内置超时保护)
|
||||
func (s *Scanner) isAlive(ip net.IP) bool {
|
||||
waitMs := s.Timeout.Milliseconds()
|
||||
if waitMs < 1000 {
|
||||
waitMs = 1000
|
||||
}
|
||||
var args []string
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
args = []string{"-n", "1", "-w", fmt.Sprintf("%d", waitMs), ip.String()}
|
||||
case "darwin":
|
||||
args = []string{"-c", "1", "-W", fmt.Sprintf("%d", waitMs), ip.String()}
|
||||
default:
|
||||
args = []string{"-c", "1", "-W", fmt.Sprintf("%d", waitMs/1000), ip.String()}
|
||||
}
|
||||
return exec.Command("ping", args...).Run() == nil
|
||||
}
|
||||
|
||||
// inc IP 递增
|
||||
func inc(ip net.IP) {
|
||||
for j := len(ip) - 1; j >= 0; j-- {
|
||||
ip[j]++
|
||||
if ip[j] > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user