-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added new Vanara.Net assembly with support for DNS services and DHCP …
…clients.
- Loading branch information
Showing
9 changed files
with
954 additions
and
1 deletion.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Net.NetworkInformation; | ||
using System.Runtime.InteropServices; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Vanara.Extensions; | ||
using Vanara.InteropServices; | ||
using Vanara.PInvoke; | ||
using static Vanara.PInvoke.DnsApi; | ||
|
||
namespace Vanara.Net; | ||
|
||
/// <summary>Represents a DNS service.</summary> | ||
/// <seealso cref="System.IDisposable"/> | ||
public class DnsService : IDisposable | ||
{ | ||
private readonly IntPtr ctx; | ||
private readonly AutoResetEvent evt = new(false); | ||
private readonly SafePDNS_SERVICE_INSTANCE pSvcInst; | ||
private bool disposed = false, registering = false; | ||
private Win32Error err = 0; | ||
private DNS_SERVICE_REGISTER_REQUEST req; | ||
|
||
/// <summary>Initializes a new instance of the <see cref="DnsService"/> class.</summary> | ||
/// <param name="serviceName">The name of the service.</param> | ||
/// <param name="hostName">The name of the host of the service.</param> | ||
/// <param name="port">The port.</param> | ||
/// <param name="priority">The service priority.</param> | ||
/// <param name="weight">The service weight.</param> | ||
/// <param name="address">The service-associated address and port.</param> | ||
/// <param name="properties">A dictionary of property keys and values.</param> | ||
public DnsService(string serviceName, string hostName, ushort port, [Optional] ushort priority, | ||
[Optional] ushort weight, [Optional] IPAddress? address, [Optional] IDictionary<string, string>? properties) | ||
{ | ||
using SafeCoTaskMemHandle v4 = address is null ? SafeCoTaskMemHandle.Null : new(address.MapToIPv4().GetAddressBytes()); | ||
using SafeCoTaskMemHandle v6 = address is null ? SafeCoTaskMemHandle.Null : new(address.MapToIPv6().GetAddressBytes()); | ||
pSvcInst = DnsServiceConstructInstance(serviceName, hostName, v4, v6, port, | ||
priority, weight, (uint)(properties?.Count ?? 0), properties?.Keys.ToArray(), properties?.Values.ToArray()); | ||
ctx = (IntPtr)GCHandle.Alloc(this, GCHandleType.Normal); | ||
} | ||
|
||
internal DnsService(IntPtr pInst) | ||
{ | ||
pSvcInst = new SafePDNS_SERVICE_INSTANCE(pInst); | ||
ctx = (IntPtr)GCHandle.Alloc(this, GCHandleType.Normal); | ||
} | ||
|
||
/// <summary>A string that represents the name of the host of the service.</summary> | ||
public string HostName => pSvcInst.pszHostName; | ||
|
||
/// <summary> | ||
/// A string that represents the service name. This is a fully qualified domain name that begins with a service name, and ends with | ||
/// ".local". It takes the generalized form "<ServiceName>._<ServiceType>._<TransportProtocol>.local". For example, "MyMusicServer._http._tcp.local". | ||
/// </summary> | ||
public string InstanceName => pSvcInst.pszInstanceName; | ||
|
||
/// <summary>A value that contains the interface index on which the service was discovered.</summary> | ||
public uint InterfaceIndex => pSvcInst.dwInterfaceIndex; | ||
|
||
/// <summary>The service-associated IPv4 address, if defined.</summary> | ||
public IPAddress? Ip4Address => pSvcInst.ip4Address?.Address.MapToIPv4(); | ||
|
||
/// <summary>The service-associated IPv6 address, if defined.</summary> | ||
public IPAddress? Ip6Address => pSvcInst.ip6Address?.Address.MapToIPv6(); | ||
|
||
/// <summary>Gets a value indicating whether this instance is registered.</summary> | ||
/// <value><see langword="true"/> if this instance is registered; otherwise, <see langword="false"/>.</value> | ||
public bool IsRegistered => req.Version != 0; | ||
|
||
/// <summary>A value that represents the port on which the service is running.</summary> | ||
public ushort Port => pSvcInst.wPort; | ||
|
||
/// <summary>A value that represents the service priority.</summary> | ||
public ushort Priority => pSvcInst.wPriority; | ||
|
||
/// <summary>The DNS service properties.</summary> | ||
public IReadOnlyDictionary<string, string> Properties => pSvcInst.properties; | ||
|
||
/// <summary>A value that represents the service weight.</summary> | ||
public ushort Weight => pSvcInst.wWeight; | ||
|
||
/// <summary>Used to obtain more information about a service advertised on the local network.</summary> | ||
/// <param name="serviceName"> | ||
/// The service name. This is a fully qualified domain name that begins with a service name, and ends with ".local". It takes the | ||
/// generalized form "<ServiceName>._<ServiceType>._<TransportProtocol>.local". For example, "MyMusicServer._http._tcp.local". | ||
/// </param> | ||
/// <param name="adapter">The interface over which the query is sent. If <see langword="null"/>, then all interfaces will be considered.</param> | ||
/// <param name="cancellationToken">A cancellation token that can be used to cancel a pending asynchronous resolve operation.</param> | ||
/// <returns>If successful, returns a new <see cref="DnsService"/> instance; otherwise, throws the appropriate DNS-specific exception.</returns> | ||
/// <remarks>This function is asynchronous.</remarks> | ||
public static async Task<DnsService> ResolveAsync(string serviceName, NetworkInterface? adapter = null, CancellationToken cancellationToken = default) | ||
{ | ||
using ManualResetEvent evt = new(false); | ||
Win32Error err = 0; | ||
IntPtr result = default; | ||
DNS_SERVICE_RESOLVE_REQUEST res = new() | ||
{ | ||
Version = DNS_QUERY_REQUEST_VERSION1, | ||
InterfaceIndex = (uint)(adapter?.GetIPProperties().GetIPv4Properties().Index ?? 0), | ||
QueryName = serviceName, | ||
pResolveCompletionCallback = ResolveCallback, | ||
}; | ||
DnsServiceResolve(res, out DNS_SERVICE_CANCEL c).ThrowUnless(Win32Error.DNS_REQUEST_PENDING); | ||
await Task.Run(() => | ||
{ | ||
if (WaitHandle.WaitAny(new[] { cancellationToken.WaitHandle, evt }) == 0) | ||
DnsServiceResolveCancel(c); | ||
}); | ||
return result != IntPtr.Zero ? new DnsService(result) : throw err.GetException(); | ||
|
||
void ResolveCallback(Win32Error Status, IntPtr pQueryContext, IntPtr pInstance) | ||
{ | ||
if ((err = Status).Succeeded) | ||
result = pInstance; | ||
evt.Set(); | ||
} | ||
} | ||
|
||
/// <summary>Used to remove a registered service.</summary> | ||
/// <returns>If not successful, throws an appropriate DNS-specific exception.</returns> | ||
/// <remarks>This function is asynchronous.</remarks> | ||
public async Task DeRegisterAsync() | ||
{ | ||
if (!IsRegistered) | ||
return; | ||
|
||
if (registering) | ||
throw new InvalidOperationException("Service is already being deregistered."); | ||
|
||
registering = true; | ||
DnsServiceDeRegister(req, IntPtr.Zero); | ||
await Task.Run(() => evt.WaitOne()); | ||
registering = false; | ||
req = default; | ||
err.ThrowIfFailed(); | ||
} | ||
|
||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> | ||
public void Dispose() | ||
{ | ||
if (disposed) | ||
return; | ||
|
||
disposed = true; | ||
pSvcInst?.Dispose(); | ||
if (ctx != IntPtr.Zero) | ||
GCHandle.FromIntPtr(ctx).Free(); | ||
} | ||
|
||
/// <summary>Used to register a discoverable service on this device.</summary> | ||
/// <param name="unicastEnabled"> | ||
/// <see langword="true"/> if the DNS protocol should be used to advertise the service; <see langword="false"/> if the mDNS protocol | ||
/// should be used. | ||
/// </param> | ||
/// <param name="adapter"> | ||
/// An optional value that contains the network interface over which the service is to be advertised. If <see langword="null"/>, then all | ||
/// interfaces will be considered. | ||
/// </param> | ||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchonous operation.</param> | ||
/// <exception cref="System.InvalidOperationException">Service is already registered.</exception> | ||
/// <remarks> | ||
/// This function is asynchronous. To deregister the service, call DnsServiceDeRegister. The registration is tied to the lifetime of the | ||
/// calling process. If the process goes away, the service will be automatically deregistered. | ||
/// </remarks> | ||
public async Task RegisterAsync(bool unicastEnabled = false, NetworkInterface? adapter = null, CancellationToken cancellationToken = default) | ||
{ | ||
if (IsRegistered) | ||
throw new InvalidOperationException("Service is already registered."); | ||
|
||
if (registering) | ||
throw new InvalidOperationException("Service is already being registered."); | ||
|
||
req = new DNS_SERVICE_REGISTER_REQUEST() | ||
{ | ||
Version = DNS_QUERY_REQUEST_VERSION1, | ||
InterfaceIndex = (uint)(adapter?.GetIPProperties().GetIPv4Properties().Index ?? 0), | ||
pServiceInstance = pSvcInst, | ||
pQueryContext = ctx, | ||
pRegisterCompletionCallback = RegCallback, | ||
unicastEnabled = unicastEnabled | ||
}; | ||
registering = true; | ||
DnsServiceRegister(req, out DNS_SERVICE_CANCEL svcCancel).ThrowUnless(Win32Error.DNS_REQUEST_PENDING); | ||
await Task.Run(() => | ||
{ | ||
if (WaitHandle.WaitAny(new[] { cancellationToken.WaitHandle, evt }) == 0) | ||
DnsServiceResolveCancel(svcCancel); | ||
}); | ||
registering = false; | ||
err.ThrowIfFailed(); | ||
} | ||
|
||
private static void RegCallback(Win32Error Status, IntPtr pQueryContext, IntPtr pInstance) | ||
{ | ||
DnsService svc = (DnsService)GCHandle.FromIntPtr(pQueryContext).Target; | ||
using SafePDNS_SERVICE_INSTANCE i = new(pInstance); | ||
svc.err = Status; | ||
svc.evt.Set(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<Description>Abstracted classes around Win32 networking functions to provide simplified and object-oriented access to key networking capabilities like DNS, DHCP, filtering, access, and discovery.</Description> | ||
<AssemblyTitle>$(AssemblyName)</AssemblyTitle> | ||
<AssemblyName>Vanara.Net</AssemblyName> | ||
<PackageId>$(AssemblyName)</PackageId> | ||
<PackageTags>vanara;net-extensions;networking</PackageTags> | ||
<PackageReleaseNotes /> | ||
<PackageReadmeFile>pkgreadme.md</PackageReadmeFile> | ||
<RootNamespace>Vanara.Net</RootNamespace> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\PInvoke\Dhcp\Vanara.PInvoke.Dhcp.csproj" /> | ||
<ProjectReference Include="..\PInvoke\DnsApi\Vanara.PInvoke.DnsApi.csproj" /> | ||
<ProjectReference Include="..\PInvoke\Kernel32\Vanara.PInvoke.Kernel32.csproj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
![Vanara](https://raw.githubusercontent.com/dahall/Vanara/master/docs/icons/VanaraHeading.png) | ||
### **Vanara.Management NuGet Package** | ||
[![Version](https://img.shields.io/nuget/v/Vanara.Management?label=NuGet&style=flat-square)](https://github.com/dahall/Vanara/releases) | ||
[![Build status](https://img.shields.io/appveyor/build/dahall/vanara?label=AppVeyor%20build&style=flat-square)](https://ci.appveyor.com/project/dahall/vanara) | ||
|
||
Extensions and helper classes for System.Management. | ||
|
||
### **What is Vanara?** | ||
|
||
[Vanara](https://github.com/dahall/Vanara) is a community project that contains various .NET assemblies which have P/Invoke functions, interfaces, enums and structures from Windows libraries. Each assembly is associated with one or a few tightly related libraries. | ||
|
||
### **Issues?** | ||
|
||
First check if it's already fixed by trying the [AppVeyor build](https://ci.appveyor.com/nuget/vanara-prerelease). | ||
If you're still running into problems, file an [issue](https://github.com/dahall/Vanara/issues). | ||
|
||
### **Included in Vanara.Management** | ||
|
||
Classes | ||
--- | ||
DynamicMgmtObject ManagementExtensions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
## Assembly report for Vanara.Management.dll | ||
Extensions and helper classes for System.Management. | ||
### Classes | ||
Class | Description | ||
---- | ---- | ||
[Vanara.Management.DynamicMgmtObject](https://github.com/dahall/Vanara/search?l=C%23&q=DynamicMgmtObject) | A dynamic object to handle WMI `System.Management.ManagementBaseObject` references. | ||
[Vanara.Management.ManagementExtensions](https://github.com/dahall/Vanara/search?l=C%23&q=ManagementExtensions) | Extension methods to work more easily with `System.Management`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<AssemblyName>UnitTest.PInvoke.Net</AssemblyName> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\..\Net\Vanara.Net.csproj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
using Microsoft.CodeAnalysis; | ||
using NUnit.Framework; | ||
using NUnit.Framework.Internal; | ||
using System; | ||
using System.Runtime.InteropServices; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using System.Xml.Linq; | ||
using Vanara.Net; | ||
using static Vanara.PInvoke.Dhcp; | ||
|
||
namespace Vanara.PInvoke.Tests; | ||
|
||
[TestFixture] | ||
public class NetTests | ||
{ | ||
[OneTimeSetUp] | ||
public void _Setup() | ||
{ | ||
} | ||
|
||
[OneTimeTearDown] | ||
public void _TearDown() | ||
{ | ||
} | ||
|
||
[Test] | ||
public async Task TestDnsCreateService() | ||
{ | ||
const string svcName = "initial._windns-example._udp.local"; | ||
using var dnsClient = new DnsService(svcName, "example.com", 1); | ||
await dnsClient.RegisterAsync(); | ||
TestContext.WriteLine("Created\r\n================="); | ||
dnsClient.WriteValues(); | ||
try | ||
{ | ||
Assert.That(dnsClient.IsRegistered, Is.True); | ||
using var dnsLookup = DnsService.ResolveAsync(svcName).Result; | ||
Assert.That(dnsLookup.InstanceName, Is.EqualTo(svcName)); | ||
TestContext.WriteLine("Found\r\n================="); | ||
dnsLookup.WriteValues(); | ||
} | ||
finally | ||
{ | ||
await dnsClient.DeRegisterAsync(); | ||
} | ||
} | ||
|
||
[Test] | ||
public void TestDhcpProps() | ||
{ | ||
using var client = new DhcpClient(); | ||
client.WriteValues(); | ||
client.GetDhcpServers().WriteValues(); | ||
client.GetOriginalSubnetMask().WriteValues(); | ||
} | ||
|
||
[Test] | ||
public void TestDhcpListeners() | ||
{ | ||
var client = new DhcpClient(); | ||
try | ||
{ | ||
client.ChangeEventIds = new[] { DHCP_OPTION_ID.OPTION_DEFAULT_TTL, DHCP_OPTION_ID.OPTION_LEASE_TIME, DHCP_OPTION_ID.OPTION_MESSAGE }; | ||
Thread.Sleep(1000); | ||
client.ChangeEventIds = null; | ||
Thread.Sleep(1000); | ||
} | ||
finally | ||
{ | ||
client.Dispose(); | ||
} | ||
} | ||
} |
Oops, something went wrong.