232 lines
7.9 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.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);
// 查找UUIDPlatformGUID
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();
}
}