303 lines
9.8 KiB
C#
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.

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<string> StartScanAsync(string networkSegment, string subnetMask);
ScanStatus? GetScanStatus(string taskId);
void CancelScan(string taskId);
Task<object> 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<AmtScanService> _logger;
private static readonly ConcurrentDictionary<string, ScanStatus> _scanTasks = new();
private static readonly ConcurrentDictionary<string, CancellationTokenSource> _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<AmtScanService> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
public async Task<string> 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<string> ipList, CancellationToken cancellationToken)
{
var status = _scanTasks[taskId];
var foundDevices = new ConcurrentBag<AmtPendingDevice_new>();
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<AppDbContext>();
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<AmtPendingDevice_new?> 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<bool> 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<string> GenerateIpList(string networkSegment, string subnetMask)
{
var ipList = new List<string>();
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;
}
/// <summary>
/// 测试单个IP的AMT端口用于调试
/// </summary>
public async Task<object> 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
};
}
}
}