TCP Proxy example

Share your advanced PureBasic knowledge/code with the community.
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

TCP Proxy example

Post by Lunasole »

That one is simplified variant of code I've used time ago.
It's cleared from all stuff like custom protocol/encryption, and here is a simple proxy for 1 client and 1 connection with hardcoded remote server.
It can however be easily modified for multiple client connections -- this depends on number of CLIENT instances and their handling in loop (or multiple remote servers also).

The example is configured to be a telnet proxy (for simplicity), you can test it this way:
1. Running following code from debugger or compiled exe and allowing it go through firewall
2. Executing windows telnet app and making connection to localhost:23 or 127.0.0.1:23

Then telnet connection will go through proxy and be redirected to some telnet server I picked from google for tests ^^

Code: Select all

EnableExplicit

; Raw TCP proxy example
; Inspired by http://www.martinbroadhurst.com/source/tcpproxy.c.html
;   2016         (c) Lunasole

;{ Network stuff }
	
	; this has to be 1500 maximum, if transfering over internet. else packets becoming fragmented
	; but well, maybe fragmentation is not a problem for raw proxy. need to try
	#TCP_SIZE = 1024 ; 4096          ; used to transfer TCP data
	
	; a structure to represent IP adress as single long and array of 4 bytes
	Structure IP4Adress
		StructureUnion
			IP.l
			BYTE.a [4]
		EndStructureUnion
	EndStructure
	
	; A structure used to represent both client and server instances
	Structure NETPOINT
		IP.IP4Adress   ; the IPV4 adress info [client mode]
		PORT.i		   ; local port [if server's client] or server port [if server]
		HANDLE.i	   ; a handle representing connection of this object
		LEvent.i	   ; last network event which occured on this object
		State.l		   ; if 1, then connection was established and this structure should be valid
		TimeStamp.i	   ; used to track last activity time for this connection [last time data received]
		RetryStamp.i   ; used on connection establish/reconnect
	EndStructure
	
	; ----------------------------------------------------------
	; some common network routines
	; ---------------------------------------------------------- 
	; receives hostname/IP adress string and resolves it to IP4 adress [long], then converts to string
	Procedure$ GetIPByHost(Host$="")
		Protected Result.l = 0
		If Host$=""
			Host$=Hostname()
		EndIf
		Protected SelfLoop = OpenNetworkConnection(Host$,0 , #PB_Network_UDP)
		If SelfLoop
			Result=GetClientIP(SelfLoop)
			CloseNetworkConnection(SelfLoop)
		EndIf
		ProcedureReturn IPString (Result)
	EndProcedure
	
	
	; connect to specified server using TCP
	; RETURN: on success 1, *lpData will contain the connection data
	Procedure net_start (*lpData.NETPOINT, Server.s, ServerPort)
		Protected HANDLE.i =  OpenNetworkConnection(Server, ServerPort , #PB_Network_TCP, 30000)
		If HANDLE
			*lpData\HANDLE = HANDLE
			*lpData\State = 1
			*lpData\TimeStamp = ElapsedMilliseconds()
			*lpData\RetryStamp = 0
			ProcedureReturn 1
		EndIf
	EndProcedure
	
	; zeroes specified connection data [should be called by server after #PB_NetworkEvent_Disconnect] 
	Procedure net_cls (*lpData.NETPOINT)
		ClearStructure(*lpData, NETPOINT)
	EndProcedure
	
	; close specified connection and zeroes related structure. includes net_cls() call
	Procedure net_stop (*lpData.NETPOINT)
		If *lpData\HANDLE And *lpData\State
			CloseNetworkConnection(*lpData\HANDLE)
		EndIf
		net_cls (*lpData)
	EndProcedure
	
	; used by server to receive some client data
	Procedure srv_getClient (HANDLE, *lpData.NETPOINT)
		*lpData\HANDLE = HANDLE
		*lpData\IP\IP = GetClientIP (HANDLE)
		*lpData\PORT = GetClientPort(HANDLE) 
		*lpData\State = 1
		*lpData\TimeStamp = ElapsedMilliseconds()
		*lpData\RetryStamp = 0
	EndProcedure
	
	; used by server: starts new server using specified port
	; RETURN: 1 on success; *lpData contains server data
	Procedure srv_start (*lpData.NETPOINT, Port)
		Protected HANDLE.i =  CreateNetworkServer(#PB_Any, Port, #PB_Network_TCP)
		If HANDLE
			*lpData\HANDLE = HANDLE
			*lpData\State = 1
			*lpData\TimeStamp = ElapsedMilliseconds()
			*lpData\RetryStamp = 0
			ProcedureReturn 1
		EndIf
	EndProcedure
	
	; used by server: stop specified server and clear related data structure. includes net_cls() call
	Procedure srv_stop (*lpData.NETPOINT)
		If *lpData\HANDLE
			CloseNetworkServer(*lpData\HANDLE)
			ClearStructure(*lpData, NETPOINT)
		EndIf
	EndProcedure
	
	
	; ----------------------------------------------------------
	; TCP proxy
	; ----------------------------------------------------------
	; Transfer all data from network buffer "from_id", to "to_id"
	; Params are connection handles
	; RETURN:	size of transfered data on success
	;         	-1 If nothing sent
	;	        -2 if receiver buffer is full [?]
	;   	    -3 if from_id or to_id is null or their state = 0
	Procedure DataTransfer(*source.NETPOINT, *target.NETPOINT)
		Protected Dim buf.a (#TCP_SIZE)
		Protected state, transfer, bytes_read, bytes_written
		Protected from_id = *source\HANDLE
		Protected to_id = *target\HANDLE
		
		If *source\HANDLE And *target\HANDLE And *source\STATE And *target\STATE
			bytes_read = ReceiveNetworkData (from_id, @buf(0), #TCP_SIZE)
			While Not bytes_read <= 0
				bytes_written = SendNetworkData (to_id, @buf(0), bytes_read)
				
				If bytes_written = -1             ; nothing can be sent
					state = -1 : Break
				ElseIf bytes_written < bytes_read ; the receiver buffer is probably full
					transfer + bytes_written
					state = -2 : Break
				ElseIf bytes_written = #TCP_SIZE  ; it's all ok, continue
					transfer + bytes_read
					bytes_read = ReceiveNetworkData (from_id, @buf(0), #TCP_SIZE); continue 
				Else
					transfer + bytes_read ; no more data to transfer
					Break
				EndIf
			Wend
		Else
			state = -3 ; from_id or to_id is null
		EndIf
		
		If state
			ProcedureReturn state
		Else
			ProcedureReturn transfer
		EndIf
	EndProcedure
	
;}


Global CLIENT.NETPOINT      ; connection to CLIENT
Global THIS_SRV.NETPOINT	; this server instance
Global REMOTE.NETPOINT		; connection to remote host

#THIS_PORT    = 23                  ; this server port (client should connect to it through 127.0.0.1:port)

#REMOTE_HOST   = "news.thundernews.com"       ; remote host address (data from client will be transfered to it)
#REMOTE_PORT    = 23						  ; remote host port


; Misc
#NET_LIFETIME        = 3 * 60 * 1000  ; connection timeout, if there is no activity on connection within it, connection will be closed
#NET_RETRYTIME      = 1 * 60 * 1000	  ; a timeout between remote host connection attempts
#NET_FAST         = 1				  ; the delay in loop to not load CPU for 100%
#ENABLE_GUI = 1						  ; if 1, use console window for output, else use debug
									  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

If #ENABLE_GUI
	OpenConsole("Proxy example by Lunasole")
EndIf

Procedure log_add (text$, color = 0)
	If #ENABLE_GUI
		If color
			ConsoleColor(color, 0)
		Else
			ConsoleColor(7, 0)
		EndIf
		PrintN(text$)
	Else
		Debug text$
	EndIf
EndProcedure

If InitNetwork()
	log_add("START SERVER using port " + Str(#THIS_PORT) + ". Result: " + Str(srv_start(@THIS_SRV, #THIS_PORT)), 8)
	
	Global Timer
	Repeat
		Timer = ElapsedMilliseconds()
		
		; this server action
		THIS_SRV\LEvent = NetworkServerEvent (THIS_SRV\HANDLE)
		Select THIS_SRV\LEvent
			Case #PB_NetworkEvent_Connect ; client connected
				If Not CLIENT\State 
					srv_getClient (EventClient(), CLIENT) ; get some info about client
					log_add ("CLIENT CONNECTED: "  + Str (EventClient()) + " [" + IPString (CLIENT\IP\IP) + "]", 14)
				Else
					log_add ("CLIENT ALREADY CONNECTED. DROPPED ANOTHER ONE: " + Str(EventClient()) + " [" + IPString (GetClientIP(EventClient())) + "]", 14)
					CloseNetworkConnection(EventClient())
				EndIf
				
			Case #PB_NetworkEvent_Data  ; a data from client received
				If EventClient() = CLIENT\HANDLE ; if it is our client
					If REMOTE\State
						log_add ("CLIENT >> REMOTE = " + Str(DataTransfer (CLIENT, REMOTE)), 2)
					EndIf
					CLIENT\TimeStamp = Timer ; update activity time
					REMOTE\TimeStamp = Timer
				EndIf
				
			Case #PB_NetworkEvent_Disconnect ; client disconnected
				If EventClient() = CLIENT\HANDLE ; if it is our client
					log_add ( "CLIENT DISCONNECTED: "  + Str (EventClient()) + " [" + IPString (CLIENT\IP\IP) + "]", 14)
					net_cls (CLIENT)
					net_stop(REMOTE) ; close connection with proxy also
				Else
					log_add ( "UNKNOWN DISCONNECTED: " + Str (EventClient()) + " [" + IPString (GetClientIP(EventClient())) + "]", 14)
				EndIf
		EndSelect
		
		; attempt to connect/reconnect to remote proxy if CLIENT connected but proxy not
		If CLIENT\State And Not REMOTE\State
			If REMOTE\RetryStamp + #NET_RETRYTIME < Timer ; reconnect timeout
				REMOTE\RetryStamp = Timer
				If net_start(@REMOTE, GetIPByHost(#REMOTE_HOST), #REMOTE_PORT)
					log_add ("CONNECTED TO REMOTE HOST [" + #REMOTE_HOST + "]", 11)
					REMOTE\TimeStamp = Timer
				Else
					log_add ("! CANNOT CONNECT TO REMOTE HOST [" + #REMOTE_HOST + "]", 12)
				EndIf
			EndIf
		EndIf
		
		; remote proxy action
		If REMOTE\State
			REMOTE\LEvent   = NetworkClientEvent(REMOTE\HANDLE) 
			If REMOTE\LEvent = #PB_NetworkEvent_Data ; incoming data from C&C, it uses MA protocol for everything
				If CLIENT\State 
					; receive data from remote and send to client
					log_add ("CLIENT << REMOTE = " + Str(DataTransfer(REMOTE, CLIENT)), 3)
					CLIENT\TimeStamp = Timer ; update activity time
					REMOTE\TimeStamp = Timer
				EndIf
			EndIf
		EndIf
		
		
		; general control
		; check if all connections alive and drop if not
		If REMOTE\State
			If REMOTE\TimeStamp + #NET_LIFETIME < Timer
				log_add ("Connection timed out: REMOTE HOST", 14)
				net_stop(REMOTE)
			EndIf
		EndIf
		If CLIENT\State
			If CLIENT\TimeStamp + #NET_LIFETIME < Timer
				log_add ("Connection timed out: CLIENT", 14)
				net_stop(CLIENT)
			EndIf
		EndIf
		
		;       Delay(#NET_FAST)
	ForEver
EndIf
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: TCP Proxy example

Post by Kwai chang caine »

I have test it and apparently that works :wink:
I say "apparently" because i have already a PROXY in my enterprise and your code not pass through it :|

So i have a question, i'm a newbie in network :oops:
I search a code for read the request sending by a navigator (IE, FF, opera, Chrome, etc..)
They are sniffer for do that, but sometime too much informations appears :shock:
Me i search just to have the request
Someone say to me perhaps, it's possible to read this request if i use a PROXY in the same machine.
Do you know if it's possible with your code ??

So even if not, thanks a lot for sharing 8)
ImageThe happiness is a road...
Not a destination
Marc56us
Addict
Addict
Posts: 1600
Joined: Sat Feb 08, 2014 3:26 pm

Re: TCP Proxy example

Post by Marc56us »

Many corporate proxy requires one connection and authentication per day and per program.
PB component uses IE component so try to connect in IE one time before try using PB connection.

:wink:
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: TCP Proxy example

Post by Lunasole »

Kwai chang caine wrote:I have test it and apparently that works :wink:
It works, but is far enough from use it in something commercial ^^
There are optimizations and code cleanup/improvements required, but I don't need this stuff for now so don't know when will make it really nice and completed.
Kwai chang caine wrote: So i have a question, i'm a newbie in network :oops:
I search a code for read the request sending by a navigator (IE, FF, opera, Chrome, etc..)
They are sniffer for do that, but sometime too much informations appears :shock:
Me i search just to have the request
Someone say to me perhaps, it's possible to read this request if i use a PROXY in the same machine.
Do you know if it's possible with your code ??

So even if not, thanks a lot for sharing 8)
It is possible to do basing on my example (can even modify or block requests), but this requires to write whole parser of HTTP protocol (instead of just passing data with DataTransfer()).

Also modern browsers are using lot of connections to send requests and receive data, so this example has to be rewritten to work with multiple connections as mentioned in first post.


So it should be simplier to use sniffer as for me ^^ I can suggest you HTTPNetworkSniffer by Nirsoft (free), it doesn't give too much outputs unlike Wireshark. Maybe this can help.

PS. Also anyway capturing will not work with HTTPS (now many sites using it by default)
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: TCP Proxy example

Post by Kwai chang caine »

Thanks a lot at you two for your answers 8)
ImageThe happiness is a road...
Not a destination
Post Reply