232 lines
7.9 KiB
C#
232 lines
7.9 KiB
C#
using System.Net;
|
||
using System.Net.Security;
|
||
using System.Security.Cryptography;
|
||
using System.Security.Cryptography.X509Certificates;
|
||
using System.Text;
|
||
using System.Xml.Linq;
|
||
|
||
namespace AmtScanner.Api.Services;
|
||
|
||
public interface IAmtWsmanService
|
||
{
|
||
Task<string?> GetUuidAsync(string ip, string username, string password);
|
||
Task<AmtSystemInfo?> GetSystemInfoAsync(string ip, string username, string password);
|
||
}
|
||
|
||
public class AmtSystemInfo
|
||
{
|
||
public string? Uuid { get; set; }
|
||
public string? Hostname { get; set; }
|
||
public string? Manufacturer { get; set; }
|
||
public string? Model { get; set; }
|
||
public string? SerialNumber { get; set; }
|
||
}
|
||
|
||
public class AmtWsmanService : IAmtWsmanService
|
||
{
|
||
private readonly ILogger<AmtWsmanService> _logger;
|
||
private const int AMT_PORT = 16992;
|
||
private const int AMT_TLS_PORT = 16993;
|
||
|
||
public AmtWsmanService(ILogger<AmtWsmanService> logger)
|
||
{
|
||
_logger = logger;
|
||
}
|
||
|
||
public async Task<string?> GetUuidAsync(string ip, string username, string password)
|
||
{
|
||
try
|
||
{
|
||
var info = await GetSystemInfoAsync(ip, username, password);
|
||
return info?.Uuid;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "获取UUID失败: {Ip}", ip);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public async Task<AmtSystemInfo?> GetSystemInfoAsync(string ip, string username, string password)
|
||
{
|
||
// 构建WS-Management请求获取CIM_ComputerSystemPackage
|
||
var soapEnvelope = BuildGetUuidRequest(ip, AMT_PORT);
|
||
|
||
try
|
||
{
|
||
// 先尝试非TLS端口
|
||
var response = await SendWsmanRequestWithDigestAsync(ip, AMT_PORT, username, password, soapEnvelope, false);
|
||
if (response != null)
|
||
{
|
||
_logger.LogDebug("非TLS连接成功,响应长度: {Length}", response.Length);
|
||
return ParseSystemInfo(response);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogDebug("非TLS连接失败,尝试TLS: {Error}", ex.Message);
|
||
}
|
||
|
||
try
|
||
{
|
||
// 尝试TLS端口
|
||
soapEnvelope = BuildGetUuidRequest(ip, AMT_TLS_PORT);
|
||
var response = await SendWsmanRequestWithDigestAsync(ip, AMT_TLS_PORT, username, password, soapEnvelope, true);
|
||
if (response != null)
|
||
{
|
||
_logger.LogDebug("TLS连接成功,响应长度: {Length}", response.Length);
|
||
return ParseSystemInfo(response);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "TLS连接也失败: {Ip}", ip);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用 Digest 认证发送 WS-Man 请求
|
||
/// </summary>
|
||
private async Task<string?> SendWsmanRequestWithDigestAsync(string ip, int port, string username, string password, string soapEnvelope, bool useTls)
|
||
{
|
||
var protocol = useTls ? "https" : "http";
|
||
var url = $"{protocol}://{ip}:{port}/wsman";
|
||
|
||
_logger.LogDebug("尝试连接: {Url}", url);
|
||
|
||
// 创建 HttpClientHandler,配置 Digest 认证
|
||
var handler = new HttpClientHandler
|
||
{
|
||
Credentials = new CredentialCache
|
||
{
|
||
{ new Uri($"{protocol}://{ip}:{port}"), "Digest", new NetworkCredential(username, password) }
|
||
},
|
||
PreAuthenticate = false,
|
||
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
|
||
};
|
||
|
||
using var client = new HttpClient(handler)
|
||
{
|
||
Timeout = TimeSpan.FromSeconds(15)
|
||
};
|
||
|
||
var content = new StringContent(soapEnvelope, Encoding.UTF8, "application/soap+xml");
|
||
|
||
try
|
||
{
|
||
var response = await client.PostAsync(url, content);
|
||
|
||
_logger.LogDebug("响应状态码: {StatusCode}", response.StatusCode);
|
||
|
||
if (response.IsSuccessStatusCode)
|
||
{
|
||
var responseContent = await response.Content.ReadAsStringAsync();
|
||
_logger.LogDebug("响应内容: {Content}", responseContent.Length > 500 ? responseContent[..500] + "..." : responseContent);
|
||
return responseContent;
|
||
}
|
||
|
||
var errorContent = await response.Content.ReadAsStringAsync();
|
||
_logger.LogWarning("WS-Management请求失败: {StatusCode}, 响应: {Response}", response.StatusCode, errorContent);
|
||
return null;
|
||
}
|
||
catch (HttpRequestException ex)
|
||
{
|
||
_logger.LogWarning("HTTP请求异常: {Message}", ex.Message);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
private static string BuildGetUuidRequest(string ip, int port)
|
||
{
|
||
// 使用 Intel AMT SDK 的 GET_XML 格式
|
||
var toAddress = $"http://{ip}:{port}/wsman";
|
||
var messageId = $"uuid:{Guid.NewGuid()}";
|
||
|
||
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
|
||
<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope""
|
||
xmlns:a=""http://schemas.xmlsoap.org/ws/2004/08/addressing""
|
||
xmlns:w=""http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"">
|
||
<s:Header>
|
||
<a:To>{toAddress}</a:To>
|
||
<w:ResourceURI s:mustUnderstand=""true"">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystemPackage</w:ResourceURI>
|
||
<a:ReplyTo>
|
||
<a:Address s:mustUnderstand=""true"">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||
</a:ReplyTo>
|
||
<a:Action s:mustUnderstand=""true"">http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</a:Action>
|
||
<w:MaxEnvelopeSize s:mustUnderstand=""true"">153600</w:MaxEnvelopeSize>
|
||
<a:MessageID>{messageId}</a:MessageID>
|
||
<w:Locale xml:lang=""en-US"" s:mustUnderstand=""false"" />
|
||
<w:OperationTimeout>PT60.000S</w:OperationTimeout>
|
||
</s:Header>
|
||
<s:Body/>
|
||
</s:Envelope>";
|
||
}
|
||
|
||
private AmtSystemInfo? ParseSystemInfo(string xmlResponse)
|
||
{
|
||
try
|
||
{
|
||
var doc = XDocument.Parse(xmlResponse);
|
||
|
||
// 查找UUID(PlatformGUID)
|
||
var uuid = FindElementValue(doc, "PlatformGUID");
|
||
|
||
// 如果没找到PlatformGUID,尝试其他方式
|
||
if (string.IsNullOrEmpty(uuid))
|
||
{
|
||
// 尝试从CIM_Chassis获取UUID
|
||
uuid = FindElementValue(doc, "UUID");
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(uuid))
|
||
{
|
||
// 尝试从Tag字段获取
|
||
uuid = FindElementValue(doc, "Tag");
|
||
}
|
||
|
||
// 格式化UUID(如果需要)
|
||
if (!string.IsNullOrEmpty(uuid))
|
||
{
|
||
uuid = FormatUuid(uuid);
|
||
}
|
||
|
||
return new AmtSystemInfo
|
||
{
|
||
Uuid = uuid,
|
||
Hostname = FindElementValue(doc, "Name") ?? FindElementValue(doc, "ElementName"),
|
||
Manufacturer = FindElementValue(doc, "Manufacturer"),
|
||
Model = FindElementValue(doc, "Model"),
|
||
SerialNumber = FindElementValue(doc, "SerialNumber")
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "解析WS-Management响应失败");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private static string? FindElementValue(XDocument doc, string localName)
|
||
{
|
||
var element = doc.Descendants()
|
||
.FirstOrDefault(e => e.Name.LocalName.Equals(localName, StringComparison.OrdinalIgnoreCase));
|
||
return element?.Value;
|
||
}
|
||
|
||
private static string FormatUuid(string uuid)
|
||
{
|
||
// 移除可能的前缀和格式化
|
||
uuid = uuid.Replace("uuid:", "").Replace("-", "").Trim();
|
||
|
||
if (uuid.Length == 32)
|
||
{
|
||
// 格式化为标准UUID格式
|
||
return $"{uuid[..8]}-{uuid[8..12]}-{uuid[12..16]}-{uuid[16..20]}-{uuid[20..]}".ToLower();
|
||
}
|
||
|
||
return uuid.ToLower();
|
||
}
|
||
}
|