0

I am creating a managed wrapper in C# for the DHCP server API and have a problem with a unmanaged struct that defines a fixed size buffer. I want to add a filter on a DHCP server and the API (dhcpsapi.h) defines the following:

#define MAX_PATTERN_LENGTH      255
#define MAC_ADDRESS_LENGTH      6
#define HWTYPE_ETHERNET_10MB    1

typedef enum _DHCP_FILTER_LIST_TYPE {
    Deny,
    Allow
} DHCP_FILTER_LIST_TYPE, *LPDHCP_FILTER_LIST_TYPE;

typedef struct _DHCP_ADDR_PATTERN {
    BOOL MatchHWType;
    BYTE HWType;
    BOOL IsWildcard;
    BYTE Length;
    BYTE Pattern[MAX_PATTERN_LENGTH];
} DHCP_ADDR_PATTERN, *LPDHCP_ADDR_PATTERN;

typedef struct _DHCP_FILTER_ADD_INFOV4 {
    DHCP_ADDR_PATTERN     AddrPatt;
    LPWSTR                Comment;
    DHCP_FILTER_LIST_TYPE ListType;
} DHCP_FILTER_ADD_INFO, *LPDHCP_FILTER_ADD_INFO;

So I defined the following structs/enums/functions:

    [StructLayout(LayoutKind.Sequential)]
    unsafe internal struct DHCP_FILTER_ADD_INFO
    {
        public DHCP_ADDR_PATTERN AddrPatt;
        public char* Comment;
        public DhcpFilterListType ListType;
    }

    [StructLayout(LayoutKind.Sequential)]
    unsafe internal struct DHCP_ADDR_PATTERN
    {
        public bool MatchHWType;
        public HWTYPE HWType;
        public bool IsWildcard;
        public byte Length;   
        public fixed byte Pattern[255];
    }

    internal enum HWTYPE : byte
    {
        HWTYPE_ETHERNET_10MB = 1
        ...
    }

    // NativeMethods.DhcpAddFilterV4 signature
    [DllImport("dhcpsapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern DHCP_ERROR_CODES DhcpAddFilterV4(string serverIpAddress, IntPtr addFilterInfoPtr, bool forceFlag);

    // Wrapper function
    unsafe public static void AddFilter(string serverAddressString, byte[] macAddress, DhcpFilterListType listType)
    {
        if (macAddress == null)
            throw new ArgumentNullException(nameof(macAddress));
        if (macAddress.Length < 6)
            throw new ArgumentException("Invalid mac address", nameof(macAddress));
        if (!Enum.IsDefined(typeof(DhcpFilterListType), listType))
            throw new ArgumentOutOfRangeException(nameof(listType));

        var addressPattern = new DHCP_ADDR_PATTERN
        {
            HWType = HWTYPE.HWTYPE_ETHERNET_10MB,
            IsWildcard = false,
            MatchHWType = true,
            Length = 6
        };
        // Copy the mac address into the fixed size array
        fixed (byte* macPtr = macAddress)
        {
            Buffer.MemoryCopy(macPtr, addressPattern.Pattern, 255, 6);
        }
        var filterAddInfo = new DHCP_FILTER_ADD_INFO
        {
            ListType = listType,
            AddrPatt = addressPattern
        };
        IntPtr filterAddInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(filterAddInfo));
        try
        {
            Marshal.StructureToPtr(filterAddInfo, filterAddInfoPtr, true);
            DHCP_ERROR_CODES result = NativeMethods.DhcpAddFilterV4(serverAddressString, filterAddInfoPtr, true);
            if (result != DHCP_ERROR_CODES.ERROR_SUCCESS)
                throw DhcpServerException.GetExceptionForErrorCode(result);
        }
        finally
        {
            Marshal.FreeHGlobal(filterAddInfoPtr);
        }
    }

So, when I call the function and try to add a filter for the mac address "ABCDEF123456", the DHCP MMC shows an entry with a mac address "AB0000000000". So I slightly changed the DHCP_ADDR_PATTERN struct to use the MarshalAs-Attribute on the Pattern field:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
public byte[] Pattern;

This required me to also change the wrapper function:

byte[] pattern = new byte[255];
Buffer.BlockCopy(macAddress, 0, pattern, 0, 6);
addressPattern.Pattern = pattern;

And then it works like a charm. What magic is done when using the second method and what happens with the data in the first method i presented?

  • That's caused by Marshal.StructureToPtr(), it doesn't know enough about the size of the fixed sized buffer and [only copies one byte](https://stackoverflow.com/questions/9152119/marshal-structuretoptr-fails-with-bool-and-fixed-size-array). Not the only problem btw, 3rd argument must be false to avoid random AVEs. And the DHCP_FILTER_ADD_INFO struct requires CharSet=CharSet.Unicode to produce a correct LPWSTR. You avoid a lot of this misery by declaring DhcpAddFilterV4() correctly, 2nd argument should be ref DHCP_FILTER_ADD_INFO. – Hans Passant Apr 13 '19 at 15:52
  • Thank you for the fast reply. I applied all changes you suggested. Thanks! I thought by specifying the fixed size of the array it will definetely know its length. Is this a bug or did i misunderstand sth.? – fortender Apr 13 '19 at 16:28

0 Answers0