335 lines
13 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.Text;
using System.Xml.Linq;
namespace AmtScanner.Api.Services;
public interface IAmtNetworkService
{
Task<AmtNetworkConfig?> GetNetworkConfigAsync(string ip, string username, string password);
Task<bool> SetStaticIpAsync(string ip, string username, string password, string newIp, string subnetMask, string? gateway, string? primaryDns, string? secondaryDns);
Task<bool> SetDhcpModeAsync(string ip, string username, string password);
}
public class AmtNetworkConfig
{
public bool DhcpEnabled { get; set; }
public string? IpAddress { get; set; }
public string? SubnetMask { get; set; }
public string? Gateway { get; set; }
public string? PrimaryDns { get; set; }
public string? SecondaryDns { get; set; }
public string? MacAddress { get; set; }
public bool LinkIsUp { get; set; }
public bool SharedMac { get; set; }
public bool IpSyncEnabled { get; set; }
}
public class AmtNetworkService : IAmtNetworkService
{
private readonly ILogger<AmtNetworkService> _logger;
private const int AMT_PORT = 16992;
// AMT_EthernetPortSettings 的 InstanceID
private const string WIRED_INSTANCE_ID = "Intel(r) AMT Ethernet Port Settings 0";
public AmtNetworkService(ILogger<AmtNetworkService> logger)
{
_logger = logger;
}
public async Task<AmtNetworkConfig?> GetNetworkConfigAsync(string ip, string username, string password)
{
try
{
// 使用 GET 请求配合 SelectorSet 获取 AMT_EthernetPortSettings
var soapEnvelope = BuildGetEthernetPortSettingsRequest(ip, AMT_PORT);
var response = await SendWsmanRequestAsync(ip, AMT_PORT, username, password, soapEnvelope);
if (response == null)
{
_logger.LogWarning("无法获取 AMT_EthernetPortSettings");
return null;
}
_logger.LogDebug("获取到网络配置响应: {Response}", response.Length > 500 ? response[..500] + "..." : response);
return ParseNetworkConfig(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取网络配置失败: {Ip}", ip);
return null;
}
}
public async Task<bool> SetStaticIpAsync(string ip, string username, string password, string newIp, string subnetMask, string? gateway, string? primaryDns, string? secondaryDns)
{
try
{
// 首先获取当前配置
var currentConfig = await GetNetworkConfigAsync(ip, username, password);
if (currentConfig == null)
{
_logger.LogWarning("无法获取当前配置尝试直接设置静态IP");
}
// 构建 PUT 请求设置静态 IP
var soapEnvelope = BuildSetStaticIpRequest(ip, AMT_PORT, newIp, subnetMask, gateway, primaryDns, secondaryDns);
var response = await SendWsmanRequestAsync(ip, AMT_PORT, username, password, soapEnvelope);
if (response == null)
{
_logger.LogWarning("设置静态IP请求无响应");
return false;
}
if (response.Contains("Fault"))
{
_logger.LogWarning("设置静态IP返回错误: {Response}", response);
return false;
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "设置静态 IP 失败: {Ip}", ip);
return false;
}
}
public async Task<bool> SetDhcpModeAsync(string ip, string username, string password)
{
try
{
var soapEnvelope = BuildSetDhcpRequest(ip, AMT_PORT);
var response = await SendWsmanRequestAsync(ip, AMT_PORT, username, password, soapEnvelope);
if (response == null)
{
_logger.LogWarning("设置DHCP请求无响应");
return false;
}
if (response.Contains("Fault"))
{
_logger.LogWarning("设置DHCP返回错误: {Response}", response);
return false;
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "设置 DHCP 模式失败: {Ip}", ip);
return false;
}
}
private async Task<string?> SendWsmanRequestAsync(string ip, int port, string username, string password, string soapEnvelope)
{
var url = $"http://{ip}:{port}/wsman";
_logger.LogDebug("发送 WS-Man 请求到: {Url}", url);
_logger.LogDebug("请求内容: {Envelope}", soapEnvelope.Length > 1000 ? soapEnvelope[..1000] + "..." : soapEnvelope);
var handler = new HttpClientHandler
{
Credentials = new CredentialCache
{
{ new Uri($"http://{ip}:{port}"), "Digest", new NetworkCredential(username, password) }
},
PreAuthenticate = false
};
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);
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogDebug("响应状态码: {StatusCode}", response.StatusCode);
_logger.LogDebug("响应内容: {Content}", responseContent.Length > 1000 ? responseContent[..1000] + "..." : responseContent);
if (response.IsSuccessStatusCode)
{
return responseContent;
}
_logger.LogWarning("WS-Management 请求失败: {StatusCode}, 响应: {Response}", response.StatusCode, responseContent);
return null;
}
catch (Exception ex)
{
_logger.LogWarning("HTTP 请求异常: {Message}", ex.Message);
return null;
}
}
private static string BuildGetEthernetPortSettingsRequest(string ip, int port)
{
var toAddress = $"http://{ip}:{port}/wsman";
var messageId = $"uuid:{Guid.NewGuid()}";
// 使用 GET 请求配合 SelectorSet这是 SDK 推荐的方式
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://intel.com/wbem/wscim/1/amt-schema/1/AMT_EthernetPortSettings</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>
<w:SelectorSet>
<w:Selector Name=""InstanceID"">{WIRED_INSTANCE_ID}</w:Selector>
</w:SelectorSet>
</s:Header>
<s:Body/>
</s:Envelope>";
}
private static string BuildSetStaticIpRequest(string ip, int port, string ipAddress, string subnetMask, string? gateway, string? primaryDns, string? secondaryDns)
{
var toAddress = $"http://{ip}:{port}/wsman";
var messageId = $"uuid:{Guid.NewGuid()}";
// 构建可选字段
var gatewayXml = string.IsNullOrEmpty(gateway) ? "" : $"<h:DefaultGateway>{gateway}</h:DefaultGateway>";
var primaryDnsXml = string.IsNullOrEmpty(primaryDns) ? "" : $"<h:PrimaryDNS>{primaryDns}</h:PrimaryDNS>";
var secondaryDnsXml = string.IsNullOrEmpty(secondaryDns) ? "" : $"<h:SecondaryDNS>{secondaryDns}</h:SecondaryDNS>";
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://intel.com/wbem/wscim/1/amt-schema/1/AMT_EthernetPortSettings</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/Put</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>
<w:SelectorSet>
<w:Selector Name=""InstanceID"">{WIRED_INSTANCE_ID}</w:Selector>
</w:SelectorSet>
</s:Header>
<s:Body>
<h:AMT_EthernetPortSettings xmlns:h=""http://intel.com/wbem/wscim/1/amt-schema/1/AMT_EthernetPortSettings"">
<h:InstanceID>{WIRED_INSTANCE_ID}</h:InstanceID>
<h:ElementName>Intel(r) AMT Ethernet Port Settings</h:ElementName>
<h:DHCPEnabled>false</h:DHCPEnabled>
<h:IPAddress>{ipAddress}</h:IPAddress>
<h:SubnetMask>{subnetMask}</h:SubnetMask>
{gatewayXml}
{primaryDnsXml}
{secondaryDnsXml}
</h:AMT_EthernetPortSettings>
</s:Body>
</s:Envelope>";
}
private static string BuildSetDhcpRequest(string ip, int port)
{
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://intel.com/wbem/wscim/1/amt-schema/1/AMT_EthernetPortSettings</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/Put</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>
<w:SelectorSet>
<w:Selector Name=""InstanceID"">{WIRED_INSTANCE_ID}</w:Selector>
</w:SelectorSet>
</s:Header>
<s:Body>
<h:AMT_EthernetPortSettings xmlns:h=""http://intel.com/wbem/wscim/1/amt-schema/1/AMT_EthernetPortSettings"">
<h:InstanceID>{WIRED_INSTANCE_ID}</h:InstanceID>
<h:ElementName>Intel(r) AMT Ethernet Port Settings</h:ElementName>
<h:DHCPEnabled>true</h:DHCPEnabled>
</h:AMT_EthernetPortSettings>
</s:Body>
</s:Envelope>";
}
private AmtNetworkConfig? ParseNetworkConfig(string xmlResponse)
{
try
{
var doc = XDocument.Parse(xmlResponse);
// 检查是否有错误
var fault = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "Fault");
if (fault != null)
{
_logger.LogWarning("WS-Man 响应包含错误: {Fault}", fault.ToString());
return null;
}
var config = new AmtNetworkConfig
{
DhcpEnabled = GetBoolValue(doc, "DHCPEnabled"),
IpAddress = GetStringValue(doc, "IPAddress"),
SubnetMask = GetStringValue(doc, "SubnetMask"),
Gateway = GetStringValue(doc, "DefaultGateway"),
PrimaryDns = GetStringValue(doc, "PrimaryDNS"),
SecondaryDns = GetStringValue(doc, "SecondaryDNS"),
MacAddress = GetStringValue(doc, "MACAddress"),
LinkIsUp = GetBoolValue(doc, "LinkIsUp"),
SharedMac = GetBoolValue(doc, "SharedMAC"),
IpSyncEnabled = GetBoolValue(doc, "IpSyncEnabled")
};
_logger.LogDebug("解析网络配置: DHCP={Dhcp}, IP={Ip}, MAC={Mac}",
config.DhcpEnabled, config.IpAddress, config.MacAddress);
return config;
}
catch (Exception ex)
{
_logger.LogError(ex, "解析网络配置失败");
return null;
}
}
private static string? GetStringValue(XDocument doc, string localName)
{
var element = doc.Descendants()
.FirstOrDefault(e => e.Name.LocalName.Equals(localName, StringComparison.OrdinalIgnoreCase));
return element?.Value;
}
private static bool GetBoolValue(XDocument doc, string localName)
{
var value = GetStringValue(doc, localName);
return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
}
}