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 } } }