using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using AmtScanner.Api.Data; using AmtScanner.Api.Models; using Microsoft.EntityFrameworkCore; namespace AmtScanner.Api.Services; public interface IAmtScanService { Task StartScanAsync(string networkSegment, string subnetMask); ScanStatus? GetScanStatus(string taskId); void CancelScan(string taskId); Task TestSingleIpAsync(string ip); } public class ScanStatus { public string TaskId { get; set; } = string.Empty; public string Status { get; set; } = "idle"; // idle, running, completed, cancelled public int TotalCount { get; set; } private int _scannedCount; private int _foundDevices; public int ScannedCount { get => _scannedCount; set => _scannedCount = value; } public int FoundDevices { get => _foundDevices; set => _foundDevices = value; } public void IncrementScanned() => Interlocked.Increment(ref _scannedCount); public void IncrementFound() => Interlocked.Increment(ref _foundDevices); } public class AmtScanService : IAmtScanService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private static readonly ConcurrentDictionary _scanTasks = new(); private static readonly ConcurrentDictionary _cancellationTokens = new(); // AMT端口: 16992 (HTTP), 16993 (HTTPS) private const int AMT_HTTP_PORT = 16992; private const int AMT_HTTPS_PORT = 16993; private const int TIMEOUT_MS = 1000; // TCP连接超时1秒足够 public AmtScanService(IServiceScopeFactory scopeFactory, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; } public async Task StartScanAsync(string networkSegment, string subnetMask) { var taskId = Guid.NewGuid().ToString("N"); var cts = new CancellationTokenSource(); var ipList = GenerateIpList(networkSegment, subnetMask); var status = new ScanStatus { TaskId = taskId, Status = "running", TotalCount = ipList.Count, ScannedCount = 0, FoundDevices = 0 }; _scanTasks[taskId] = status; _cancellationTokens[taskId] = cts; // 后台执行扫描 _ = Task.Run(async () => await ExecuteScanAsync(taskId, ipList, cts.Token)); return taskId; } public ScanStatus? GetScanStatus(string taskId) { return _scanTasks.TryGetValue(taskId, out var status) ? status : null; } public void CancelScan(string taskId) { if (_cancellationTokens.TryGetValue(taskId, out var cts)) { cts.Cancel(); if (_scanTasks.TryGetValue(taskId, out var status)) { status.Status = "cancelled"; } } } private async Task ExecuteScanAsync(string taskId, List ipList, CancellationToken cancellationToken) { var status = _scanTasks[taskId]; var foundDevices = new ConcurrentBag(); try { // 并行扫描,TCP端口扫描可以用更高并发 var options = new ParallelOptions { MaxDegreeOfParallelism = 100, CancellationToken = cancellationToken }; await Parallel.ForEachAsync(ipList, options, async (ip, ct) => { try { var result = await ScanSingleIpAsync(ip, ct); if (result != null) { foundDevices.Add(result); status.IncrementFound(); } } catch (Exception ex) { _logger.LogDebug("扫描 {Ip} 失败: {Error}", ip, ex.Message); } finally { status.IncrementScanned(); } }); // 保存发现的设备到数据库 if (foundDevices.Any()) { using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); foreach (var device in foundDevices) { // 检查是否已存在 var existing = await db.AmtPendingDevices_new .FirstOrDefaultAsync(d => d.IpAddress == device.IpAddress); if (existing == null) { db.AmtPendingDevices_new.Add(device); } else { // 更新已存在的设备信息 existing.AmtVersion = device.AmtVersion; existing.ProvisioningState = device.ProvisioningState; existing.UpdatedAt = DateTime.UtcNow; } } await db.SaveChangesAsync(); _logger.LogInformation("扫描完成,发现 {Count} 个AMT设备", foundDevices.Count); } status.Status = "completed"; } catch (OperationCanceledException) { status.Status = "cancelled"; _logger.LogInformation("扫描任务 {TaskId} 已取消", taskId); } catch (Exception ex) { status.Status = "completed"; _logger.LogError(ex, "扫描任务 {TaskId} 出错", taskId); } finally { // 清理 _cancellationTokens.TryRemove(taskId, out _); } } private async Task ScanSingleIpAsync(string ip, CancellationToken ct) { try { // 检测16992和16993端口 bool httpOpen = await IsPortOpenAsync(ip, AMT_HTTP_PORT, TIMEOUT_MS, ct); bool httpsOpen = await IsPortOpenAsync(ip, AMT_HTTPS_PORT, TIMEOUT_MS, ct); if (httpOpen || httpsOpen) { string portInfo = ""; if (httpOpen && httpsOpen) portInfo = "16992,16993"; else if (httpOpen) portInfo = "16992"; else portInfo = "16993"; _logger.LogInformation("发现AMT设备: {Ip}, 开放端口: {Ports}", ip, portInfo); return new AmtPendingDevice_new { IpAddress = ip, AmtVersion = "Unknown", // 需要通过WS-Man获取 ProvisioningState = httpsOpen ? "Post" : "Pre", // HTTPS开放通常表示已配置 Source = "scan", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; } return null; } catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; } catch { return null; } } private static async Task IsPortOpenAsync(string ip, int port, int timeoutMs, CancellationToken ct) { try { using var client = new TcpClient(); using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); cts.CancelAfter(timeoutMs); await client.ConnectAsync(IPAddress.Parse(ip), port, cts.Token); return true; } catch { return false; } } private static List GenerateIpList(string networkSegment, string subnetMask) { var ipList = new List(); var networkBytes = IPAddress.Parse(networkSegment).GetAddressBytes(); var maskBytes = IPAddress.Parse(subnetMask).GetAddressBytes(); // 计算网络地址和广播地址 var networkAddress = new byte[4]; var broadcastAddress = new byte[4]; for (int i = 0; i < 4; i++) { networkAddress[i] = (byte)(networkBytes[i] & maskBytes[i]); broadcastAddress[i] = (byte)(networkBytes[i] | ~maskBytes[i]); } // 生成IP列表(排除网络地址和广播地址) var startIp = BitConverter.ToUInt32(networkAddress.Reverse().ToArray(), 0) + 1; var endIp = BitConverter.ToUInt32(broadcastAddress.Reverse().ToArray(), 0) - 1; for (uint ip = startIp; ip <= endIp; ip++) { var bytes = BitConverter.GetBytes(ip).Reverse().ToArray(); ipList.Add(new IPAddress(bytes).ToString()); } return ipList; } /// /// 测试单个IP的AMT端口,用于调试 /// public async Task TestSingleIpAsync(string ip) { try { bool httpOpen = await IsPortOpenAsync(ip, AMT_HTTP_PORT, 2000, CancellationToken.None); bool httpsOpen = await IsPortOpenAsync(ip, AMT_HTTPS_PORT, 2000, CancellationToken.None); return new { success = httpOpen || httpsOpen, ip = ip, port16992 = httpOpen, port16993 = httpsOpen, isAmtDevice = httpOpen || httpsOpen }; } catch (Exception ex) { return new { success = false, ip = ip, error = ex.Message, errorType = ex.GetType().Name }; } } }