[gtk-server] TCP package boundaries

  • From: Klaus Grue <grue@xxxxxxx>
  • To: gtk-server@xxxxxxxxxxxxx
  • Date: Fri, 4 Feb 2011 11:47:55 +0100 (CET)

Hi,

Using the gtk-server via TCP, I noted the following problem:

My program sends a command followed by an ASCII Newline (code 10).

Then my program receives two responses: an ok for the command and then a "-1" which indicates that an error has occurred.

Inspection of the -log=logfile shows that the gtk-server thinks it got a command followed by an empty line. It is the empty line which gives the "-1" response.

Using tcpdump shows that the gtk-server receives two packets: one containing the command and one containing just the newline character.

Looking into gtk-server.c at the lines just before /* End TCP story */ shows that the gtk-server indeed interprets package boundaries as marking the end of a command.

However, the TCP protocol just allows to send a stream of bytes and package boundaries can be changed arbitrarily during transmission. Thus, it seems dangerous to rely on package boundaries being preserved. Even if a command is sent as one package, it may arrive as several packages.

Thus, if the gtk-server is still maintained, I would suggest to change the code such that newline bytes are used as command separators rather than package boundaries. I have outlined what the change could look like below.

Cheers,
Klaus

---

Here is the current code from gtk-server.c, edited to fit e-mail line width:

  /* Set the page factor used by realloc to 0 */
  page = 0;
  /* We enter the mainloop - read incoming text */
  while(1){
    len = 0;
    do {
      numbytes = recv(new_fd, buf, MAX_LEN, 0);
      if (numbytes < 0) COMPLAIN;
      /* Realloc preserves contents, add one for \0 */
      if (len + numbytes > MAX_LEN*page + 1) {
        page++; in = realloc(in, MAX_LEN*page + 1);}
      if (in == NULL) COMPLAIN;
      memcpy(in + len, buf, numbytes);
      len += numbytes;}
    while (numbytes == MAX_LEN);
    /* If disconnected, quit the gtk-server */
    if (numbytes == 0) exit(EXIT_SUCCESS);
    /* Make sure we have a NULL terminated character array */
    if (len > 0) in[len] = '\0';
    /* If logging is enabled, write incoming text to log */
    if (logfile != NULL){
      fprintf(logfile, "SCRIPT: %s\n", Trim_String(in));
      fflush(logfile);}
    retstr = Call_Realize(Trim_String(in), cinv_ctx);
    /* If logging is enabled, write returnstring to log */
    if (logfile != NULL){
      fprintf(logfile, "SERVER: %s\n", retstr);
      fflush(logfile);}
    /* Now send the result back to the socket */
    strcat(retstr, "\n");
    numbytes = send(new_fd, retstr, strlen(retstr), 0);
if (numbytes < 0) Print_Error("%s%s", 2, "\nError in sending to TCP: ", strerror(errno));}
...
} /* End TCP story */

---

As can be seen above, the gtk-server reads bytes until there are no more bytes in the buffers. Thus, it reads until a package boundary. I would suggest to read until a '\n' is reached.

SUGGESTION: Change this:

    /* Make sure we have a NULL terminated character array */
    if (len > 0) in[len] = '\0';

Into this:

    /* Make sure we have a command */
    in[len] = '\0';
    endpoint = index(in,'\n');
    if(endpoint == NULL) continue;
    *(endpoint++)='\0';

---

Then there is the problem that we could have received more than one command (in case the client program is so eager that it sends several commands in one go). So we need to move all uninterpreted bytes to the beginning of the "in" buffer:

SUGGESTION: change this:

    retstr = Call_Realize(Trim_String(in), cinv_ctx);

Into this:

    retstr = Call_Realize(Trim_String(in), cinv_ctx);
    for(len=0;*endpoint!='\0';len++) in[len]=*(endpoint++);

---

Now, whenever the while(1) restarts, there may be uninterpreted bytes in the "in" buffer, so we should not reset "len".

SUGGESTION: Change this:

  /* We enter the mainloop - read incoming text */
  while(1){
    len = 0;

Into this:

  /* We enter the mainloop - read incoming text */
  len = 0;
  while(1){

Other related posts:

  • » [gtk-server] TCP package boundaries - Klaus Grue