Komodia's Redirector DLL framework guide

From Komodia
Jump to: navigation, search

This page covers the programming aspects of Komodia's Redirector product when writing a DLL to extend the Redirector’s functionality, this option is good for you if you know C/C++ and you want to have best performance without the need to modify the product's source code (this works also with binary products)

There are two extra options to extend the Redirector:

  • Modify the source code directly inside the class: CClientCustomizations (it's similar to the DLL interface), you also need to uncomment this line: #define SERVICE_CUSTOM_EXTENSIONS inside KomodiaProduct.h
  • Use the Komodia's Redirector COM framework guide to extend the Redirector via a COM aware language such as: VB.Net/C#/Delphi

Contents

Downloading the samples

You can download the samples from: Komodia's DLL Framework sample, keep in mind that you need the trial or full version of Komodia's Redirector to work with the samples.

Setting the Redirector to work with the DLL

You can view how to set the Redirector to work with the DLL here: Komodia's Redirector installation guide#DLL_control.

Working with the samples

We provided a number of samples with a direct refrences to what needs changing which makes creating your application very easy. Of course we recommend on reading this guide, but to get started just modify the lines we marked out.

There is much more you can do with the framework that is not contained in the samples, the samples are the scenarios that is most used by our clients.

PCProxyDLL - Bare framework

This sample is a minimum bare, it doesn't do anything, just compiles.

PCProxyDLL - Redirect host or url

Host redirection

This sample redirects a connection based on the host or URL, you need to modify the following code snippest which is located inside PCProxyDLL.cpp at line 319:

if (pContext && //This checks that we are inside a new request, and not inside an existing request
    aHeader.Feed(pData,
	         dwDataSize))
	{
		//Get information about the session
		std::string sHost;
		sHost=aHeader.GetHost();

		//Get the resource
		std::string sResource;
		sResource=aHeader.GetFullAddress();

		//Should we send a redirect?
		if (stristr(sHost.c_str(),
			    "cnn.com") ||
		    stristr(sHost.c_str(),
			    "amazon.com"))
		{
			//Build a redirect
			std::string sRedirect;
			
			//Is it a SSL redirect?
			if (!pContext->bSSL)
				sRedirect=BuildRedirectReply("http://www.google.com",true,true);
			else
				sRedirect=BuildRedirectReply("https://gmail.google.com",true,true);

			//Send it back to the user
			*pNewBuffer=new char[sRedirect.size()];
			memcpy(*pNewBuffer,
				   sRedirect.c_str(),
				   sRedirect.size());
			rNewDataSize=sRedirect.size();
			rNewBufferToReceive=true;
			rTerminateSession=true;

			//Done
			return true;
		}
  • On line 390-393 you can check the host or you can also check the full URL by comparing the variable sResource.
  • On line 396-415 you perform the actual redirect, based if it's HTTP or HTTPS.

Google safe search

                        //Lets say we want to modify the outgoing request and not redirect it (this is another feature
			//which is not related to the redirect code)
			if (stristr(sHost.c_str(),
				    "google.com"))
			{
				//Lets add a safe strict option
				std::string sNewRequest;

				//Which params are we parsing?
				if (bFed)
					sNewRequest=BuildNewSafeRequest(&(pContext->aData[0]),
									pContext->aData.size());
				else
					sNewRequest=BuildNewSafeRequest(pData,
									dwDataSize);

				//Did we get a request
				if (!sNewRequest.empty())
				{
					//Replace it
					//Send it back to the user
					*pNewBuffer=new char[sNewRequest.size()];
					memcpy(*pNewBuffer,
					       sNewRequest.c_str(),
					       sNewRequest.size());
					rNewDataSize=sNewRequest.size();
					
					//Done
					return true;
				}
			}

Another feature is the ability to force google safe search, you can see the actual code on line 420-447.

PCProxyDLL - Add headers

This samples adds and removes headers from the outgoing HTTP data, taken from line 133:

if (pContext &&
    pContext->bNewRequest && //This checks that we are inside a new request, and not inside an existing request
    IsHTTP(pData,
 	   dwDataSize))
	{
		//The request can fragment and we don't want to be here per fragment, but per request
		pContext->bNewRequest=false;

		//Here we build the filter
		//These fileds are just for the sake for demonstration
		//Please change them to reflect your own fields
		CHTTPHelper aHelper;
		
		//Each method can be called multiple times
		//Fields to change
		//Uncomment the next code, keep in mind that it may cause problems with
		//Sites that are based on your browser
		aHelper.ChangeHTTPHeader("user-agent","Blank agent");

		//Fields to add
		aHelper.InsertSpecialField("X-Redirector","Just a value");

		//Fields to remove
		aHelper.FilterHTTPHeader("Refer");

		//Build the struct
		//Don't call this function unless you want to have a filter
		//Or have a filter override
		rManipulations=aHelper.GetFinalStruct();

		//Done
		return false;
	}
  • On line 150 the code modifies the "user-agent" field to contain the value of: "Blank agent".
  • On line 153 the code adds a custom header field called: "X-Redirector".
  • On line 156 the code deletes the refer header field.

PCProxyDLL - Redirect host or URL with HTTP Parser

This samples redirects based on host and URL, please note that this DLL works only if the Redirector has the optional HTTP Parser.

Taken from line 142:

        CHTTPHeader aHeader;
	if (aHeader.Feed(pHeader,
			 dwHeaderSize))
	{
		//We can get some usefull information at this stage
		//Host
		std::string sHost(aHeader.GetHost());

		//URI
		std::string sURI(aHeader.GetURL());

		//The full address
		std::string sFullAddress(aHeader.GetFullAddress());

		//Save the host in the session data
		if (pData &&
		    pData->sHost.empty())
		    pData->sHost=sHost;

		//Lets say we want to build a redirect based on the request
		//We need to make sure that we are not redirecting over and over
		//The site we are redirecting to
		//This code performs it
		if (!stristr(sHost.c_str(),
			     "komodia.com") &&
		    stristr(sHost.c_str(),
		  	    "google.com") &&
		    stristr(sURI.c_str(),
		   	    "porn"))
		{
			//Redirect to address, always in the form of http://website
			std::string sRedirectTo;
			sRedirectTo="http://www.komodia.com";

			//Save the data
			*ppNewHeader=new char[sRedirectTo.size()+1];
			memcpy(*ppNewHeader,
				   sRedirectTo.c_str(),
				   sRedirectTo.size()+1);

			//Set size
			rNewHeaderSize=sRedirectTo.size()+1;

			//Let Redirector know that this is a redirect
			return dhrRedirect;
		}
	}
  • On line 165-170 the code checks for Google web site and "porn" keyword.
  • On line 174 the code set the redirection to Komodia's web site.

PCProxyDLL - Modify body based on header

The sample modifies the returns HTML body based on the existance of a header, keep in mind it's possible to just modify the body without testing for the header like in the sample. Taken from line 161:

if (bHeaderCheck)
	{
		//Try to parse the header
		CHTTPHeader aHeader;
		if (aHeader.Feed(pHeader,
				 dwHeaderSize))
		{
			//We can get some usefull information at this stage
			//Response code
			DWORD dwResponseCode(aHeader.GetResponseCode());

			//Try to get the content type
			std::string sContentType(aHeader.GetHeaderString("content-type"));

			//Now there are some content we don't want to perform parental control on
			//Of course you can change these settings
			//These settings are just for sample sake
			//For these kind of files (.swf, .zip, .exe) filtering should be performed at the level of HTTPRequestBeforeSend
			if (strstr(sContentType.c_str(),
				   "image/") ||
			    strstr(sContentType.c_str(),
				   "video/") ||
			    strstr(sContentType.c_str(),
				   "audio/") ||
			   strstr(sContentType.c_str(),
			          "application/"))
				//Don't do parental
				return dhrDontDoParental;
		}
	}
	else
	{
		//Try to parse the header
		CHTTPHeader aHeader;
		if (aHeader.Feed(pHeader,
				 dwHeaderSize))
		{
			//Check if we have a certain header
			if (strstr(aHeader.GetHeaderString("connection").c_str(),
					                   "keep-alive"))
			{
				//Lets modify the body
				std::string sNewBody="<html><body>Just a body</body></html>";
				*ppNewData=new char[sNewBody.length()];
				memcpy(*ppNewData,
				       sNewBody.c_str(),
				       sNewBody.length());

				//Set the size
				rNewDataSize=sNewBody.length();

				//Done
				return dhrModify;
			}
		}
	}
  • On the first line the code tests at which part is it, it's possible that the reply is long and you might know from the header alone if you need this reply or not, so if we only have the header the code tests for multimedia file types and if it matches then this reply will not be processed. You can modify this section to exclude or include your types.
  • If it's the full reply (else part) then the code inserts the header to the header class, and then checks the connection header (you can modify it to any header you want, or remove this test all together), and if it's true it creates a new body. Although the sample doesn't use the old body, you can file the data of the body inside the variable pData.

PCProxyDLL - Bare framework (with accept support)

This sample is a minimum bare that also support incoming connections (this is used for Komodia's Advanced Redirector, it doesn't do anything, just compiles.

DLL Functions

The DLL exposes seven functions for PCProxy to use. PCProxy performs a sanity check on the DLL when loading it. If one of the functions is missing, PCProxy will not load the DLL.

PCInitialize

bool _stdcall PCInitialize();

This function is called after the DLL is loaded and passes the sanity check. All user-specific initializations, such as creating threads and global variables, should be here.

If the return value is false, the DLL will be unloaded and PCProxy will not use it.

PCUninitialize

void _stdcall PCUninitialize();

This function is called before PCProxy unloads the DLL. It is not called if Initialize returned a false value. All uninitializations should be performed here.

Deallocate

void _stdcall Deallocate(char* pData);

This function is called to deallocate data that the DLL has allocated. The reason for this function is that different types of compilers and languages use different types of allocation and deallocation commands. Thus, PCProxy will deallocate data allocated by the DLL using this method.

In the sample we use a C++ array delete:

delete [] pData;

Context variable

The context variable is seen accross the calls that start with NewConnection where the user should set it to hold a custom number or struct to store data which will be used in the other calls that belong to the same session. The usage of the context is optional.

The sequence is:

  1. NewConnection - Set the context.
  2. DataBeforeSend, DataBeforeReceive, HTTPRequestBeforeSend, HTTPRequestBeforeReceive - Use the context.
  3. ConnectionClosed - Release the context's resources.

Example

Lets say this is your structure:

typedef struct _DLLData
{
    DWORD dwPID;
    std::wstring sAppName;
} DLLData;

Then inside NewConnection you would:

//Build the data
DLLData* pAppData=new DLLData;
pAppData->dwPID=pPInformation.ulPID;
pAppData->sAppName=std::wstring((wchar_t*)pPInformation.pProcessName);

//Save the data
rContext=(ContextDWORD*)pAppData;

Then inside the sub functions you can access that struct by doing:

DLLData* pAppData=(DLLData*)rContext;

Also don't forget to delete it on ConnectionClose:

DLLData* pAppData=(DLLData*)rContext;
delete pAppData;

NewConnection

bool _stdcall NewConnection(ContextDWORD& rContext,
			    bool bFromEncryption,
			    unsigned long usListeningPort,
			    unsigned long& rIP,
			    unsigned short& rPort,
			    const ProcessInformation& pPInformation,
			    bool& rEnableSSL,
			    bool& rSSLSwitch,
			    bool& bFreezeReceive,
			    const char* pPeekData,
			    unsigned long ulPeekDataSize,
			    unsigned long& rWaitMoreData,
			    ProxyInformation& rProxyInformation,
                            const char* pDNSEntry);

This function is called whenever a new connection was made to PCProxy.

The parameters are:

rContext – A DWORD to be used across all functions that are called for this connection. The best practice is to allocate a struct with all necessary connection-specific information, and store its address inside this variable.

On Windows 8/8.1 when using a WFP, in case there's a cascading proxy redirect, there may be an option to get the original application (on top of the proxy process that was redirected), and then rContext would be a pointer to a wide string containing that process name (you can still modify the context as you would when it was zero).

In case the connection intercepted was part of HTTP Connect instead of rContext==0, it will contain a char* string pointing as the HTTP Connect command.

Because it's possible to get a context from the SDK, in the default sample the context structure contains a reserved variable with zero value, to allow to know if the context came from the SDK or created from the DLL.

bFromEncryption – In case the data was originally encrypted with SSL but decrypted with the SSL decryptors, this flag will be true.

usListeningPort – The port of the socket that accepted the connection inside the Redirector (currently it will be 12344 or 12346).

rIP – The destination IP that this session is trying to connect to. The programmer can change this value and the session will connect to the new IP.

rPort – The destination port that this session is trying to connect to. The programmer can change this value and the session will connect to the new port.

pPInformation – Contains information about the calling process, the structure definition is:

typedef struct _ProcessInformation
{
	unsigned long		ulPID;
	const unsigned short*	pProcessName;
	const unsigned short*	pDomain;
	const unsigned short*	pUsername;
} ProcessInformation;

The reason the strings are unsigned short* and not char* is because they are unicode, they can easily be converted to std using:

std::wstring sProcessName((wchar_t*)pPInformation.pProcessName);

rEnableSSL – Outputs this stream as SSL data (the port will usually be changed to 443).

rSSLSwitch – Set to true to notify that you might change this connection to SSL at a later stage, but not now. Useful for proxies.

bFreezeReceive – Set to true to tell the Redirector not to send you any client data for processing until you are ready. Useful when negotiating with a 3rd party proxy.

pPeekData – This buffer contains data “peeked” from the client, which means for example that in a web request this can contain the actual request before making any connection, this is useful when wanting to make connecting decisions based on the traffic type.

rWaitMoreData – This flag sets the timeout in milliseconds to wait before invoking this function again, more information on this in a few paragraphs below.

rProxyInformation – Contains the proxy information, in case there’s a global proxy defined, the structure will hold this data, it is also possible to modify the struct to disable the proxy, enable a proxy, and of course set the proxy settings, this setting is per connection.

The structure definition:

typedef enum _DLLProxyType
{
	dptHTTP,
	dptHTTPConnect,
	dptHTTPConnectSSL,
	dptHTTPHybrid,
	dptHTTPHybridSSL,
	dptSocks4,
	dptSocks5
} DLLProxyType;

You can see what each value means here: Komodia's Redirector installation guide#Proxy_type.

typedef struct _ProxyInformation
{
	bool		bUsingProxy;
	DLLProxyType	aProxyType;
	char*		pUsername;
	char*		pPassword;
	unsigned long	ulIP;
	unsigned short	usPort;
} ProxyInformation;

Return value: True – Allow this session False – Reject this session

Sample code:

//Allow only port 80
if (usPort!=80)
    return false;
else
    return true;

This code allows only port 80 sessions through. Keep in mind that it only blocks sessions that are intercepted by the LSP according to the rules you set.

Sample code for redirecting all port 80 sessions to SSL:

//Is it HTTP port?
if (rPort==80)
{
	//Change port to HTTPS
	rPort=443;

	//Enable the SSL
	rEnableSSL=true;
}

Sample code for saving session specific information inside the context variable:

//Defined at global scope
typedef struct _DLLData
{
	DWORD			dwIP;
	Unsigned short	usPort;
} DLLData;

//This part goes into the function
DLLData* pDLLData=new DLLData;
pDLLData->dwIP=rIP;
pDLLData->usPort=rPort;

//Store the struct at context level
rContext=(DWORD)pDLLData;

//Some code

//Done
return true;

Inside the sample DLL (the one with the code), this function contains code that detects whether the session is HTTP and if so, it waits until it has the complete session.

HandleProxyHTTPConnect

bool _stdcall HandleProxyHTTPConnect(const char* pConnectString,
				     char** ppNewString,
				     ContextDWORD dwContext)

This function is called after the Redirector creates the HTTP Connect clause, it allows you to modify the content of it, and usually it is done to add custom authentication parameters.

pConnectString – Original connection clause (null terminated)

ppNewString – If the string is changed, the new string (null terminated) will be allocated into this variable (by you)

dwContext – The DLL-specific context that was created at the NewConnection function

Return value: True – Clause was modified False – Clause wasn’t modified

DataBeforeSend

bool _stdcall DataBeforeSend(const char* pData,
			     DWORD dwDataSize,
			     char** pNewBuffer, 
			     DWORD& rNewDataSize,
			     bool& rTerminateSession,
			     bool& rNewBufferToReceive,
                             OutgoingDataManipulations& rManipulations
			     ContextDWORD dwContext);

This function is called before sending the data to the final destination. For example, HTTP requests are seen at this stage, keep in mind that TCP is stream based which means that data comes in chunks, therefore HTTP requests can be chunked across multiple calls, there are number of ways to make sure you receive a complete web request:

  1. Use the code inside NewConnection (under the DLL sample) which waits until it has the complete web request, and then you will receive the complete request here.
  2. Use HTTP Parser add-on module, which does this for you transparently, you, can see how the HTTP Parser add-on module works later in this manual.
  3. In case you want to do perform header manipulation such as removing/modifying/adding fields, you don’t need the complete request, you can just use the built in filtering solution that does that for you (sample code is inside the DLL sample)

The parameters are:

pData – The data to process.

dwDataSize – The size of data to process.

pNewBuffer – If the data is modified, this will hold the modified data. Must be allocated by the programmer, since the proxy will delete it.

rNewDataSize – If the data is modified, this will hold the modified data size.

rTerminateSession – Set to true to terminate the session when this function exits (if you don't want the data to be sent, you need to clear it).

rNewBufferToReceive – Takes the contents of the new data and sends it to the application that initiated the session (used mostly for redirects).

rManipulations – This structure contains the manipulations you want to employ on a HTTP header, of course this is optional. You don’t use this structure directly, instead you use a helper class called CHTTPHelper which is described later in this document.

dwContext – The DLL-specific context that was created at the NewConnection function.

Return value: True – Data was modified. False – Data wasn’t modified.

The flag rTerminateSession is not affected by the return value. You can terminate the session without changing the data that you received inside pData.

Sample code to redirect the web traffic into a different site:

        //Check that we are port 80
	//Get socket data from the context
	DLLData* pDLLData=(DLLData*)dwContext;

	//Set terminate to false, just in case
	rTerminateSession=false;

	//Check we are port 80
	if (pDLLData->usPort==80 &&
	    pDLLData->dwIP!=inet_addr("64.118.87.10")) //This is our web site address
	{
		//Our quote
		std::string sQuote;
		sQuote='"';

		//Build a new string
		std::string sTmp;
		sTmp+="<html><head><META HTTP-EQUIV="+sQuote+"Refresh"+sQuote+" CONTENT="+sQuote+"0;URL=";
		sTmp+="http://www.komodia.com";
		sTmp+=sQuote+"></head></html>";

		//Convert the size
		char aTmp[11];
		itoa(sTmp.size(),
			 aTmp,
			 10);

		//HTML header
		std::string sHeader;
		sHeader="HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n";
		sHeader+="Content-Encoding: text/html\r\n";
		sHeader+="Content-Length: ";
		sHeader+=aTmp;
		sHeader+="\r\n\r\n";
		sHeader+=sTmp;
		
		//Our size
		rNewDataSize=sHeader.size();
		
		//Allocate new buffer
		*pNewBuffer=new char[rNewDataSize];
		memcpy(*pNewBuffer,
			   sHeader.c_str(),
			   sHeader.size());
		
		//Send to client
		rNewBufferToReceive=true;

		//Terminate the session
		rTerminateSession=true;

		//Set to change
		return true;
	}
	else
		return false;

In this code we get the session information, and then redirect every session that will connect to port 80 to Komodia’s website using HTML redirection. The code compares the destination IP of the current session to the IP of Komodia’s website and if the addresses matches there will be no redirection, this to avoid redirection loops.

Inside the sample DLL this functions contains code that modifies the header, in this case, it modifies the user-agent field, adds a new fields called “X-Redirector” and deletes the field “refer”:

        //Do we have a context?
	ContextData* pContext;
	pContext=(ContextData*)dwContext;

	//This code adds HTTP manipulations
	//This code also make sure to set the filter once per request, because a request can come in many times
	//Because it is fragmented
	//Is this HTTP?
	if (pContext &&
		pContext->bNewRequest && //This checks that we are inside a new request, and not inside an existing request
		IsHTTP(pData,
			   dwDataSize))
	{
		//The request can fragment and we don't want to be here per fragment, but per request
		pContext->bNewRequest=false;

		//Here we build the filter
		//These fileds are just for the sake for demonstration
		//Please change them to reflect your own fields
		CHTTPHelper aHelper;
		
		//Each method can be called multiple times
		//Fields to change
		//Uncomment the next code, keep in mind that it may cause problems with
		//Sites that are based on your browser
		aHelper.ChangeHTTPHeader("user-agent","Blank agent");

		//Fields to add
		aHelper.InsertSpecialField("X-Redirector","Just a value");

		//Fields to remove
		aHelper.FilterHTTPHeader("Refer");

		//Build the struct
		//Don't call this function unless you want to have a filter
		//Or have a filter override
		rManipulations=aHelper.GetFinalStruct();

		//Done
		return false;
	}

	//Done
	return false; 

This is the sample code taken from the DLL, the class used here CHTTPHelper is documented later in this chapter.

DataBeforeReceive

bool _stdcall DataBeforeReceive(const char* pData,
				DWORD dwDataSize,
				char** pNewBuffer,
				DWORD& rNewDataSize,
				bool& rTerminateSession,
				bool& rThawReceiveFreeze,
				bool& rEnableSSL,
				bool bServerClosed,
				ContextDWORD dwContext);

This function is called before sending the data back to the application that initiated the session. Any answer from a web server will be caught by this function, keep in mind that answers comes in fragmented, if you need HTTP answers to come in complete and/or decoded you might consider using the HTTP Parser add-on that is documented later in this chapter.

The parameters are:

pData – The data to process.

dwDataSize – Size of data to process.

pNewBuffer – If the data is modified, this will hold the modified data. It must be allocated by the programmer, since the proxy will delete it.

rNewDataSize – If the data is modified, this will hold the modified data size.

rTerminateSession – Set to true to terminate the session when this function exits (if you don't want the data to be received, you need to clear it).

rThawReceiveFreeze – Set to true to allow Redirector to process data from the client that was held due to the bFreezeReceive flag in the function NewConnection.

rEnableSSL – Set to true to switch to SSL session. This can be set only if the flag rSSLSwitch was set during the NewConnection method call.

bServerClosed – When this flag is set, it means that the remote server closed the connection and at this stage you have last chance to send data to the client side before the Redirector closes the connection.

dwContext – DLL-specific context that was created at the NewConnection function.

Return value: True – Data was modified. False – Data was not modified.

The flag rTerminateSession is not affected by the return value. You can terminate the session without changing the data that you received inside pData.

ConnectionClosed

void _stdcall ConnectionClosed(bool bError,
			       DWORD dwContext);

This function is called when the session has ended.

bError – Indicates whether the session ended because of an error (failed connection attempt), or because the remote side ended the connection.

This is the place to release the dwContext if it was allocated. Sample code:

delete (DLLData*)dwContext;

DLLQueryData

char* _stdcall DLLQueryData(const char* pData)

This function is optional (it doesn't have to be exported), it allows you to send/receive data to/from the DLL via the COM interface.

pData - Data sent from COM

Return value: Data to return back to the COM call.

The COM call to initiate this method is: CDataController::FeedDLL

HRESULT FeedDLL(BSTR bData,[out,retval]BSTR* pDataBack)

bData - Data to send to the DLL. pDataBack - Data received from the DLL.

DLLSetFunctions

void _stdcall DLLSetFunctions(const FunctionsStruct* pFuncs)

This function is optional (it doesn't have to be exported), if it's defined it will be called before the DLL initialize method (PCInitialize).

  • Important - Make sure the functions is defined in the DLL .DEF file.

pFuncs - Contains the functions to get/set flags and save data.

Structure definition:

//Return value of get flag
#define GET_FLAG_BUFFER_OVERFLOW -1
#define GET_FLAG_NO_RESULT 0
#define GET_FLAG_OK_RESULT 1

//Flag manipulation functions
typedef int (_stdcall *PGetFlagInformation)(const wchar_t* pIndex,
					    wchar_t* pValue,
					    DWORD dwValueStringSize);

typedef void (_stdcall *PSetFlagInformation)(const wchar_t* pIndex,
					     const wchar_t* pValue);

typedef void (_stdcall *PSaveInformation)();

//Structure with Redirector specific functions
typedef struct _FunctionsStruct
{
	DWORD			dwFunctionCount;
	PGetFlagInformation	pGetFlagInformation;
	PSetFlagInformation     pSetFlagInformation;
	PSaveInformation	pSaveInformation;
} FunctionsStruct;

Get flag

        //Here can use the flags
	//For example lets get the Unique GUID
	unsigned short usTmp[256]=L"";
	if (pFuncs->pGetFlagInformation &&
		(*pFuncs->pGetFlagInformation)(L"userguid",usTmp,256)==GET_FLAG_OK_RESULT)
	{
		//The GUID is inside usTmp
	}

Set flag

        //Now we can set some custom var
	if (pFuncs->pSetFlagInformation)
		(*pFuncs->pSetFlagInformation)(L"myvar",L"myvalue");

Save data

        //We can also save the data
	(*pFuncs->pSaveInformation)();

TCP Accept methods

The Accept methods are used only with the Advanced Redirector module with LSP only, they receive incoming TCP connections.

NewAccept

bool _stdcall NewAccept(ContextDWORD& rContext,
		        const ProcessInformation& pPInformation,
			unsigned long ulRemoteIP,
			unsigned short usRemotePort,
			unsigned long ulLocalIP,
			unsigned short usLocalPort);

This function is called whenever a new incoming connection was made to PCProxy.

The parameters are:

rContext – A DWORD to be used across all functions that are called for this connection.

pPInformation – Contains information about the calling process.

ulRemoteIP - IP of the remote connection.

usRemotePort - Port of the remote connection.

ulLocalIP - IP of the local server this connection was connected to.

usLocalPort - Port of the local server this connection was connected to.

Return value: True – Allow this session False – Reject this session

DataBeforeSendA

bool _stdcall DataBeforeSendA(const char* pData,
			      DWORD dwDataSize,
			      char** pNewBuffer, 
			      DWORD& rNewDataSize,
			      bool& rTerminateSession,
			      bool& rNewBufferToReceive,
                              ContextDWORD dwContext);

This function is called before sending the data to the final destination (from server to client).

The parameters are:

pData – The data to process.

dwDataSize – The size of data to process.

pNewBuffer – If the data is modified, this will hold the modified data. Must be allocated by the programmer, since the proxy will delete it.

rNewDataSize – If the data is modified, this will hold the modified data size.

rTerminateSession – Set to true to terminate the session when this function exits (if you don't want the data to be sent, you need to clear it).

rNewBufferToReceive – Takes the contents of the new data and sends it to the application that initiated the session (used mostly for redirects).

dwContext – The DLL-specific context that was created at the NewConnection function.

Return value: True – Data was modified. False – Data wasn’t modified.

The flag rTerminateSession is not affected by the return value. You can terminate the session without changing the data that you received inside pData.

DataBeforeReceiveA

bool _stdcall DataBeforeReceiveA(const char* pData,
				 DWORD dwDataSize,
				 char** pNewBuffer,
				 DWORD& rNewDataSize,
				 bool& rTerminateSession,
				 bool bServerClosed,
				 ContextDWORD dwContext);

This function is called before sending the data back from the server to remote process that initiated the session.

The parameters are:

pData – The data to process.

dwDataSize – Size of data to process.

pNewBuffer – If the data is modified, this will hold the modified data. It must be allocated by the programmer, since the proxy will delete it.

rNewDataSize – If the data is modified, this will hold the modified data size.

rTerminateSession – Set to true to terminate the session when this function exits (if you don't want the data to be received, you need to clear it).

bServerClosed – When this flag is set, it means that the local server closed the connection and at this stage you have last chance to send data to the client side before the Redirector closes the connection.

dwContext – DLL-specific context that was created at the NewConnection function.

Return value: True – Data was modified. False – Data was not modified.

The flag rTerminateSession is not affected by the return value. You can terminate the session without changing the data that you received inside pData.

Threading model

Calls to NewConnection come from different threads. Calls to DataBeforeReceive for a specific connection are always from the same thread. Calls to DataBeforeSend for a specific connection are always from the same thread, but not the same thread as that of DataBeforeReceive.

CHTTPHelper

This class is used to create HTTP filtering, e.g. Adding, modifying and removing HTTP header fields.

Inside the PCProxySample DLL sample there’s a usage of this class in the correct context. This section will go over the four main functions of the class.

Overview

When adding parameters to the functions don’t add the ‘:’:

Correct: “Connection” Incorrect: “Connection:”

And never terminate the strings with \r\n

Correct: “Some data” Incorrect: “Some data\r\n”

Headers are case insensitive, data is case sensitive.

FilterHTTPHeader

void FilterHTTPHeader(const std::string& rHeader);

Use this function to add name of headers to remove from the request, a sample header would be “If-Modified-Since” removing this field will not allow server to give you cached result.

ChangeHTTPHeader

void ChangeHTTPHeader(const std::string& rHeader,
		      const std::string& rNew);

Use this function to change data of a specific header, for example:

ChangeHTTPHeader(“user-agent”,
		 “Some agent”);

The filter will only modify existing headers, if the header isn’t in the request it will not be added.

InsertSpecialField

void InsertSpecialField(const std::string& rField,
			const std::string& rData);

Use this function to add a field to the request, the field will be added only if it doesn’t exist in the original request:

InsertSpecialField(“X-Redirector”,
		   “Some data”);

If you want to make sure a field always exists with a value you want but you’re no sure whether this field always appear, you can set the filter to delete the header field and then insert your field.

GetFinalStruct

OutgoingDataManipulations GetFinalStruct(bool bOverideProxy=false,
				         bool bOveridGlobal=false)const;

This is the function that converts all the data you entered into a struct the Redirector can use.

Parameters:

bOverideProxy - Set to true to disable HTTP proxy and HTTP hybrid proxy filtering (which adjusts the request from normal request to a proxy request)

bOveridGlobal – Set to true to disable global HTTP filtering (which you set via the console)

HTTP Parser add on module

Integration with existing DLL functions

When working with HTTP Parser, you may still want to handle non HTTP data therefore all functions that part of the regular DLL extension are still active when using parental control module, you may choose to use them or not, it’s up to you.

Architecture

After a connection is being made (after NewConnection was called) the Redirector assembles the outgoing request (e.g. GET / HTTP/1.1) and when it’s complete (incase of a POST request, when it contains all the POST data) the Redirector calls the function HTTPRequestBeforeSend so it can perform analysis on the outgoing request, keep in mind that during the assembly of the request the method DataBeforeSend is called per chunk, but if you choose to work with the HTTP Parser module you can just ignore it (ignoring=not performing any operation at that level)

At this stage you have number of options available to you:

  • Do nothing (dhrNothing) – Request will be aggregated at will be sent normally at the end, you should try to call this return value the less you can.
  • Don’t perform parental on this request/reply (dhrDontDoParental) – Incase you know that you have no need to further parse this specific request/reply, the answer received will not be parsed with the parental control add-on, if there will be a request made after this one in the same socket connection, the process will start again (the don’t perform parental control is good for one request only)
  • Block the connection (dhrBlock) – The request is not sent and the connection is closed.
  • Redirect the connection to a different URL (dhrRedirect) – You specify a URL you want the browser to be redirected to, e.g. if you specify http://www.komodia.com the session will be redirected to Komodia’s web site.
  • Modify (dhrModify) – You modify the header and maybe the body to contain custom data, with this return value it's your responsibility to make sure that the header data matches the body data (for example if body is larger, the content-size should match the new size)
  • Return HTML (dhrReturnHTML) – The Redirector will take your HTML, wrap it with HTTP header and will send it back to the browser, while discarding the original request, this is good for custom block pages.
  • Return HTML and header (dhrReturnHTMLAndHeader) – You specify the HTML and HTML header to return back to the browser, while discarding the original request, this is good for block pages.
  • Tell the SDK to choose what to do (dhrSkip) - The default behaviour is only to do dhrNothing for HTMLs that require processing.
  • Defer the decision (dhrDefer) - When receiving a partial reply (bServerCheck==true) you might want to make a decision later, but not wait until you receive the entire data, returning this value means that the next chunk of data received you will be called again with bServerCheck==true, you can defer the reply until you receive the full reply, at that point you must make a decision.
  • return dhrModifyBodySDKAdjustHeader - This will tell the SDK that you only modified the HTML data, and the SDK will adjust the header to match the new data.

Return values valid only for HTTPRequestBeforeSend:

  • Don't process post (dhrDontProcessPost) - Inside HTTP you may get only the header for a post, but the post is too long and you might not need it (for example email upload), returning this value means that the Redirector will not try and build the post output.
  • Don't process post and don't do parental (dhrDontDoParentalAndDontProcessPost) - Same as above, but will also perform the same result as dhrDontDoParental.
  • Only modify the header but don't process the post payload (dhrModifyDontProcessPostPayload) - This is used in case you only want to modify the post header, but don't care about the data itself, and don't want to wait for the entire data to be uploaded.
  • Only modify the header but don't process the post payload and not the reply as well (dhrDontDoParentalAndDontProcessPostModify) - This is used in case you only want to modify the post header, but don't care about the data itself, and don't want to wait for the entire data to be uploaded, and you don't care about the result reply.

Return values valid only for HTTPRequestBeforeReceive:

Parsing of the header is done via a helper class called CHTTPHeader.

CHTTPHeader

This class is used to help you work with HTTP headers, although the Redirector gives you complete requests/replies, you still want to do some inspection on them, this class helps you by parsing the requests/replies and give you methods to easily get and modify the data you want.

Feed / ForceFeed

This two functions are used to give the class the raw data, the difference between Feed and ForceFeed is that Feed is sequential, which means that you can give it sequential fragmented chunks of the header, unlike ForceFeed that clears previous content and start from the beginning, our case we can use either methods, return value is true for indication we have a complete header and false for not having a complete header, usage example:

CHTTPHeader aHeader;
if (aHeader.Feed(pHeader,
	         dwHeaderSize))
{
    //Do something
}

GetHeaderString / GetHeaderStringCase

After we got a parsed header we want to start inspecting the contents, this two functions get the data of the specific HTTP headers (the only difference is that GetHeaderStringCase preserves the case of the data), for example, lets look at this simple HTTP request:

GET /index.php HTTP/1.1
User-agent: Mozilla Firefox
Connection: Keep-alive
Host: www.google.com

Now lets get the value of connection (without preserving case), keep in mind that all requests are made in lower case (we are assuming we continue from the last example):

std::string sConnection(aHeader.GetHeaderString(“connection”));

IsRequest

bool IsRequest()const;

This function will return true if the parsed header was a HTTP request and false if it was a HTTP reply.

GetHost

std::string GetHost()const;

This function will give us the host of the request (relevant to requests only).

GetResponseCode

unsigned short GetResponseCode()const;

This function returns the HTTP response code (relevant to replies only)

GetURL

const std::string& GetURL()const;

This function returns the URL of the request, in our example it would be /index.php

GetFullAddress

const std::string& GetFullAddress()const;

This function returns the full address of the request, in our example it would be: http://www.google.com/index.php

SetHeader

void SetHeader(const std::string& rHeader,
	       const std::string& rData);

This function is used to modify the header, in case the header given exists it will be changed, if the header do not exist, it will be added with the specified value, for example changing our header connection type (rHeader is case insensitive):

aHeader.SetHeader(“connection”,”close”);

DeleteHeader

void DeleteHeader(const std::string& rHeader);

This function will delete a header from the request/reply (if it exists, rHeader is case insensitive)

GetHeaderData

DataVector GetHeaderData(DWORD dwStart=0)const;

Get the data of the header as a vector of chars, this data will include all the changes we have performed on the header.

HTTP Parser functions

There are two functions to implement when using the HTTP Parser.

The returned enum definition:

typedef enum _DLLHTTPResult
{
	dhrNothing=0,
	dhrBlock,
	dhrRedirect,
	dhrModify,
	dhrReturnHTML,
	dhrReturnHTMLAndHeader,
	dhrDontDoParental,
	dhrDontProcessPost,
	dhrDontDoParentalAndDontProcessPost,
	dhrSkip,
	dhrModifyBodySDKAdjustHeader,
	dhrDefer,
        dhrModifyDontProcessPostPayload,
	dhrDontDoParentalAndDontProcessPostModify,
        dhrBuildButDontAggregate,
	dhrDefer,
	dhrWaitForCategory,
	dhrDontDoParentalAndModify,
	dhrStartInject,
	dhrPayloadInjected,
	dhrPayloadInjectedBuildButDontAggregate
} DLLHTTPResult;

HTTPRequestBeforeSend

DLLHTTPResult _stdcall HTTPRequestBeforeSend(const char* pHeader,
					     DWORD dwHeaderSize,
					     char** ppNewHeader,
					     DWORD& rNewHeaderSize,
					     bool bPartialPost,
					     DWORD dwContext);

This function is called when an outgoing request is ready to be inspected.

The parameters are:

pHeader – The pointer containing the header data, in case the request is post and bPartialPost==0 then the post data will come in this parameter right after the header, the post data will start right after \r\n\r\n that terminates the header.

dwHeaderSize – Size of the header in bytes.

ppNewHeader – If you need to return data to the Redirector, in case of modify or redirect, you need to allocate the data and give it to this pointer.

rNewHeaderSize – Size of the new data or redirect address.

bPartialPost - If this flag is true, it means that the current info is just the header of a post, but there's no post data, this allows you the ability to tell the SDK not to aggregate any post data for this connection when returning dhrDontProcessPost.

dwContext – DLL-specific context that was created at the NewConnection function.

Return value:

Action to take with this request, you can look what each value means under the Komodia's Redirector DLL framework guide#Architecture section.

Sample code taken from the DLL:

//Take the context
ContextData* pData;
pData=(ContextData*)dwContext;

//At this stage we have a complete header
//It's easiest to work with the supplied class
//For header parsing
//Give it to the header
CHTTPHeader aHeader;
if (aHeader.Feed(pHeader,
		     dwHeaderSize))
{
	//We can get some usefull information at this stage
	//Host
	std::string sHost(aHeader.GetHost());

	//URI
	std::string sURI(aHeader.GetURL());

	//The full address
	std::string sFullAddress(aHeader.GetFullAddress());

//Save the host in the session data
	if (pData &&
	    pData->sHost.empty())
pData->sHost=sHost;

This code snipest parses the header and extracts basic data we can use, now lets perform a redirect:

//Lets say we want to build a redirect based on the request
//We need to make sure that we are not redirecting over and over
//The site we are redirecting to
//This code performs it
if (!strstr(sHost.c_str(),
		"komodia.com") &&
    !strstr(sHost.c_str(),
		"google"))
{
//Redirect to address, always in the form of http://website
	std::string sRedirectTo;
	sRedirectTo="http://www.komodia.com";

	//Save the data
	*ppNewHeader=new char[sRedirectTo.size()+1];
	memcpy(*ppNewHeader,
		 sRedirectTo.c_str(),
		 sRedirectTo.size()+1);

	//Set size
	rNewHeaderSize=sRedirectTo.size()+1;

	//Let Redirector know that this is a redirect
	return dhrRedirect;
}

HTTPRequestBeforeReceive

DLLHTTPResult _stdcall HTTPRequestBeforeReceive(const char* pHeader,
						DWORD dwHeaderSize,
						char** ppNewHeader,
						DWORD& rNewHeaderSize,
						const char* pData,
						DWORD dwDataSize,
						char** ppNewData,
						DWORD& rNewDataSize,
						bool bHeaderCheck,
						DWORD dwContext);

This function is called when an incoming request is ready to be inspected so you can decide if you want to do something with it right now (instead of waiting for the download to complete), it is then called again when it contain the full HTTP data (data is in the buffers, the browser didn’t receive it yet), the data is parsed, even if the original was Gzip encoding of chunked transfer encoding, you still receive plain text and the returned headers are modified automatically to accommodate for this change.

The parameters are:

pHeader – The pointer containing the header data

dwHeaderSize – Size of the header in bytes.

ppNewHeader – If you need to return data to the Redirector, in case of modify or redirect, you need to allocate the data and give it to this pointer.

rNewHeaderSize – Size of the new data or redirect address.

pData – The pointer containing the actual data (HTML usually)

dwDataSize – Size of the data in bytes.

ppNewData – If you need to return new data to the Redirector, in case of modify or redirect, you need to allocate the data and give it to this pointer.

rNewDataSize - Size of the new data.

bHeaderCheck – This flag is true when this call is with header only and data is still downloading, keep in mind that if the Redirector managed to get the complete reply (header+data) in one call, this method will be called only once (with this flag false). When the flag is false it means that this is the complete reply.

dwContext – DLL-specific context that was created at the NewConnection function.

Return value:

Action to take with this reply, you can look what each value means under the Komodia's Redirector DLL framework guide#Architecture section.

It's important to note that when the bHeaderCheck==true the header supplied will be the original header and there might be data inside the data pointer, which is the partial data received so far, but bHeaderCheck==false, the header might be a modified header because the SDK removes some encoding header fields.

Sample code taken from the DLL:

//Is this a header check (gives us change to determine what we are receiving, in case it's a big file
//we might not want to process it
//Header check means that we have a complete header, but not all the data
if (bHeaderCheck)
{
	//Try to parse the header
	CHTTPHeader aHeader;
	if (aHeader.Feed(pHeader,
		     dwHeaderSize))
	{
		//We can get some usefull information at this stage
		//Response code
		DWORD dwResponseCode(aHeader.GetResponseCode());

		//Try to get the content type
		std::string sContentType(aHeader.GetHeaderString("content-type"));

		//Now there are some content we don't want to perform parental control on
		//Of course you can change these settings
		//These settings are just for sample sake
		//For these kind of files (.swf, .zip, .exe) filtering should be performed at the level of HTTPRequestBeforeSend
		if (strstr(sContentType.c_str(),
			     "image/") ||
		    strstr(sContentType.c_str(),
			     "video/") ||
		    strstr(sContentType.c_str(),
			     "audio/") ||
		    strstr(sContentType.c_str(),
			     "application/"))
			//Don't do parental
			return dhrDontDoParental;
		}
	}

This code snipest checks the reply while we still haven’t got the data, and checks the content-type, if it’s media files usually we don’t need to hold them in buffer (because they are large files mostly) and in case of parental control if they should be rejected it should be done at the stage of HTTPRequestBeforeSend.

Now lets perform a redirect in this stage (after we got all the data):

else
{
        //Take the host from the session data
	std::string sHost;

	//Do we have session data?
	if (pContextData)
		sHost=pContextData->sHost;

	//Uncomment the next command to disable the redirect sample
	//return dhrNothing;

	//At this stage we have a full request, for those we didn't disable
	//Lets say we want to perform a filtering here
	//We need to make sure that we are not redirecting over and over
	//The site we are redirecting to
	//This code performs it
	if (!strstr(sHost.c_str(),
			"komodia.com") &&
	    !strstr(sHost.c_str(),
			"google"))
	{
			//Redirect to address, always in the form of http://website
		std::string sRedirectTo;
		sRedirectTo="http://www.komodia.com";

		//Save the data
		*ppNewHeader=new char[sRedirectTo.size()+1];
		memcpy(*ppNewHeader,
		 	 sRedirectTo.c_str(),
			 sRedirectTo.size()+1);

		//Set size
		rNewHeaderSize=sRedirectTo.size()+1;

		//Let Redirector know that this is a redirect
		return dhrRedirect;
	}
}

HTML Element injection

There are two ways to inject HTML elements, first one is getting the entire page then modifying it, the second one is mostly used to inject JavaScript above or below the <head> or <body> element, and there's a special mode to allow injection while streaming the data, this allows for faster load times.

This method of injection can be done only at the receive stage if the page has not arrived completely (if it did, just modify it)

Sequence:

  1. Call to HTTPRequestBeforeReceive with bHeaderCheck==true and no data, if you want or think you want to inject to this page then return dhrStartInject, this will start the inject process, but at the end you will not have to inject.
  2. There will be calls to HTTPRequestBeforeReceive with bHeaderCheck==true and partial data inside pData variable, you can inspect the data and see if you can inject, if there's not enough data to make a decision you can return dhrStartInject to be called again with more data. You can make any other calls like dhrDontDoParental to tell the SDK you don't care about this reply anymore. Also it is possible that while waiting, you got the full page, and HTTPRequestBeforeReceive will be called with bHeaderCheck==false, so here you modify it regularly and not with the inject methods.
  3. When you found the data you want to inject to, you put the modified data (including the injection) inside ppNewData, and return dhrPayloadInjected or dhrPayloadInjectedBuildButDontAggregate.
  4. If you called dhrPayloadInjected the SDK will stop calling you anymore for this reply and will send the new reply with the injection to the browser
  5. If you called dhrPayloadInjectedBuildButDontAggregate the SDK will perform the injection and call you one last time with the full data, this data will be read only, you will not be able to do any modification on it (also the data will not include the injection).

HTTP 101 response code

When a parser sees a HTTP 101 response code it will send the reply header in the HTTP reply callback, it might supply data or not, at that stage you can block the connection or modify the header, incase you didn't the data will no longer be processed by the HTTP Parser anymore and all the data can be processed at the raw data callbacks.

Common pitfalls

Multi threading

The callbacks calls are multi threaded, make sure that non thread safe code is protected with Mutex or Critical section.

Dependencies

The DLL failed to load because it is dependent on 3rd party DLLs, for example CRT runtime DLL that is not installed by default on all machines.

Not understanding what bHeaderCheck does

bHeaderCheck is the core of the HTTP reply processing, not using it will often lead to problems of not seeing HTTP responses.

Modifying HTTP body size

When modifying the HTTP body size, it must be reflected in the header, this can be done by either using a flag that tells the SDK to adjust the header automatically (dhrModifyBodySDKAdjustHeader), or modify the header in the DLL and use a flag that indicates that the header has been modified (dhrModify).

Crashes/Memory corruption

Crashes and memory corruption may affect the SDK and cause traffic to get blocked.

Spending too much time inside callbacks

The DLL should not spend more then five seconds inside a callback, more then that can cause unexpected behavior (the true safe limit is 20 seconds, after 5 seconds inside a callback warnings will be written to the log)