1

I have this record:

type
  TSocks5_Packet = record
    Socks_ID: String[6];
    Socks_Packet: array of byte;
  end;

I put String[6] because I'm sure that this string will always have 6 characters (and to try facilitate my job, but wasn't enough).

I try to send this record here:

procedure TForm1.SocksServerClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  Socks5_Request: TSocks5_Packet;
begin
  SetLength(Socks5_Request.Socks_Packet, Socket.ReceiveLength);
  Socket.ReceiveBuf(Socks5_Request.Socks_Packet[0], Length(Socks5_Request.Socks_Packet));
  Socks5_Request.Socks_ID:= PSocket_Identity(Socket.Data).Sock_ID;
  TunnelClient.SendBuf(Socks5_Request, Length(Socks5_Request.Socks_Packet + Length(Socks5_Request.Socks_ID)));
end;

I'm pretty sure the problem is on SendBuf second parameter, where I specify the count of bytes to be sent. What is the correct approach, and how I should study to learn it?

user2864778
  • 333
  • 5
  • 18
  • You haven't mentioned the problem but the title suggests that it is about what you expect the size of the record to be does not match with what you receive. Probably you are not aware that Socks_Packet is a pointer. – Sertac Akyuz Dec 13 '16 at 22:24
  • Your handling of both arguments isn't going to work. Instead of trying to bit the data in memory you are going to have to serialize it. – David Heffernan Dec 13 '16 at 22:35
  • @SertacAkyuz, the problem is that the data is coming invalid on the receiving socket. – user2864778 Dec 13 '16 at 22:49
  • @MartynA, I'm using Delphi 10. – user2864778 Dec 13 '16 at 22:49
  • @DavidHeffernan, Can you please post a code? I'm almost sure that the problem corrupting the data is the Length used on SendBuf, which doesn't meet the real Length of data being sent. – user2864778 Dec 13 '16 at 22:49
  • It would be better to understand rather than have somebody write the code for you. You first of all need to understand how the record memory is laid out and why it can't be blitted. What you are "almost sure" of isn't even the half of your problem. – David Heffernan Dec 13 '16 at 22:50
  • @DavidHeffernan, yes, that's why on the original question I asked how I should study to understand this problem. Because I dont even know what problem is this, or how to search about it? – user2864778 Dec 13 '16 at 22:51
  • 2
    It doesn't matter a jot what length you use. The record contains a pointer to the array and not the array itself. And short strings were deprecated last century. – David Heffernan Dec 13 '16 at 22:54
  • 1
    An *array of* anything is a dynamic array, which means it's a pointer to the beginning of a block of memory. How big that block of memory is or how it's arranged in memory depends on the size of the array and the type of each element. The first can only be determined at runtime when you SetLength on the array, but element [0] us still only a pointer to the start of a block of memory. You can't just pass that pointer out of process. As @David said, you're going to need to serialize it. See http://stackoverflow.com/q/11427706/62576 for an example of how to do so. – Ken White Dec 13 '16 at 23:25

1 Answers1

5

There are several problems with your code:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for your patience. I'm studying your solution, instead of copy/paste only. So far I still not get it completely on my program. Thank you again. – user2864778 Dec 14 '16 at 14:11