DHCP with P/Invoke

R

Richard

Hello,

I'm working on an application to allow our network team to use a small
application to make DHCP reservations on our Microsoft DHCP Server.

The problem is you have to use P/Invoke to do it, and from what I've
found on the web and in this newsgroup is that it's not easy, however I
believe it can/has been done.

At the moment I'm just trying to find out information about an existing
reservation but I'm getting stuck. I read the newsgroup article
'P/Invoke with DHCP management API' which got me started but the last
comment made on 2006-01-01 didn't make sense to me and I can't get the
damn thing to work, in my test console app.

Here is the code I am using, could someone point me in the right
direction please?:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace DHCP
{
class Program
{
static void Main(string[] args)
{
String ServerIpAddress = "192.168.0.250";

UInt32 DHCPResult = 0;
try
{
DhcpApi.DHCP_SEARCH_INFO searchInfo = new
DhcpApi.DHCP_SEARCH_INFO();
DhcpApi.DHCP_SEARCH_INFO_TYPE searchInfoType =
DhcpApi.DHCP_SEARCH_INFO_TYPE.DhcpClientIpAddress;

searchInfo.SearchType = searchInfoType;
searchInfo.ClientIpAddress =
DhcpApi.ConvertIPAddress("192.168.0.5");

DhcpApi.DHCP_CLIENT_INFO clientInfo = new
DhcpApi.DHCP_CLIENT_INFO();

DHCPResult = DhcpApi.DhcpGetClientInfo(ServerIpAddress,
ref searchInfo, ref clientInfo);
Console.WriteLine(DHCPResult.ToString() + " Client
Info: " + clientInfo.ClientName);
}
catch (Exception ex)
{

Console.WriteLine("****************************************************************************");
Console.WriteLine("ERROR: \t" + DHCPResult.ToString());
Console.WriteLine("MESSAGE: \t" + ex.Message);
Console.WriteLine();
Console.WriteLine("FULL DETAILS:");
Console.WriteLine(ex.ToString());

Console.WriteLine("****************************************************************************");
}

Console.ReadKey(true);
}
}

public class DhcpApi
{
/// <summary>
/// The DhcpGetClientInfo function returns information about a
specific DHCP client.
/// </summary>
/// <param name="ServerIpAddress">[in] Unicode string that
specifies the IP address of the DHCP server.</param>
/// <param name="SearchInfo">[in] DHCP_SEARCH_INFO structure
that contains the parameters for the search. </param>
/// <param name="ClientInfo">[out] Pointer to a
DHCP_CLIENT_INFO structure that contains information describing the DHCP
client that most closely matches the provided search parameters. If no
client is found, this parameter will be null.</param>
/// <returns>This function returns ERROR_SUCCESS upon a
successful call. Otherwise, it returns one of the DHCP Server Management
API Error Codes.</returns>
[DllImport("dhcpsapi.dll", SetLastError = true,CharSet =
CharSet.Unicode)]
public static extern UInt32
DhcpGetClientInfo([MarshalAs(UnmanagedType.LPWStr)]String
ServerIpAddress, ref DHCP_SEARCH_INFO SearchInfo, ref DHCP_CLIENT_INFO
ClientInfo);

/// <summary>
/// The DHCP_SEARCH_INFO_TYPE enumeration defines the set of
possible
/// attributes used to search DHCP client information records.
/// </summary>
public enum DHCP_SEARCH_INFO_TYPE : int
{
/// <summary>
/// The search will be performed against the assigned DHCP
client IP address, represented as a 32-bit unsigned integer value.
/// </summary>
DhcpClientIpAddress = 0,
/// <summary>
/// The search will be performed against the MAC address of
the DHCP client network interface device, represented as a
DHCP_BINARY_DATA structure.
/// </summary>
DhcpClientHardwareAddress = 1,
/// <summary>
/// The search will be performed again the DHCP client's
network name, represented as a Unicode string.
/// </summary>
DhcpClientName = 2
}

/// <summary>
/// The DHCP_SEARCH_INFO structure defines the DHCP client record
/// data used to search against for particular server operations.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DHCP_SEARCH_INFO
{
// typedef struct _DHCP_CLIENT_SEARCH_INFO {
// DHCP_SEARCH_INFO_TYPE SearchType;
// union {
// DHCP_IP_ADDRESS ClientIpAddress;
// DHCP_CLIENT_UID ClientHardwareAddress;
// LPWSTR ClientName;
// } SearchInfo;
//} DHCP_SEARCH_INFO, *LPDHCP_SEARCH_INFO;

public DHCP_SEARCH_INFO_TYPE SearchType;
public UInt32 ClientIpAddress;
}
/// <summary>
/// The DHCP_CLIENT_INFO structure defines a client information
record
/// used by the DHCP server.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DHCP_CLIENT_INFO
{
//
****************************************************************************
// DHCP_IP_ADDRESS Specifies an IP address, with the first
byte containing
// the first number in the address, the second byte
containing the second
// number, the third byte containing the third number, and
the last byte
// containing the last number in the address. For example,
the address
// 192.1.1.10 is represented as 11000000 00000001 00000001
00001010 (binary),
// or 3221291274 (decimal).
//
****************************************************************************
public UInt32 ClientIpAddress;
public UInt32 SubnetMask;
public DHCP_BINARY_DATA ClientHardwareAddress;
[MarshalAs(UnmanagedType.LPWStr)]
public String ClientName;
[MarshalAs(UnmanagedType.LPWStr)]
public String ClientComment;
public DATE_TIME ClientLeaseExpires;
public DHCP_HOST_INFO OwnerHost;
}
}
}


I'm also hoping to implement the following API functions in the future,
if anyone has any pointers on these as well! ;o):

DhcpGetClientInfo
DhcpDeleteClientInfo
DhcpCreateClientInfo
DhcpSetClientInfo
DhcpEnumSubnetClients

Cheers,

Richard W.
 
M

Mattias Sjögren

Richard,
At the moment I'm just trying to find out information about an existing
reservation but I'm getting stuck. I read the newsgroup article
'P/Invoke with DHCP management API' which got me started but the last
comment made on 2006-01-01 didn't make sense to me and I can't get the
damn thing to work, in my test console app.

What he's saying is that you have to change the method signature to

public static extern UInt32 DhcpGetClientInfo(String ServerIpAddress,
ref DHCP_SEARCH_INFO SearchInfo, out IntPtr ClientInfo);

and then make the calling code

IntPtr pClientInfo;
DHCPResult = DhcpApi.DhcpGetClientInfo(ServerIpAddress, ref
searchInfo, out pClientInfo);
if (DHCPResult == 0 && pClientInfo != IntPtr.Zero)
{
DHCP_CLIENT_INFO clientInfo =
(DHCP_CLIENT_INFO)Marshal.PtrToStructure(pClientInfo,
typeof(DHCP_CLIENT_INFO));
...
}



Mattias
 
R

Richard

Mattias,
What he's saying is that you have to change the method signature to

public static extern UInt32 DhcpGetClientInfo(String ServerIpAddress,
ref DHCP_SEARCH_INFO SearchInfo, out IntPtr ClientInfo);

and then make the calling code

IntPtr pClientInfo;
DHCPResult = DhcpApi.DhcpGetClientInfo(ServerIpAddress, ref
searchInfo, out pClientInfo);
if (DHCPResult == 0 && pClientInfo != IntPtr.Zero)
{
DHCP_CLIENT_INFO clientInfo =
(DHCP_CLIENT_INFO)Marshal.PtrToStructure(pClientInfo,
typeof(DHCP_CLIENT_INFO));
...
}

Thank you very much, that's got it working a treat. :blush:)

I can move on now, I'm sure I'll have more questions about this how
topic so watch this thread people!! ;o)

Cheers,

Richard
 
R

Richard

Mattias,

You mention in the thread 'Problem Creating Structures with P/Invoke'
that when using the struct DHCP_IP_ARRAY that you should dereference the
pointer to retrieve the addresses with Marshal.Copy().

I've been trying all morning to do it, but can't seem to get my head
round it. Could you offer some assistance again please? I get the code
below working but I'm trying to retrieve a list of the subnets returned
not just the number.

....
{
DHCP_IP_ARRAY ips = new DHCP_IP_ARRAY();
uint nr = 0, total = 0, resumeHandle = 0;

DhcpEnumSubnets("192.168.0.250", ref resumeHandle, 1000, ref ips,
ref nr, ref total);

Console.WriteLine("Elements read {0} of total {1}", nr, total);
}

[StructLayout(LayoutKind.Sequential)]
public struct DHCP_IP_ARRAY
{
public uint NumElements;
public IntPtr IPAddresses;
}

[DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint DhcpEnumSubnets(
string ServerIP,
ref uint resumeHandle,
uint PerferedMax,
ref DHCP_IP_ARRAY ipAddresses,
ref uint ElementsRead,
ref uint ElementsTotal);

Cheers,

Richard
 
M

Mattias Sjögren

Richard,
You mention in the thread 'Problem Creating Structures with P/Invoke'
that when using the struct DHCP_IP_ARRAY that you should dereference the
pointer to retrieve the addresses with Marshal.Copy().

I've been trying all morning to do it, but can't seem to get my head
round it. Could you offer some assistance again please?

Looks like it's pretty much the same issue as in your last post. The
parameter is actually a pointer to a pointer to the struct. So try it
like this:
[DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint DhcpEnumSubnets(
string ServerIP,
ref uint resumeHandle,
uint PerferedMax, out IntPtr ipAddresses,
ref uint ElementsRead,
ref uint ElementsTotal);

If the argument comes back non-null, you can use
Marshal.PtrToStructure or Marshal.Copy to retrieve the DHCP_IP_ARRAY
data.


Mattias
 
R

Richard

Mattias,
Looks like it's pretty much the same issue as in your last post. The
parameter is actually a pointer to a pointer to the struct. So try it
like this:
[DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint DhcpEnumSubnets(
string ServerIP,
ref uint resumeHandle,
uint PerferedMax, out IntPtr ipAddresses,
ref uint ElementsRead,
ref uint ElementsTotal);

If the argument comes back non-null, you can use
Marshal.PtrToStructure or Marshal.Copy to retrieve the DHCP_IP_ARRAY
data.

Thank you, I've got this bit working, although I had to work hard to get
it back into a decent format.

If there are any glaring mistakes then let me know, but the code below
does work for me. Also if any one has a better (more efficient method)
for converting the UInt32 IP Address back in to a dot notation IP
Address then please post here! ;o)

Now onto the next bit. Anyone got some pointers on searching for a
MAC/Hardware Address on the DHCP server using DhcpGetClientInfo? I can
retrieve the ClientInfo structure back if I search for the IP Address
but not if I use the hardware address or client name.

Code for others who search this thread, I'll stick it up on the
pinvoke.net (wiki) when I get a chance:

static void Main()
{
String ServerIpAddress = "192.168.0.250";
UInt32 DHCPResult = 0;

IntPtr ips;
uint nr = 0;
uint total = 0;
uint resumeHandle = 0;

if (DHCPResult == 0)
{
DHCPResult = DhcpEnumSubnets(ServerIpAddress, ref resumeHandle,
1000, out ips, ref nr, ref total);

DHCP_IP_ARRAY ipArray =
(DHCP_IP_ARRAY)Marshal.PtrToStructure(ips, typeof(DHCP_IP_ARRAY));

int size = (int)ipArray.NumElements;
IntPtr outArray = ipArray.IPAddresses;
DHCP_IP_ADDRESS[] ipAddressArray = new DHCP_IP_ADDRESS[size];
IntPtr current = outArray;
for (int i = 0; i < size; i++)
{
ipAddressArray = new DHCP_IP_ADDRESS();
Marshal.PtrToStructure(current, ipAddressArray);
Marshal.DestroyStructure(current, typeof(DHCP_IP_ADDRESS));
current = (IntPtr)((int)current +
Marshal.SizeOf(ipAddressArray));

Console.WriteLine("{0}",
UInt32IPAddressToString(ipAddressArray.IPAddress));
}
Marshal.FreeCoTaskMem(outArray);

Console.WriteLine("Elements read {0} of total {1}", nr, total);

}
else
{
int code = 0;
unchecked
{
code = (int)DHCPResult;
}
Win32Exception winex = new Win32Exception(code);
Console.WriteLine(winex.NativeErrorCode + " : " + winex.Message);
}
}

[DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint DhcpEnumSubnets(
string ServerIpAddress,
ref uint ResumeHandle,
uint PreferredMaximum,
out IntPtr EnumInfo,
ref uint ElementsRead,
ref uint ElementsTotal
);

[StructLayout(LayoutKind.Sequential)]
public struct DHCP_IP_ARRAY
{
public uint NumElements;
public IntPtr IPAddresses;
}

/// This is a custom type/class
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DHCP_IP_ADDRESS
{
public UInt32 IPAddress;
}

public static string UInt32IPAddressToString(UInt32 ipAddress)
{
IPAddress ipA = new IPAddress(ipAddress);
string[] sIp = ipA.ToString().Split('.');

return sIp[3] + "." + sIp[2] + "." + sIp[1] + "." + sIp[0];
}

Regards,

Richard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top