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

namespace nfapinet 
{
     public enum NF_STATUS
	{
		NF_STATUS_SUCCESS		= 0,
		NF_STATUS_FAIL			= -1,
		NF_STATUS_INVALID_ENDPOINT_ID	= -2,
		NF_STATUS_NOT_INITIALIZED	= -3,
		NF_STATUS_IO_ERROR		= -4
	};

	public enum NF_DIRECTION
	{
		NF_D_IN = 1,		// Incoming TCP connection or UDP packet
		NF_D_OUT = 2,		// Outgoing TCP connection or UDP packet
		NF_D_BOTH = 3		// Any direction
	};

	public enum NF_FILTERING_FLAG
	{
		NF_ALLOW = 0,		// Allow the activity without filtering transmitted packets
		NF_BLOCK = 1,		// Block the activity
		NF_FILTER = 2,		// Filter the transmitted packets
		NF_SUSPENDED = 4,	// Suspend receives from server and sends from client
		NF_OFFLINE = 8,		// Emulate establishing a TCP connection with remote server
		NF_INDICATE_CONNECT_REQUESTS = 16, // Indicate outgoing connect requests to API
		NF_DISABLE_REDIRECT_PROTECTION = 32, // Disable blocking indicating connect requests for outgoing connections of local proxies
        NF_PEND_CONNECT_REQUEST = 64, // Pend outgoing connect request to complete it later using nf_complete(TCP|UDP)ConnectRequest
        NF_FILTER_AS_IP_PACKETS = 128, // Indicate the traffic as IP packets via ipSend/ipReceive
        NF_READONLY = 256,				// Don't block the IP packets and indicate them to ipSend/ipReceive only for monitoring
        NF_CONTROL_FLOW = 512,			// Use the flow limit rules even without NF_FILTER flag
		NF_REDIRECT = 1024,			// Redirect the outgoing TCP connections to address specified in redirectTo
		NF_BYPASS_IP_PACKETS = 2048,	// Bypass the traffic as IP packets, when used with NF_FILTER_AS_IP_PACKETS flag
    };

    public enum NF_FLAGS
    {
	    NFF_NONE = 0, 
	    NFF_DONT_DISABLE_TEREDO	= 1, // Don't disable Teredo 
	    NFF_DONT_DISABLE_TCP_OFFLOADING	= 2, // Don't disable TCP offloading
	    NFF_DISABLE_AUTO_REGISTER	= 4, // Dont' try to register the driver automatically
	    NFF_DISABLE_AUTO_START	= 8,    // Dont' try to start the driver automatically
    };


    public enum NF_CONSTS
    {
		NF_MAX_ADDRESS_LENGTH = 28,
		NF_MAX_IP_ADDRESS_LENGTH = 16
    };

    public enum NF_DRIVER_TYPE
    {
	    DT_UNKNOWN = 0,
	    DT_TDI = 1,
	    DT_WFP = 2
    };

	/**
	*	Filtering rule
	**/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_RULE
	{
        public int protocol;	// IPPROTO_TCP or IPPROTO_UDP        
        public UInt32 processId;	// Process identifier
        public Byte direction;	// See NF_DIRECTION
        public ushort localPort;	// Local port
        public ushort remotePort;	// Remote port
        public ushort ip_family;	// AF_INET for IPv4 and AF_INET6 for IPv6
		
		// Local IP (or network if localIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=(int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddress;	
		
		// Local IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddressMask; 
		
		// Remote IP (or network if remoteIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] remoteIpAddress; 
		
		// Remote IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] remoteIpAddressMask;

        public UInt32 filteringFlag;	// See NF_FILTERING_FLAG
	};

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_PORT_RANGE
    {
        public ushort valueLow;
        public ushort valueHigh;
    };

    /**
    *	Filtering rule with additional fields
    **/
    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
    public struct NF_RULE_EX
    {
        public int protocol;	// IPPROTO_TCP or IPPROTO_UDP        
        public UInt32 processId;	// Process identifier
        public Byte direction;	// See NF_DIRECTION
        public ushort localPort;	// Local port
        public ushort remotePort;	// Remote port
        public ushort ip_family;	// AF_INET for IPv4 and AF_INET6 for IPv6

        // Local IP (or network if localIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddress;

        // Local IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddressMask;

        // Remote IP (or network if remoteIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] remoteIpAddress;

        // Remote IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] remoteIpAddressMask;

        public UInt32 filteringFlag;	// See NF_FILTERING_FLAG

        // Tail part of the process path mask 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string processName;

	    public NF_PORT_RANGE	localPortRange; // Local port(s)
	    public NF_PORT_RANGE	remotePortRange; // Remote port(s)

	    // Remote address for redirection as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
	    [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] redirectTo;
	    
        // Process identifier of a local proxy
	    public UInt32 localProxyProcessId;	
    };

	/**
	*	TCP connection properties
	**/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_TCP_CONN_INFO
	{
		public UInt32	filteringFlag;	// See NF_FILTERING_FLAG
        public UInt32 processId;		// Process identifier
        public Byte direction;		// See NF_DIRECTION
        public ushort ip_family;		// AF_INET for IPv4 and AF_INET6 for IPv6
		
		// Local address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] localAddress; 
		
		// Remote address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] remoteAddress;
	};

	/**
	*	UDP endpoint properties
	**/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_UDP_CONN_INFO
	{
        public UInt32 processId;		// Process identifier
        public ushort ip_family;		// AF_INET for IPv4 and AF_INET6 for IPv6
		
		// Local address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] localAddress; 
	};

    /**
    *	UDP options
    **/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_UDP_OPTIONS
    {
        public UInt32 flags;		// UDP flags
        public Int32 optionsLength; // options length
        [MarshalAs(UnmanagedType.ByValArray)]
        public byte[] options; // Options array
    };

	/**
	*	UDP connect request properties
	**/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_UDP_CONN_REQUEST
	{
        public UInt32 filteringFlag;	// See NF_FILTERING_FLAG
        public UInt32 processId;		// Process identifier
        public ushort ip_family;		// AF_INET for IPv4 and AF_INET6 for IPv6
		
		// Local address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] localAddress; 

		// Remote address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] remoteAddress;
	};

    public enum NF_IP_FLAG
    {
        NFIF_NONE = 0,		// No flags
        NFIF_READONLY = 1,	// The packet was not blocked and indicated only for monitoring in read-only mode 
                            // (see NF_READ_ONLY flags from NF_FILTERING_FLAG).
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_IP_PACKET_OPTIONS
    {	
	    public ushort	ip_family;	// AF_INET for IPv4 and AF_INET6 for IPv6
	    public UInt32   ipHeaderSize;	// Size in bytes of IP header
	    public UInt32	compartmentId;  // Network routing compartment identifier (can be zero)
	    public UInt32	interfaceIndex; // Index of the interface on which the original packet data was received (irrelevant to outgoing packets)
        public UInt32   subInterfaceIndex;	// Index of the subinterface on which the original packet data was received (irrelevant to outgoing packets)
        public UInt32   flags;				// Can be a combination of flags from NF_IP_FLAG enumeration
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_FLOWCTL_DATA
    {
        public ulong inLimit;
        public ulong outLimit;
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_FLOWCTL_MODIFY_DATA
    {
        public UInt32 fcHandle;
        public NF_FLOWCTL_DATA	data;
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_FLOWCTL_STAT
    {
        public ulong inBytes;
        public ulong outBytes;
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_FLOWCTL_SET_DATA
    {
        public ulong endpointId;
        public UInt32 fcHandle;
    };

    /**
    *	Binding rule
    **/
    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
    public struct NF_BINDING_RULE
    {
        // IPPROTO_TCP or IPPROTO_UDP
        public int		protocol;	

        // Process identifier
	    public UInt32	processId;	

	    // Tail part of the process path mask 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string   processName;

        // Local port
	    public ushort	localPort;	

        // AF_INET for IPv4 and AF_INET6 for IPv6
	    public ushort	ip_family;	
    	
	    // Local IP (or network if localIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
	    public byte[]	localIpAddress;	
    	
	    // Local IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
	    public byte[]	localIpAddressMask; 
    	
	    // Redirect bind request to this IP 
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
	    public byte[]	newLocalIpAddress; 

	    // Redirect bind request to this port, if it is not zero
	    public ushort	newLocalPort;

        // See NF_FILTERING_FLAG, NF_ALLOW or NF_FILTER
	    public ulong	filteringFlag;	
    };

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_threadStart();
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_threadEnd();
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpConnected(ulong id, ref NF_TCP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpClosed(ulong id, ref NF_TCP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpReceive(ulong id, IntPtr buf, int len);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpSend(ulong id, IntPtr buf, int len);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpCanReceive(ulong id);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpCanSend(ulong id);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpCreated(ulong id, ref NF_UDP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnReq);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpClosed(ulong id, ref NF_UDP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpReceive(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpSend(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpCanReceive(ulong id);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpCanSend(ulong id);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_ipReceive(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_ipSend(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions);
    
    /*
     * Event handler interface
     */
    public interface NF_EventHandler
    {
        void threadStart();
        void threadEnd();
	    void tcpConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo);
        void tcpConnected(ulong id, NF_TCP_CONN_INFO pConnInfo);
        void tcpClosed(ulong id, NF_TCP_CONN_INFO pConnInfo);
        void tcpReceive(ulong id, IntPtr buf, int len);
        void tcpSend(ulong id, IntPtr buf, int len);
        void tcpCanReceive(ulong id);
        void tcpCanSend(ulong id);
        void udpCreated(ulong id, NF_UDP_CONN_INFO pConnInfo);
        void udpConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnReq);
        void udpClosed(ulong id, NF_UDP_CONN_INFO pConnInfo);
        void udpReceive(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options, int optionsLen);
        void udpSend(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options, int optionsLen);
        void udpCanReceive(ulong id);
        void udpCanSend(ulong id);
    };

    /*
     * IP event handler interface
     */
    public interface NF_IPEventHandler
    {
        void ipReceive(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions);
        void ipSend(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions);
    };

    /*
     *  Internal events forwarder
     */
    class NF_EventHandlerFwd
    {
        public static NF_EventHandler m_pEventHandler = null;
        
        public static void threadStart()
        {
            m_pEventHandler.threadStart();
        }
        public static void threadEnd()
        {
            m_pEventHandler.threadEnd();
        }
	    public static void tcpConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.tcpConnectRequest(id, ref pConnInfo);
        }
        public static void tcpConnected(ulong id, ref NF_TCP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.tcpConnected(id, pConnInfo);
        }
        public static void tcpClosed(ulong id, ref NF_TCP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.tcpClosed(id, pConnInfo);
        }
        public static void tcpReceive(ulong id, IntPtr buf, int len)
        {
            m_pEventHandler.tcpReceive(id, buf, len);
        }
        public static void tcpSend(ulong id, IntPtr buf, int len)
        {
            m_pEventHandler.tcpSend(id, buf, len);
        }
        public static void tcpCanReceive(ulong id)
        {
            m_pEventHandler.tcpCanReceive(id);
        }
        public static void tcpCanSend(ulong id)
        {
            m_pEventHandler.tcpCanSend(id);
        }
        public static void udpCreated(ulong id, ref NF_UDP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.udpCreated(id, pConnInfo);
        }
        public static void udpConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnReq)
        {
            m_pEventHandler.udpConnectRequest(id, ref pConnReq);
        }
        public static void udpClosed(ulong id, ref NF_UDP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.udpClosed(id, pConnInfo);
        }
        public static void udpReceive(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options)
        {
            if (options.ToInt64() != 0)
            {
                NF_UDP_OPTIONS optionsCopy = (NF_UDP_OPTIONS)Marshal.PtrToStructure((IntPtr)options, typeof(NF_UDP_OPTIONS));
                int optionsLen = 8 + optionsCopy.optionsLength;
                m_pEventHandler.udpReceive(id, remoteAddress, buf, len, options, optionsLen);
            }
            else
            {
                m_pEventHandler.udpReceive(id, remoteAddress, buf, len, (IntPtr)null, 0);
            }
        }
        public static void udpSend(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options)
        {
            if (options.ToInt64() != 0)
            {
                NF_UDP_OPTIONS optionsCopy = (NF_UDP_OPTIONS)Marshal.PtrToStructure((IntPtr)options, typeof(NF_UDP_OPTIONS));
                int optionsLen = 8 + optionsCopy.optionsLength;
                m_pEventHandler.udpSend(id, remoteAddress, buf, len, options, optionsLen);
            }
            else
            {
                m_pEventHandler.udpSend(id, remoteAddress, buf, len, (IntPtr)null, 0);
            }
        }
        public static void udpCanReceive(ulong id)
        {
            m_pEventHandler.udpCanReceive(id);
        }
        public static void udpCanSend(ulong id)
        {
            m_pEventHandler.udpCanSend(id);
        }
    };

    /*
     *  Internal IP events forwarder
     */
    class NF_IPEventHandlerFwd
    {
        public static NF_IPEventHandler m_pEventHandler = null;

        public static void ipReceive(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions)
        {
            m_pEventHandler.ipReceive(buf, len, ref ipOptions);
        }
        public static void ipSend(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions)
        {
            m_pEventHandler.ipSend(buf, len, ref ipOptions);
        }
    };

    /*
     *  Event handler structure for C API
     */
    [StructLayout(LayoutKind.Sequential, Pack=1)]
	struct NF_EventHandlerInternal
	{
		public cbd_threadStart threadStart;
		public cbd_threadEnd threadEnd;
		public cbd_tcpConnectRequest tcpConnectRequest;
		public cbd_tcpConnected tcpConnected;
		public cbd_tcpClosed tcpClosed;
		public cbd_tcpReceive tcpReceive;
		public cbd_tcpSend tcpSend;
		public cbd_tcpCanReceive tcpCanReceive;
		public cbd_tcpCanSend tcpCanSend;
		public cbd_udpCreated udpCreated;
		public cbd_udpConnectRequest udpConnectRequest;
        public cbd_udpClosed udpClosed;
		public cbd_udpReceive udpReceive;
		public cbd_udpSend udpSend;
		public cbd_udpCanReceive udpCanReceive;
		public cbd_udpCanSend udpCanSend;
    };

    /*
     *  IP event handler structure for C API
     */
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct NF_IPEventHandlerInternal
    {
        public cbd_ipReceive ipReceive;
        public cbd_ipSend ipSend;
    };

    // Managed wrapper over API 
	public class NFAPI
	{
        private static IntPtr m_pEventHandlerRaw = (IntPtr)null;
        private static NF_EventHandlerInternal m_pEventHandler;
        private static IntPtr m_pIPEventHandlerRaw = (IntPtr)null;
        private static NF_IPEventHandlerInternal m_pIPEventHandler;

		/**
		* Initializes the internal data structures and starts the filtering thread.
		* @param driverName The name of hooking driver, without ".sys" extension.
		* @param pHandler Pointer to event handling object
		**/
        [DllImport("nfapi", CallingConvention=CallingConvention.Cdecl)]
		public static extern NF_STATUS nf_init(String driverName, IntPtr pHandler);
        
        public static NF_STATUS nf_init(String driverName, NF_EventHandler pHandler)
        {
            NF_EventHandlerFwd.m_pEventHandler = pHandler;

            nf_adjustProcessPriviledges();

            m_pEventHandler = new NF_EventHandlerInternal();
             
            m_pEventHandler.threadStart = new cbd_threadStart(NF_EventHandlerFwd.threadStart);
            m_pEventHandler.threadEnd = new cbd_threadEnd(NF_EventHandlerFwd.threadEnd);
            m_pEventHandler.tcpConnectRequest = new cbd_tcpConnectRequest(NF_EventHandlerFwd.tcpConnectRequest);
            m_pEventHandler.tcpConnected = new cbd_tcpConnected(NF_EventHandlerFwd.tcpConnected);
            m_pEventHandler.tcpClosed = new cbd_tcpClosed(NF_EventHandlerFwd.tcpClosed);
            m_pEventHandler.tcpReceive = new cbd_tcpReceive(NF_EventHandlerFwd.tcpReceive);
            m_pEventHandler.tcpSend = new cbd_tcpSend(NF_EventHandlerFwd.tcpSend);
            m_pEventHandler.tcpCanReceive = new cbd_tcpCanReceive(NF_EventHandlerFwd.tcpCanReceive);
            m_pEventHandler.tcpCanSend = new cbd_tcpCanSend(NF_EventHandlerFwd.tcpCanSend);
            m_pEventHandler.udpCreated = new cbd_udpCreated(NF_EventHandlerFwd.udpCreated);
            m_pEventHandler.udpConnectRequest = new cbd_udpConnectRequest(NF_EventHandlerFwd.udpConnectRequest);
            m_pEventHandler.udpClosed = new cbd_udpClosed(NF_EventHandlerFwd.udpClosed);
            m_pEventHandler.udpReceive = new cbd_udpReceive(NF_EventHandlerFwd.udpReceive);
            m_pEventHandler.udpSend = new cbd_udpSend(NF_EventHandlerFwd.udpSend);
            m_pEventHandler.udpCanReceive = new cbd_udpCanReceive(NF_EventHandlerFwd.udpCanReceive);
            m_pEventHandler.udpCanSend = new cbd_udpCanSend(NF_EventHandlerFwd.udpCanSend);

            m_pEventHandlerRaw = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NF_EventHandlerInternal)));
            Marshal.StructureToPtr(m_pEventHandler, m_pEventHandlerRaw, true);

            return nf_init(driverName, m_pEventHandlerRaw);
        }

		/**
		* Stops the filtering thread, breaks all filtered connections and closes
		* a connection with the hooking driver.
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern void nf_free();

		/**
		* Registers and starts a driver with specified name (without ".sys" extension)
		* @param driverName 
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_registerDriver(String driverName);

        /**
        * Registers and starts a driver with specified name (without ".sys" extension) and path to driver folder
        * @param driverName 
        * @param driverPath 
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_registerDriverEx(String driverName, String driverPath);

        /**
		* Unregisters a driver with specified name (without ".sys" extension)
		* @param driverName 
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_unRegisterDriver(String driverName);


		//
		// TCP control routines
		//

		/**
		* Suspends or resumes indicating of sends and receives for specified connection.
		* @param id Connection identifier
		* @param suspended true for suspend, false for resume 
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpSetConnectionState(ulong id, int suspended);

		/**
		* Sends the buffer to remote server via specified connection.
		* @param id Connection identifier
		* @param buf Pointer to data buffer
		* @param len Buffer length
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpPostSend(ulong id, IntPtr buf, int len);

		/**
		* Indicates the buffer to local process via specified connection.
		* @param id Unique connection identifier
		* @param buf Pointer to data buffer
		* @param len Buffer length
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpPostReceive(ulong id, IntPtr buf, int len);

		/**
		* Breaks the connection with given id.
		* @param id Connection identifier
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpClose(ulong id);

		//
		// UDP control routines
		//

		/**
		* Suspends or resumes indicating of sends and receives for specified socket.
		* @param id Socket identifier
		* @param suspended true for suspend, false for resume 
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_udpSetConnectionState(ulong id, int suspended);

		/**
		* Sends the buffer to remote server via specified socket.
		* @param id Socket identifier
		* @param remoteAddress Destination address
		* @param buf Pointer to data buffer
		* @param len Buffer length
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_udpPostSend(ulong id, 
			IntPtr remoteAddress, 
			IntPtr buf, int len,
			IntPtr options);

        /**
        * Indicates the buffer to local process via specified socket.
        * @param id Unique connection identifier
        * @param remoteAddress Source address
        * @param buf Pointer to data buffer
        * @param len Buffer length
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_udpPostReceive(ulong id, 
			IntPtr remoteAddress, 
			IntPtr buf, int len,
			IntPtr options);

	/**
	* Indicates a packet to TCP/IP stack
	* @param buf Pointer to IP packet
	* @param len Buffer length
	* @param options IP options
	**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_ipPostReceive(
            IntPtr buf, int len,
            ref NF_IP_PACKET_OPTIONS options);

	/**
	* Sends a packet to remote IP
	* @param buf Pointer to IP packet
	* @param len Buffer length
	* @param options IP options
	**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_ipPostSend(
            IntPtr buf, int len,
            ref NF_IP_PACKET_OPTIONS options);

		//
		// Filtering rules 
		//

		/**
		* Add a rule to the head of rules list in driver.
		* @param pRule See <tt>NF_RULE</tt>
		* @param toHead TRUE (1) - add rule to list head, FALSE (0) - add rule to tail
		**/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS nf_addRule(ref NF_RULE pRule, int toHead);

        private static void updateAddressLength(ref byte[] buf)
        {
            if (buf == null)
            {
                buf = new byte[(int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH];
            }
            else
            {
                if (buf.Length < (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)
                {
                    Array.Resize(ref buf, (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH);
                }
            }
        }

        public static NF_STATUS nf_addRule(NF_RULE pRule, int toHead)
        {
            updateAddressLength(ref pRule.localIpAddress);
            updateAddressLength(ref pRule.localIpAddressMask);
            updateAddressLength(ref pRule.remoteIpAddress);
            updateAddressLength(ref pRule.remoteIpAddressMask);

            return nf_addRule(ref pRule, toHead);
        }

        /**
        * Add a rule to the head of rules list in driver.
        * @param pRule See <tt>NF_RULE</tt>
        * @param toHead TRUE (1) - add rule to list head, FALSE (0) - add rule to tail
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS nf_addRuleEx(ref NF_RULE_EX pRule, int toHead);

        public static NF_STATUS nf_addRuleEx(NF_RULE_EX pRule, int toHead)
        {
            updateAddressLength(ref pRule.localIpAddress);
            updateAddressLength(ref pRule.localIpAddressMask);
            updateAddressLength(ref pRule.remoteIpAddress);
            updateAddressLength(ref pRule.remoteIpAddressMask);

            return nf_addRuleEx(ref pRule, toHead);
        }

        /**
        * Replace the rules in driver with the specified array.
        * @param pRules Array of <tt>NF_RULE</tt> structures
        * @param count Number of items in array
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS nf_setRules(IntPtr pRules, int count);

        public static NF_STATUS nf_setRules(NF_RULE[] rules)
        {
            NF_RULE pRule;

            IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NF_RULE)) * rules.Length);

            long longPtr = ptr.ToInt64(); 
            for (int i = 0; i < rules.Length; i++)
            {
                pRule = rules[i];

                updateAddressLength(ref pRule.localIpAddress);
                updateAddressLength(ref pRule.localIpAddressMask);
                updateAddressLength(ref pRule.remoteIpAddress);
                updateAddressLength(ref pRule.remoteIpAddressMask);

                Marshal.StructureToPtr(pRule, new IntPtr(longPtr), false);

                longPtr += Marshal.SizeOf(typeof(NF_RULE));
            }

            return nf_setRules(ptr, rules.Length);
        }

        /**
        * Replace the rules in driver with the specified array.
        * @param pRules Array of <tt>NF_RULE</tt> structures
        * @param count Number of items in array
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS nf_setRulesEx(IntPtr pRules, int count);

        public static NF_STATUS nf_setRulesEx(NF_RULE_EX[] rules)
        {
            NF_RULE_EX pRule;

            IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NF_RULE_EX)) * rules.Length);

            long longPtr = ptr.ToInt64();
            for (int i = 0; i < rules.Length; i++)
            {
                pRule = rules[i];

                updateAddressLength(ref pRule.localIpAddress);
                updateAddressLength(ref pRule.localIpAddressMask);
                updateAddressLength(ref pRule.remoteIpAddress);
                updateAddressLength(ref pRule.remoteIpAddressMask);

                Marshal.StructureToPtr(pRule, new IntPtr(longPtr), false);

                longPtr += Marshal.SizeOf(typeof(NF_RULE_EX));
            }

            return nf_setRulesEx(ptr, rules.Length);
        }

        /**
        * Removes all rules from driver.
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_deleteRules();

		/**
		 *	Sets the timeout for TCP connections and returns old timeout.
		 *	@param timeout Timeout value in milliseconds. Specify zero value to disable timeouts.
		 */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern UInt32 nf_setTCPTimeout(UInt32 timeout);

		/**
		 *	Disables indicating TCP packets to user mode for the specified endpoint
		 *  @param id Socket identifier
		 */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpDisableFiltering(ulong id);

		/**
		 *	Disables indicating UDP packets to user mode for the specified endpoint
		 *  @param id Socket identifier
		 */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_udpDisableFiltering(ulong id);

        /**
        * Returns TRUE if the specified process acts as a local proxy, accepting the redirected TCP connections.
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool nf_tcpIsProxy(uint processId);

        /**
        * Set the number of worker threads and initialization flags.
        * The function should be called before nf_init. 
        * By default nThreads = 1 and flags = 0
        * @param nThreads Number of worker threads for NF_EventHandler events 
        * @param flags A combination of flags from <tt>NF_FLAGS</tt>
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern void nf_setOptions(uint nThreads, uint flags);

        /**
        * Complete TCP connect request pended using flag NF_PEND_CONNECT_REQUEST.
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_completeTCPConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo);

        /**
        * Complete UDP connect request pended using flag NF_PEND_CONNECT_REQUEST.
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_completeUDPConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnInfo);

        /**
        * Returns in pConnInfo the properties of TCP connection with specified id.
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getTCPConnInfo(ulong id, ref NF_TCP_CONN_INFO pConnInfo);

        /**
        * Returns in pConnInfo the properties of UDP socket with specified id.
        **/
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getUDPConnInfo(ulong id, ref NF_UDP_CONN_INFO pConnInfo);

        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool nf_getProcessNameW(uint processId, IntPtr buf, int len);

        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool nf_getProcessNameFromKernel(uint processId, IntPtr buf, int len);

        /**
        * Returns the process name for given process id
        **/
        public static unsafe String nf_getProcessNameFromKernel(UInt32 processId)
        {
            char[] buf = new char[1024];

            fixed (char* p = buf)
            {
                 if (nf_getProcessNameFromKernel(processId, (IntPtr)p, buf.Length))
                 {
                     return Marshal.PtrToStringUni((IntPtr)p);
                 }
            }

            return "System";
        }

        /**
        * Returns the process name for given process id
        **/
        public static unsafe String nf_getProcessName(UInt32 processId)
        {
            char[] buf = new char[256];

            fixed (char* p = buf)
            {
                if (nf_getProcessNameW(processId, (IntPtr)p, buf.Length))
                {
                    return Marshal.PtrToStringUni((IntPtr)p);
                }
            }

            return "System";
        }
    
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern void nf_adjustProcessPriviledges();

        /*
        * Set the event handler for IP filtering events
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern void nf_setIPEventHandler(IntPtr pHandler);

        /*
        * Set the event handler for IP filtering events
        */
        public static void nf_setIPEventHandler(NF_IPEventHandler pHandler)
        {
            NF_IPEventHandlerFwd.m_pEventHandler = pHandler;

            m_pIPEventHandler = new NF_IPEventHandlerInternal();

            m_pIPEventHandler.ipReceive = new cbd_ipReceive(NF_IPEventHandlerFwd.ipReceive);
            m_pIPEventHandler.ipSend = new cbd_ipSend(NF_IPEventHandlerFwd.ipSend);

            m_pIPEventHandlerRaw = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NF_IPEventHandlerInternal)));
            Marshal.StructureToPtr(m_pIPEventHandler, m_pIPEventHandlerRaw, true);

            nf_setIPEventHandler(m_pIPEventHandlerRaw);
        }

        /**
        * Add flow control context
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_addFlowCtl(ref NF_FLOWCTL_DATA pData, ref UInt32 pFcHandle);

        /**
        * Delete flow control context
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_deleteFlowCtl(UInt32 fcHandle);

        /**
        * Associate flow control context with TCP connection
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_setTCPFlowCtl(ulong id, UInt32 fcHandle);

        /**
        * Associate flow control context with UDP socket
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_setUDPFlowCtl(ulong id, UInt32 fcHandle);

        /**
        * Modify flow control context limits
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_modifyFlowCtl(UInt32 fcHandle, ref NF_FLOWCTL_DATA pData);

        /**
        * Get flow control context statistics as the numbers of in/out bytes
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getFlowCtlStat(UInt32 fcHandle, ref NF_FLOWCTL_STAT pStat);

        /**
        * Get TCP connection statistics as the numbers of in/out bytes.
        * The function can be called only from tcpClosed handler!
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getTCPStat(ulong id, ref NF_FLOWCTL_STAT pStat);

        /**
        * Get UDP socket statistics as the numbers of in/out bytes.
        * The function can be called only from udpClosed handler!
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getUDPStat(ulong id, ref NF_FLOWCTL_STAT pStat);

        public enum NF_SOCKET_OPTIONS
        {
            TCP_SOCKET_NODELAY = 1,
            TCP_SOCKET_KEEPALIVE = 2,
            TCP_SOCKET_OOBINLINE = 3,
            TCP_SOCKET_BSDURGENT = 4,
            TCP_SOCKET_ATMARK = 5,
            TCP_SOCKET_WINDOW = 6
        }

        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpSetSockOpt(ulong id, NF_SOCKET_OPTIONS optname, ref int optval, int optlen);

        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpSetSockOpt(ulong id, NF_SOCKET_OPTIONS optname, IntPtr optval, int optlen);

        public static NF_STATUS nf_tcpSetSockOpt(ulong id, NF_SOCKET_OPTIONS optname, bool optval)
        {
            int dword = optval ? 1 : 0;
            return nf_tcpSetSockOpt(id, optname, ref dword, Marshal.SizeOf(typeof(int)));
        }

        /**
        * Add binding rule to driver
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS 
        nf_addBindingRule(ref NF_BINDING_RULE pRule, int toHead);

        public static NF_STATUS 
        nf_addBindingRule(NF_BINDING_RULE pRule, int toHead)
        {
            updateAddressLength(ref pRule.localIpAddress);
            updateAddressLength(ref pRule.localIpAddressMask);
            updateAddressLength(ref pRule.newLocalIpAddress);

            return nf_addBindingRule(ref pRule, toHead);
        }

        /**
        * Delete all binding rules from driver
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS
        nf_deleteBindingRules();

        /**
        * Returns the type of attached driver (DT_WFP, DT_TDI or DT_UNKNOWN)
        */
        [DllImport("nfapi", CallingConvention = CallingConvention.Cdecl)]
        public static extern UInt32
        nf_getDriverType();
    };

}
