restemplate/indy/Protocols/IdTunnelMaster.pas

791 lines
21 KiB
Plaintext

{
$Project$
$Workfile$
$Revision$
$DateUTC$
$Id$
This file is part of the Indy (Internet Direct) project, and is offered
under the dual-licensing agreement described on the Indy website.
(http://www.indyproject.org/)
Copyright:
(c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
$Log$
}
{
Rev 1.1 12/7/2002 06:43:42 PM JPMugaas
These should now compile except for Socks server. IPVersion has to be a
property someplace for that.
Rev 1.0 11/13/2002 08:03:54 AM JPMugaas
}
unit IdTunnelMaster;
interface
{$i IdCompilerDefines.inc}
uses
Classes, SyncObjs,
IdTCPServer, IdTCPClient, IdTunnelCommon;
type
TIdTunnelMaster = class;
///////////////////////////////////////////////////////////////////////////////
// Master Tunnel classes
//
// Thread to communicate with the service
MClientThread = class(TThread)
public
MasterParent: TIdTunnelMaster;
UserId: Integer;
MasterThread: TIdPeerThread;
OutboundClient: TIdTCPClient;
DisconnectedOnRequest: Boolean;
Locker: TCriticalSection;
SelfDisconnected: Boolean;
procedure Execute; override;
constructor Create(AMaster: TIdTunnelMaster);
destructor Destroy; override;
end;
// Slave thread - communicates with the Master, tunnel
TSlaveData = class(TObject)
public
Receiver: TReceiver;
Sender: TSender;
Locker: TCriticalSection;
SelfDisconnected: Boolean;
UserData: TObject;
end;
TIdSendMsgEvent = procedure(Thread: TIdPeerThread; var CustomMsg: String) of object;
TIdSendTrnEvent = procedure(Thread: TIdPeerThread; var Header: TIdHeader; var CustomMsg: String) of object;
TIdSendTrnEventC = procedure(var Header: TIdHeader; var CustomMsg: String) of object;
TIdTunnelEventC = procedure(Receiver: TReceiver) of object;
TIdSendMsgEventC = procedure(var CustomMsg: String) of object;
// TTunnelEvent = procedure(Thread: TSlaveThread) of object;
TIdTunnelMaster = class(TIdTCPServer)
protected
fiMappedPort: Integer;
fsMappedHost: String;
Clients: TThreadList;
fOnConnect,
fOnDisconnect,
fOnTransformRead: TIdServerThreadEvent;
fOnTransformSend: TSendTrnEvent;
fOnInterpretMsg: TSendMsgEvent;
OnlyOneThread: TCriticalSection;
// LockSlavesNumber: TCriticalSection;
//LockServicesNumber: TCriticalSection;
StatisticsLocker: TCriticalSection;
fbActive: Boolean;
fbLockDestinationHost: Boolean;
fbLockDestinationPort: Boolean;
fLogger: TLogger;
// Statistics counters
flConnectedSlaves, // Number of connected slave tunnels
flConnectedServices, // Number of connected service threads
fNumberOfConnectionsValue,
fNumberOfPacketsValue,
fCompressionRatioValue,
fCompressedBytes,
fBytesRead,
fBytesWrite: Integer;
procedure ClientOperation(Operation: Integer; UserId: Integer; s: String);
procedure SendMsg(MasterThread: TIdPeerThread; var Header: TIdHeader; s: String);
procedure DisconectAllUsers;
procedure DisconnectAllSubThreads(TunnelThread: TIdPeerThread);
function GetNumSlaves: Integer;
function GetNumServices: Integer;
function GetClientThread(UserID: Integer): MClientThread;
procedure SetActive(pbValue: Boolean); override;
procedure DoConnect(Thread: TIdPeerThread); override;
procedure DoDisconnect(Thread: TIdPeerThread); override;
function DoExecute(Thread: TIdPeerThread): boolean; override;
procedure DoTransformRead(Thread: TIdPeerThread); virtual;
procedure DoTransformSend(Thread: TIdPeerThread; var Header: TIdHeader; var CustomMsg: String);
virtual;
procedure DoInterpretMsg(Thread: TIdPeerThread; var CustomMsg: String); virtual;
procedure LogEvent(Msg: String);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetStatistics(Module: Integer; Value: Integer);
procedure GetStatistics(Module: Integer; var Value: Integer);
property Active: Boolean read FbActive write SetActive Default True;
property Logger: TLogger read fLogger write fLogger;
property NumSlaves: Integer read GetNumSlaves;
property NumServices: Integer read GetNumServices;
published
property MappedHost: string read fsMappedHost write fsMappedHost;
property MappedPort: Integer read fiMappedPort write fiMappedPort;
property LockDestinationHost: Boolean read fbLockDestinationHost write fbLockDestinationHost
default False;
property LockDestinationPort: Boolean read fbLockDestinationPort write fbLockDestinationPort
default False;
property OnConnect: TIdServerThreadEvent read FOnConnect write FOnConnect;
property OnDisconnect: TIdServerThreadEvent read FOnDisconnect write FOnDisconnect;
property OnTransformRead: TIdServerThreadEvent read fOnTransformRead write fOnTransformRead;
property OnTransformSend: TSendTrnEvent read fOnTransformSend write fOnTransformSend;
property OnInterpretMsg: TSendMsgEvent read fOnInterpretMsg write fOnInterpretMsg;
end;
//
// END Master Tunnel classes
///////////////////////////////////////////////////////////////////////////////
implementation
uses IdCoreGlobal, IdException,
IdGlobal, IdStack, IdResourceStrings, SysUtils;
///////////////////////////////////////////////////////////////////////////////
// Master Tunnel classes
//
constructor TIdTunnelMaster.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Clients := TThreadList.Create;
fbActive := False;
flConnectedSlaves := 0;
flConnectedServices := 0;
fNumberOfConnectionsValue := 0;
fNumberOfPacketsValue := 0;
fCompressionRatioValue := 0;
fCompressedBytes := 0;
fBytesRead := 0;
fBytesWrite := 0;
OnlyOneThread := TCriticalSection.Create;
StatisticsLocker := TCriticalSection.Create;
end;
destructor TIdTunnelMaster.Destroy;
begin
Logger := nil;
Active := False;
if (csDesigning in ComponentState) then begin
DisconectAllUsers; // disconnects service threads
end;
FreeAndNil(Clients);
FreeAndNil(OnlyOneThread);
FreeAndNil(StatisticsLocker);
inherited Destroy;
end;
procedure TIdTunnelMaster.SetActive(pbValue: Boolean);
begin
if fbActive = pbValue then
exit;
// if not ((csLoading in ComponentState) or (csDesigning in ComponentState)) then begin
if pbValue then begin
inherited SetActive(True);
end
else begin
inherited SetActive(False);
DisconectAllUsers; // also disconnectes service threads
end;
// end;
fbActive := pbValue;
end;
procedure TIdTunnelMaster.LogEvent(Msg: String);
begin
if Assigned(fLogger) then
fLogger.LogEvent(Msg);
end;
function TIdTunnelMaster.GetNumSlaves: Integer;
var
ClientsNo: Integer;
begin
GetStatistics(NumberOfSlavesType, ClientsNo);
Result := ClientsNo;
end;
function TIdTunnelMaster.GetNumServices: Integer;
var
ClientsNo: Integer;
begin
GetStatistics(NumberOfServicesType, ClientsNo);
Result := ClientsNo;
end;
procedure TIdTunnelMaster.GetStatistics(Module: Integer; var Value: Integer);
begin
StatisticsLocker.Enter;
try
case Module of
NumberOfSlavesType: begin
Value := flConnectedSlaves;
end;
NumberOfServicesType: begin
Value := flConnectedServices;
end;
NumberOfConnectionsType: begin
Value := fNumberOfConnectionsValue;
end;
NumberOfPacketsType: begin
Value := fNumberOfPacketsValue;
end;
CompressionRatioType: begin
if fCompressedBytes > 0 then begin
Value := Trunc((fBytesRead * 1.0) / (fCompressedBytes * 1.0) * 100.0)
end
else begin
Value := 0;
end;
end;
CompressedBytesType: begin
Value := fCompressedBytes;
end;
BytesReadType: begin
Value := fBytesRead;
end;
BytesWriteType: begin
Value := fBytesWrite;
end;
end;
finally
StatisticsLocker.Leave;
end;
end;
procedure TIdTunnelMaster.SetStatistics(Module: Integer; Value: Integer);
var
packets: Real;
ratio: Real;
begin
StatisticsLocker.Enter;
try
case Module of
NumberOfSlavesType: begin
if TIdStatisticsOperation(Value) = soIncrease then begin
Inc(flConnectedSlaves);
end
else begin
Dec(flConnectedSlaves);
end;
end;
NumberOfServicesType: begin
if TIdStatisticsOperation(Value) = soIncrease then begin
Inc(flConnectedServices);
Inc(fNumberOfConnectionsValue);
end
else begin
Dec(flConnectedServices);
end;
end;
NumberOfConnectionsType: begin
Inc(fNumberOfConnectionsValue);
end;
NumberOfPacketsType: begin
Inc(fNumberOfPacketsValue);
end;
CompressionRatioType: begin
ratio := fCompressionRatioValue;
packets := fNumberOfPacketsValue;
ratio := (ratio/100.0 * (packets - 1.0) + Value/100.0) / packets;
fCompressionRatioValue := Trunc(ratio * 100);
end;
CompressedBytesType: begin
fCompressedBytes := fCompressedBytes + Value;
end;
BytesReadType: begin
fBytesRead := fBytesRead + Value;
end;
BytesWriteType: begin
fBytesWrite := fBytesWrite + Value;
end;
end;
finally
StatisticsLocker.Leave;
end;
end;
procedure TIdTunnelMaster.DoConnect(Thread: TIdPeerThread);
begin
Thread.Data := TSlaveData.Create;
with TSlaveData(Thread.Data) do begin
Receiver := TReceiver.Create;
Sender := TSender.Create;
SelfDisconnected := False;
Locker := TCriticalSection.Create;
end;
if Assigned(OnConnect) then begin
OnConnect(Thread);
end;
SetStatistics(NumberOfSlavesType, Integer(soIncrease));
end;
procedure TIdTunnelMaster.DoDisconnect(Thread: TIdPeerThread);
begin
SetStatistics(NumberOfSlavesType, Integer(soDecrease));
// disconnect all service threads, owned by this tunnel
DisconnectAllSubThreads(Thread);
if Thread.Connection.Connected then
Thread.Connection.Disconnect;
If Assigned(OnDisconnect) then begin
OnDisconnect(Thread);
end;
with TSlaveData(Thread.Data) do begin
Receiver.Free;
Sender.Free;
Locker.Free;
TSlaveData(Thread.Data).Free;
end;
Thread.Data := nil;
end;
function TIdTunnelMaster.DoExecute(Thread: TIdPeerThread): boolean;
var
user: TSlaveData;
clientThread: MClientThread;
s: String;
ErrorConnecting: Boolean;
sIP: String;
CustomMsg: String;
Header: TIdHeader;
begin
result := true;
user := TSlaveData(Thread.Data);
if Thread.Connection.IOHandler.Readable(IdTimeoutInfinite) then begin
user.receiver.Data := Thread.Connection.CurrentReadBuffer;
// increase the packets counter
SetStatistics(NumberOfPacketsType, 0);
while user.receiver.TypeDetected do begin
// security filter
if not (user.receiver.Header.MsgType in [tmData, tmDisconnect, tmConnect, tmCustom]) then begin
Thread.Connection.Disconnect;
break;
end;
if user.receiver.NewMessage then begin
if user.Receiver.CRCFailed then begin
Thread.Connection.Disconnect;
break;
end;
// Custom data transformation
try
DoTransformRead(Thread);
except
Thread.Connection.Disconnect;
Break;
end;
// Action
case user.Receiver.Header.MsgType of
// transformation of data failed, disconnect the tunnel
tmError: begin
try
Thread.Connection.Disconnect;
break;
except
;
end;
end; // Failure END
// Data
tmData: begin
try
SetString(s, user.Receiver.Msg, user.Receiver.MsgLen);
ClientOperation(tmData, user.Receiver.Header.UserId, s);
except
;
end;
end; // Data END
// Disconnect
tmDisconnect: begin
try
ClientOperation(tmDisconnect, user.Receiver.Header.UserId, ''); {Do not Localize}
except
;
end;
end; // Disconnect END
// Connect
tmConnect: begin
// Connection should be done synchroneusly
// because more data could arrive before client
// connects asyncroneusly
try
clientThread := MClientThread.Create(self);
try
ErrorConnecting := False;
with clientThread do begin
UserId := user.Receiver.Header.UserId;
MasterThread := Thread;
OutboundClient := TIdTCPClient.Create(nil);
sIP := GStack.TInAddrToString(user.Receiver.Header.IpAddr);
if fbLockDestinationHost then begin
OutboundClient.Host := fsMappedHost;
if fbLockDestinationPort then
OutboundClient.Port := fiMappedPort
else
OutboundClient.Port := user.Receiver.Header.Port;
end
else begin
// do we tunnel all connections from the slave to the specified host
if sIP = '0.0.0.0' then begin {Do not Localize}
OutboundClient.Host := fsMappedHost;
OutboundClient.Port := user.Receiver.Header.Port; //fiMappedPort;
end
else begin
OutboundClient.Host := sIP;
OutboundClient.Port := user.Receiver.Header.Port;
end;
end;
OutboundClient.Connect;
end;
except
ErrorConnecting := True;
end;
if ErrorConnecting then begin
clientThread.Destroy;
end
else begin
clientThread.Resume;
end;
except
;
end;
end; // Connect END
// Custom data interpretation
tmCustom: begin
CustomMsg := ''; {Do not Localize}
DoInterpretMsg(Thread, CustomMsg);
if Length(CustomMsg) > 0 then begin
Header.MsgType := tmCustom;
Header.UserId := 0;
SendMsg(Thread, Header, CustomMsg);
end;
end;
end; // case
// Shift of data
user.Receiver.ShiftData;
end
else
break; // break the loop
end; // end while
end; // readable
end;
procedure TIdTunnelMaster.DoTransformRead(Thread: TIdPeerThread);
begin
if Assigned(fOnTransformRead) then
fOnTransformRead(Thread);
end;
procedure TIdTunnelMaster.DoTransformSend(Thread: TIdPeerThread; var Header: TIdHeader; var CustomMsg: String);
begin
if Assigned(fOnTransformSend) then
fOnTransformSend(Thread, Header, CustomMsg);
end;
procedure TIdTunnelMaster.DoInterpretMsg(Thread: TIdPeerThread; var CustomMsg: String);
begin
if Assigned(fOnInterpretMsg) then
fOnInterpretMsg(Thread, CustomMsg);
end;
// Disconnect all services owned by tunnel thread
procedure TIdTunnelMaster.DisconnectAllSubThreads(TunnelThread: TIdPeerThread);
var
Thread: MClientThread;
i: integer;
listTemp: TList;
begin
OnlyOneThread.Enter; // for now it is done with locking
listTemp := Clients.LockList;
try
for i := 0 to listTemp.count - 1 do begin
if Assigned(listTemp[i]) then begin
Thread := MClientThread(listTemp[i]);
if Thread.MasterThread = TunnelThread then begin
Thread.DisconnectedOnRequest := True;
Thread.OutboundClient.Disconnect;
end;
end;
end;
finally
Clients.UnlockList;
OnlyOneThread.Leave;
end;
end;
procedure TIdTunnelMaster.SendMsg(MasterThread: TIdPeerThread; var Header: TIdHeader; s: String);
var
user: TSlaveData;
tmpString: String;
begin
if Assigned(MasterThread.Data) then begin
TSlaveData(MasterThread.Data).Locker.Enter;
try
user := TSlaveData(MasterThread.Data);
try
// Custom data transformation before send
tmpString := s;
try
DoTransformSend(MasterThread, Header, tmpString);
except
IndyRaiseOuterException(EIdTunnelTransformErrorBeforeSend.Create(RSTunnelTransformErrorBS));
end;
if Header.MsgType = tmError then begin // error ocured in transformation
raise EIdTunnelTransformErrorBeforeSend.Create(RSTunnelTransformErrorBS);
end;
user.Sender.PrepareMsg(Header, PChar(tmpString), Length(tmpString));
MasterThread.Connection.Write(user.Sender.Msg);
except
raise;
end;
finally
TSlaveData(MasterThread.Data).Locker.Leave;
end;
end;
end;
function TIdTunnelMaster.GetClientThread(UserID: Integer): MClientThread;
var
Thread: MClientThread;
i: integer;
begin
Result := nil;
with Clients.LockList do
try
for i := 0 to Count-1 do begin
try
if Assigned(Items[i]) then begin
Thread := MClientThread(Items[i]);
if Thread.UserId = UserID then begin
Result := Thread;
break;
end;
end;
except
Result := nil;
end;
end;
finally
Clients.UnlockList;
end;
end;
procedure TIdTunnelMaster.DisconectAllUsers;
begin
TerminateAllThreads;
end;
procedure TIdTunnelMaster.ClientOperation(Operation: Integer; UserId: Integer; s: String);
var
Thread: MClientThread;
begin
Thread := GetClientThread(UserID);
if Assigned(Thread) then begin
Thread.Locker.Enter;
try
if not Thread.SelfDisconnected then begin
case Operation of
tmData: begin
try
Thread.OutboundClient.IOHandler.CheckForDisconnect;
if Thread.OutboundClient.Connected then
Thread.OutboundClient.Write(s);
except
try
Thread.OutboundClient.Disconnect;
except
;
end;
end;
end;
tmDisconnect: begin
Thread.DisconnectedOnRequest := True;
try
Thread.OutboundClient.Disconnect;
except
;
end;
end;
end;
end;
finally
Thread.Locker.Leave;
end;
end; // Assigned
end;
/////////////////////////////////////////////////////////////////
//
// MClientThread thread, talks to the service
//
/////////////////////////////////////////////////////////////////
constructor MClientThread.Create(AMaster: TIdTunnelMaster);
begin
MasterParent := AMaster;
FreeOnTerminate := True;
DisconnectedOnRequest := False;
SelfDisconnected := False;
Locker := TCriticalSection.Create;
MasterParent.Clients.Add(self);
AMaster.SetStatistics(NumberOfServicesType, Integer(soIncrease));
inherited Create(True);
end;
destructor MClientThread.Destroy;
var
Header: TIdHeader;
begin
MasterParent.SetStatistics(NumberOfServicesType, Integer(soDecrease));
MasterParent.Clients.Remove(self);
try
if not DisconnectedOnRequest then begin
// service disconnected the thread
try
Header.MsgType := tmDisconnect;
Header.UserId := UserId;
MasterParent.SendMsg(MasterThread, Header, RSTunnelDisconnectMsg);
except
end;
end;
if OutboundClient.Connected then
begin
OutboundClient.Disconnect;
end;
except
end;
MasterThread := nil;
try
OutboundClient.Free;
except
end;
Locker.Free;
Terminate; // dodano
inherited Destroy;
end;
// thread which talks to the service
procedure MClientThread.Execute;
var
s: String;
Header: TIdHeader;
begin
try
while not Terminated do begin
if OutboundClient.Connected then begin
if OutboundClient.IOHandler.Readable(IdTimeoutInfinite) then begin
s := OutboundClient.CurrentReadBuffer;
try
Header.MsgType := tmData;
Header.UserId := UserId;
MasterParent.SendMsg(MasterThread, Header, s);
except
Terminate;
break;
end;
end;
end
else begin
Terminate;
break;
end;
end;
except
;
end;
Locker.Enter;
try
SelfDisconnected := True;
finally
Locker.Leave;
end;
end;
end.