gvanem / Watt-32

Watt-32 TCP/IP library and samples.
https://www.watt-32.net/
18 stars 8 forks source link

Don't set `so_error` in `shutdown()` #95

Closed jwt27 closed 8 months ago

jwt27 commented 8 months ago

Ran into this recently when I made a websocket server using Boost. After any shutdown() is called, the next operation always fails with ESHUTDOWN.

What happens is: when the WS client sends a "close" header, the server (Boost code) calls shutdown(SHUT_WR) to send FIN, then continues to recv() the rest of the message. But it gets confused due to the error, so WS sessions never terminate cleanly.

Simple solution is to not set so_error. Note that this does remove all current uses of ESHUTDOWN.

Would also be nice if shutdown() could actually send the FIN. I think most code from close_s() can be moved here.

jwt27 commented 8 months ago

Basic test case:

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main()
{
  const int server = socket(AF_INET, SOCK_STREAM, 0);
  if (server == -1)
  {
    perror("socket()");
    return -1;
  }

  struct sockaddr_in addr = { };
  addr.sin_family = AF_INET,
  addr.sin_addr.s_addr = INADDR_ANY,
  addr.sin_port = htons(12345);

  if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1)
  {
    perror("bind()");
    goto fail;
  }

  if (listen(server, 8) == -1)
  {
    perror("listen()");
    goto fail;
  }

  while (1)
  {
    const int client = accept(server, NULL, NULL);
    if (client == -1)
    {
      perror("accept()");
      goto fail;
    }

    shutdown(client, SHUT_WR);

    while (1)
    {
      char buf[256];
      const int n = recv(client, buf, sizeof(buf) - 1, 0);

      if (n == -1)
      {
        perror("recv()");
        close(client);
        goto fail;
      }
      else if (n == 0)
      {
        printf("EOF\n", client);
        close(client);
        break;
      }
      else
      {
        buf[n] = '\0';
        printf("message: %s", buf);
      }
    }
  }

  close(server);
  return 0;

fail:
  close(server);
  return -1;
}

Send something to it with echo "hello" | nc 10.0.0.123 12345. Currently it fails on recv(). Patch fixes this, but netcat still waits for ctrl-c due to missing FIN.

jwt27 commented 8 months ago

Thanks for merging, again!

Quoting myself:

Would also be nice if shutdown() could actually send the FIN. I think most code from close_s() can be moved here.

Was looking into this last night. In principle only _tcp_close() should be needed. But it doesn't work, as it stops sending ACKs in FIN-WAIT2 state. The other way around also seems unfinished - there is code for a CLOSE-WAIT state, but it is never used. When the remote side sends a FIN it jumps directly to LAST-ACK.