0

Please note that this question is specifically aimed at the Ada language and Ada's "g-socket" API

I have opened a Socket locally and am listening for incoming connections. Connections are accepted and I am able to establish somewhat coherent data transfer over the connection by reading from and writing to a Stream object attached to the remote socket.

Question:

When a Stream is attached to a TCP Socket, does each call to a generalised stream 'Write procedure cause a packet to be sent immediately?

Example A:

--  two separate `'Write` calls always seems to generate two packets of 1 byte each
U8'Write (Comms, Number_Of_Security_Types);
U8'Write (Comms, Security_Type_None);

Example B:

--  One `'Write` call that happens to send the same data formatted as a 16 bit value is sent as a single packet.
U16'Write (Comms,
  (U16 (Number_Of_Security_Types) * 16#100#) +
  U16 (Security_Type_None)
  );

Example C:

--  This is a complex record with a further record nested within it.
--    its `'Write` callback is implemented as a series of many sequential `Integer'Write` calls...
Server_Init'Write (Comms, Server_Init_Rec);

Examples A and C cause malformed packets to be detected by Wireshark, but Example B creates a well crafted packet with no issues.

This behaviour seems deterministic but I cannot find any coherent documentation regarding the 'Write --> Stream --> Socket arrangement with regard to how and when packets are dispatched.

  • according this link, https://www2.adacore.com/gap-static/GNAT_Book/html/rts/g-socket__adb.htm#1395_14 i thiink what happens depends on the underlying OS – LoneWanderer Oct 13 '19 at 14:44
  • The [question](https://stackoverflow.com/questions/58361758/tcp-ip-using-ada-sockets-how-to-correctly-finish-a-packet) is different but the answers are the same. – user207421 Oct 13 '19 at 23:23

2 Answers2

1

According this link the underlying code for TCP stream Write should be as follows. As you can see, there is a loop that tries to send data until everything passed into Send_Socket. So to me, everything depends on the C_Sendto implementation fwhici itself calls an OS primitive

However, a single Write of said 8 bits does not guarantee that it will correspond to a network packet containing those packets (because of the very nature of TCP).

   -----------
   -- Write --
   -----------

   procedure Write 
     (Stream : in out Datagram_Socket_Stream_Type; 
      Item   : Ada.Streams.Stream_Element_Array) 
   is
      First : Ada.Streams.Stream_Element_Offset          := Item'First; 
      Index : Ada.Streams.Stream_Element_Offset          := First - 1; 
      Max   : constant Ada.Streams.Stream_Element_Offset := Item'Last; 

   begin
      loop
         Send_Socket <== try to send until all content of write sent ?
           (Stream.Socket,
            Item (First .. Max),
            Index,
            Stream.To);

         --  Exit when all or zero data sent. Zero means that the
         --  socket has been closed by peer.

         exit when Index < First or else Index = Max;

         First := Index + 1;
      end loop;

      if Index /= Max then
         raise Socket_Error;
      end if;
   end Write;

-- [...]
   procedure Send_Socket 
     (Socket : Socket_Type; 
      Item   : Ada.Streams.Stream_Element_Array; 
      Last   : out Ada.Streams.Stream_Element_Offset; 
      To     : Sock_Addr_Type) 
   is
      use type Ada.Streams.Stream_Element_Offset;

      Res : C.int; 
      Sin : aliased Sockaddr_In; 
      Len : aliased C.int := Sin'Size / 8; 

   begin
      Sin.Sin_Family := C.unsigned_short (Families (To.Family));
      Sin.Sin_Addr   := To_In_Addr (To.Addr);
      Sin.Sin_Port   := Port_To_Network (C.unsigned_short (To.Port));

      Res := C_Sendto -- <== where things happen
        (C.int (Socket),
         Item (Item'First)'Address,
         Item'Length, 0,
         Sin'Unchecked_Access,
         Len);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Last := Item'First + Ada.Streams.Stream_Element_Offset (Res - 1);
   end Send_Socket;

https://www2.adacore.com/gap-static/GNAT_Book/html/rts/g-socthi__adb.htm

   --------------
   -- C_Sendto --
   --------------

   function C_Sendto 
     (S     : C.int; 
      Msg   : System.Address; 
      Len   : C.int; 
      Flags : C.int; 
      To    : Sockaddr_In_Access; 
      Tolen : C.int) 
      return  C.int
   is
      Res : C.int; 

   begin
      loop
         Res := Syscall_Sendto (S, Msg, Len, Flags, To, Tolen);
         exit when Thread_Blocking_IO
           or else Res /= Failure
           or else Table (S).Non_Blocking
           or else Errno /= Constants.EWOULDBLOCK;
         delay Quantum;
      end loop;

      return Res;
   end C_Sendto;
LoneWanderer
  • 3,058
  • 1
  • 23
  • 41
1

The default behaviour of ’Write, ’Read is defined in ARM13.13.2(8.2).

In your first case, that’ll amount to two calls to sendto(). If you’ve set TCP_NODELAY, that will probably result in two IP packets on the network, as will happen if there’s a long enough time interval between the two ’Writes (100 ms?). Otherwise, the data will be buffered until the low-level network software has an IP packet-full (or, again, a long enough time interval has elapsed).

If you had

type Info is record
   A : U8;
   B : U8;
end record;

then Info’Write((B => 5, A => 6)) would result in two sendto() calls transferring one byte each, the first of value 6 (the A value; transfer in canonical order, A then B), the second of value 5.


I don’t see how these can result in malformed packets. More info needed.

Simon Wright
  • 25,108
  • 2
  • 35
  • 62
  • Re: "malformed packets". That is what Wireshark is describing them as. I'm now wondering if I'm putting too much faith in Wireshark's VNC protocol dissector. –  Oct 13 '19 at 17:29
  • It won't definitely result in two packets. There are still circumstances in which coalescing can occur, for example if the receive window is closed and no transmission can take place before the second write. In any case, for example if the receiver is slow, it can still all be *received* in a single read, – user207421 Oct 13 '19 at 18:45
  • @Wossname Make sure that you have enabled reassembling in Wireshark for VNC (reassemble VNC messages spanning multiple TCP segments) and TCP (allow subdissector to reassemble TCP streams). – Markus Oct 15 '19 at 13:36