335 lines
13 KiB
C#
335 lines
13 KiB
C#
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);
|
||
}
|
||
}
|