问题
I want to call 5 WinDivert functions in a C# code, first reflex : PInvoke here are the signatures :
internal enum WINDIVERT_LAYER
{
WINDIVERT_LAYER_NETWORK = 0, /* Network layer. */
WINDIVERT_LAYER_NETWORK_FORWARD = 1 /* Network layer (forwarded packets) */
}
internal const UInt64 WINDIVERT_FLAG_SNIFF = 1;
/*
* typedef struct
{
UINT32 IfIdx;
UINT32 SubIfIdx;
UINT8 Direction;
} WINDIVERT_ADDRESS, *PWINDIVERT_ADDRESS;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_ADDRESS
{
UInt32 IfIdx;
UInt32 SubIfIdx;
byte Direction;
}
/*
* typedef struct
{
UINT8 HdrLength:4;
UINT8 Version:4;
UINT8 TOS;
UINT16 Length;
UINT16 Id;
UINT16 FragOff0;
UINT8 TTL;
UINT8 Protocol;
UINT16 Checksum;
UINT32 SrcAddr;
UINT32 DstAddr;
} WINDIVERT_IPHDR, *PWINDIVERT_IPHDR;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_IPHDR
{
byte HdrLengthAndVersion;
byte TOS;
UInt16 Length;
UInt16 Id;
UInt16 FragOff0;
byte TTL;
byte Protocol;
UInt16 Checksum;
UInt32 SrcAddr;
UInt32 DstAddr;
}
/*
* typedef struct
{
UINT16 SrcPort;
UINT16 DstPort;
UINT16 Length;
UINT16 Checksum;
} WINDIVERT_UDPHDR, *PWINDIVERT_UDPHDR;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_UDPHDR
{
UInt16 SrcPort;
UInt16 DstPort;
UInt16 Length;
UInt16 Checksum;
}
/*
* HANDLE WinDivertOpen(
__in const char *filter,
__in WINDIVERT_LAYER layer,
__in INT16 priority,
__in UINT64 flags
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertOpen", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr WinDivertOpen(
[MarshalAs(UnmanagedType.LPStr)] string filter,
WINDIVERT_LAYER layer,
Int16 priority,
UInt64 flags);
/*
* BOOL WinDivertClose(
__in HANDLE handle
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertClose", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return:MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertClose(IntPtr handle);
/*
* BOOL WinDivertRecv(
__in HANDLE handle,
__out PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *recvLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertRecv", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertRecv(IntPtr handle, out IntPtr pPacket, uint packetLen, [Optional] out IntPtr pAddr, [Optional] out uint readLen);
/*
* BOOL WinDivertHelperParsePacket(
__in PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_IPHDR *ppIpHdr,
__out_opt PWINDIVERT_IPV6HDR *ppIpv6Hdr,
__out_opt PWINDIVERT_ICMPHDR *ppIcmpHdr,
__out_opt PWINDIVERT_ICMPV6HDR *ppIcmpv6Hdr,
__out_opt PWINDIVERT_TCPHDR *ppTcpHdr,
__out_opt PWINDIVERT_UDPHDR *ppUdpHdr,
__out_opt PVOID *ppData,
__out_opt UINT *pDataLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertHelperParsePacket", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertHelperParsePacket(IntPtr pPacket, uint packetLen, [Optional] out IntPtr ppIpHdr, [Optional] out IntPtr ppIpv6Hdr,
[Optional] out IntPtr ppIcmpHdr, [Optional] out IntPtr ppTcpHdr, [Optional] out IntPtr ppUdpHdr, [Optional] out IntPtr ppData,
[Optional]out uint pDataLen);
/*
* BOOL WinDivertSend(
__in HANDLE handle,
__in PVOID pPacket,
__in UINT packetLen,
__in PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *sendLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertSend", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertSend(IntPtr handle, IntPtr pPacket, uint packetLen, IntPtr pAddr, [Optional] out uint sendLen);
I have managed to avoid (87 = ERROR_INVALID_PARAMETER
), (998 = ERROR_NOACCESS
) errors on calls to WinDivertOpen()
, WinDivertClose()
and WinDivertRecv()
but I'm still getting System.AccessViolationException : Attempted to read or write protected memory. This is often an indication that other memory has been corrupted
and (998 = ERROR_NOACCESS)
when trying to call WinDivertHelperParsePacket()
.
Here's the code :
static void Main(string[] args)
{
const uint MAXBUF = 0xFFFF;
IntPtr handle;
IntPtr addr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_ADDRESS)));
IntPtr packet = Marshal.AllocHGlobal((int)MAXBUF);
uint packetLen;
IntPtr ip_header = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_IPHDR)));
IntPtr udp_header = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_UDPHDR)));
IntPtr payload;
uint payload_len;
uint sendLen;
IntPtr opt_param = IntPtr.Zero;
byte[] managedPacket;
IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
handle = NativeMethods.WinDivertOpen("udp.DstPort == 53", NativeMethods.WINDIVERT_LAYER.WINDIVERT_LAYER_NETWORK, 404, NativeMethods.WINDIVERT_FLAG_SNIFF);
if (handle == INVALID_HANDLE_VALUE) Console.WriteLine("open error:" + Marshal.GetLastWin32Error());
else
{
while (true)
{
if (!NativeMethods.WinDivertRecv(handle, out packet, MAXBUF, out addr, out packetLen))
{
Console.WriteLine("Recv error:" + Marshal.GetLastWin32Error());
continue;
}
try
{
managedPacket = new byte[(int)packetLen];
Marshal.Copy(packet, managedPacket, 0, (int)packetLen); // causes AccessViolationException
Console.WriteLine("---------------------------------");
/*for (int i = 0; i < packetLen; i++)
{
Console.Write("{0:X}", managedPacket[i]);
}*/
Console.WriteLine("---------------------------------");
}
catch(Exception ex)
{
Console.WriteLine("copy error :" + ex.Message);
}
if (!NativeMethods.WinDivertHelperParsePacket(packet, packetLen, out ip_header, out opt_param, out opt_param, out opt_param, out udp_header, out payload, out payload_len)) // causes AccessViolationException
{
Console.WriteLine("Parse error:" + Marshal.GetLastWin32Error());
//continue;
}
if (!NativeMethods.WinDivertSend(handle, packet, packetLen, addr, out sendLen))
{
Console.WriteLine("Send error:" + Marshal.GetLastWin32Error());
continue;
}
}
/*if (!NativeMethods.WinDivertClose(handle))
Console.WriteLine("close error:" + Marshal.GetLastWin32Error());*/
}
Console.ReadKey();
}
My boss told me that it's better/easier to write a COM object in C++ that wraps the C calls and expose it to C# to avoid the marshaling and memory handling pain. Should I stick to PInvoke or go the COM way ?
EDIT : Updates
I have tried two different ways of allocating unmanaged memory and both failed (unsafe code allowed) :
byte[] managedPacket = new byte[(int)packetLen];
NativeMethods.WINDIVERT_ADDRESS windivertAddr = new NativeMethods.WINDIVERT_ADDRESS();
GCHandle managedPacketHandle = GCHandle.Alloc(managedPacket, GCHandleType.Pinned);
IntPtr managedPacketPointer = managedPacketHandle.AddrOfPinnedObject();
GCHandle windivertAddrHandle = GCHandle.Alloc(windivertAddr, GCHandleType.Pinned);
IntPtr windivertAddrPointer = managedPacketHandle.AddrOfPinnedObject();
NativeMethods.WinDivertRecv(handle, out managedPacketPointer, (uint)(packetLen * Marshal.SizeOf(typeof(System.Byte))), out windivertAddrPointer , out readLen);
// output of managed array and struct fields = 0 and it still causes unhandled AccessViolationException even inside a try/catch
managedPacketHandle.Free();
windivertAddrPointer.Free();
and :
IntPtr packet = Marshal.AllocHGlobal((int)packetLen * Marshal.SizeOf(typeof(System.Byte)));
IntPtr addr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_ADDRESS)));
NativeMethods.WinDivertRecv(handle, out packet, (uint)(packetLen * Marshal.SizeOf(typeof(System.Byte))), out addr, out readLen)
byte[] managedPacket = new byte[(int)packetLen];
Marshal.Copy(packet, managedPacket, 0, (int)readLen);
NativeMethods.WINDIVERT_ADDRESS windivertAddr = (NativeMethods.WINDIVERT_ADDRESS)Marshal.PtrToStructure(addr, typeof(NativeMethods.WINDIVERT_ADDRESS));
// no output of managed array and struct fields and the same unhandled AccessViolationException
Marshal.FreeHGlobal(addr);
Marshal.FreeHGlobal(packet);
Also sometimes inside a loop, WinDivRecv fails with LastWin32Error : 6 = INVALID_HANDLE_VALUE
is it because the GC is messing with the handle ? I tried GC.KeepAlive(handle)
and it didn't change anything.
C++\CLI wrapper : (bridge between unmanaged C DLL and managed C# code)
[Suggested option in the comments below]
I followed these steps :
- Create a C++/CLI library project
- Create a Native C++ class that wraps the C functions
- Create a managed C++/CLI class with a field pointing to a native class instance and wraps all the unmanaged methods and do the necessary marshalling.
- Try to build ==> fails with the famous LNK2019 and LNK2028
Should I add the WinDivert DLL as a reference or only WinDivert.h ? What about the kernel driver .sys files ? And honestly it's not easier than PInvoke but a lot worse, I still have to do the same marshalling of non-blittable data types and struct/enum definitions !
来源:https://stackoverflow.com/questions/23296106/windivert-in-c-sharp