There are several problems with your code:
you are expecting String[6]
to be 6 bytes, but it is actually 7 bytes instead. You are declaring a ShortString
with a max length of 6 AnsiChar
characters, and a ShortString
contains a leading byte for the length.
your record is not packed
, so it is subject to alignment padding. So don't try to send it as-is. You are dealing with variable-length data, so you should be serializing the record's content instead.
array of byte
is a dynamic array. A dynamic array is a pointer to data allocated elsewhere in memory. The byte size of the array variable itself is SizeOf(Pointer)
, which is 4 on 32bit and 8 on 64bit. You have to dereference the pointer in order to access the allocated memory block. You are doing that on your receive, but are not doing that on your send.
TCP is a streaming transport, it has no concept of messages at all. Socket.ReceiveLength
reports the number of unread bytes that are currently in the socket's internal receive buffer at that particular moment. Those bytes are arbitrary, there may be as few as 1 byte in the buffer. And more bytes may be received after you query the length and before performing the actual read.
when sending data, it is not guaranteed to send as many bytes are you request, it may send fewer bytes. The return value of SendBuf()
indicates the actual number of bytes accepted for sending, so you may need to call SendBuf()
multiple times to send a particular piece of data. So loop the send until the data is exhausted.
Since you are trying to tunnel arbitrary data, you need to specify how many bytes you are actually tunneling.
With that said, try something more like this:
function TForm1.SendData(Socket: TCustomWinSocket; const Data; DataLen: Integer): Boolean;
var
PData: PByte;
NumSent: Integer;
begin
Result := False;
PData := PByte(@Data);
while DataLen > 0 do
begin
// SendBuf() returns -1 on error. If that error is WSAEWOULDBLOCK
// then retry the send again. Otherwise, TCustomWinSocket disconnects
// itself, and if its OnError event handler does not set the ErrorCode
// to 0 then it raises an ESocketError exception...
//
NumSent := Socket.SendBuf(PData^, DataLen);
if NumSent = -1 then
begin
if WSAGetLastError() <> WSAEWOULDBLOCK then
Exit;
end else
begin
Inc(PData, NumSent);
Dec(DataLen, NumSent);
end;
end;
Result := True;
end;
function TForm1.SendInteger(Socket: TCustomWinSocket; Value: Integer): Boolean;
begin
Value := htonl(Value);
Result := SendData(Socket, Value, SizeOf(Value));
end;
function TForm1.SendString(Socket: TCustomWinSocket; const Value: String): Boolean;
var
S: AnsiString; // or UTF8String
begin
S := AnsiString(Value);
// or: S := UTF8Encode(Value);
Result := SendInteger(Socket, Length(S));
if Result then
Result := SendData(Socket, PAnsiChar(S)^, Length(S));
end;
function TForm1.SendBytes(Socket: TCustomWinSocket; const Data: array of Byte): Boolean;
begin
Result := SendInteger(Socket, Length(Data));
if Result then
Result := SendData(Socket, PByte(Data)^, Length(Data));
end;
procedure TForm1.SocksServerClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
Packet: array of byte;
begin
Len := Socket.ReceiveLength;
if Len <= 0 the Exit;
SetLength(Packet, Len);
Len := Socket.ReceiveBuf(PByte(Packet)^, Len);
if Len <= 0 the Exit;
if Len < Length(Packet) then
SetLength(Packet, Len);
if SendString(TunnelClient, PSocket_Identity(Socket.Data).Sock_ID) then
SendBytes(TunnelClient, Packet);
end;
And then adjust your tunnel receiver accordingly to de-serialize the values as needed (read an Integer
byte count, convert it to host byte order using ntohl()
, then read the specified number of bytes).