Re: TCP socket handling errors

Sad Clouds <[email protected]>
Newsgroups gmane.os.freebsd.devel.net
Message-ID <[email protected]>
On Wed, 3 Apr 2024 17:28:52 +0200
Michael Tuexen <[email protected]> wrote:

> > On 3. Apr 2024, at 15:44, Sad Clouds <[email protected]> wrote:
> > 
> > I found a bug that is still open from May 2010 and describes the same
> > behaviour that I see with my application:
> > 
> > https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=146845
> > 
> > If this hasn't been fixed over the last 14 years, then I guess I will
> > add some code to simply ignore ECONNRESET on close(2) for FreeBSD and
> > MacOS. This seems the be the general advice from other people who hit
> > this issue.
> I'll bring this up on the bi-weekly FreeBSD transport call.
> 
> Best regards
> Michael
> > 
> 

Hello, I've attached a test program, this easily reproduces the issue
on Raspberry Pi 4 within a few seconds of running it.

Server output:
$ ./econnreset server
Server: accept()
Server: shutdown()
Server: close()
..
Server: accept()
Server: shutdown()
Server: close()
close() failed, error=Connection reset by peer


Client output (aborts when server exists due to close() failure):
$ while true; do ./econnreset client || break; done
Client: connect()
Client: shutdown()
Client: close()
..
Client: connect()
Assertion failed: (int_val == 0), function client, file econnreset.c,
line 156. Abort trap (core dumped)
econnreset.c (text/x-csrc, 5.7 KB)
/*
* cc -D_POSIX_C_SOURCE=200809L -D__BSD_VISIBLE -D__XSI_VISIBLE \
* -O2 -std=c11 -Wpedantic -Wall -o econnreset econnreset.c
*/

#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <unistd.h>

#define ADDR "127.0.0.1"
#define PORT 9999
#define SIZE (10 * 1024U * 1024U)

#define CLI_ID    "client"
#define CLI_ID_LEN 6

#define SRV_ID     "server"
#define SRV_ID_LEN 6

static void server()
{
	int int_val, sockfd, connfd;
	struct sockaddr_storage socket_addr = {0};
	socklen_t               socket_addr_size;

	uint8_t snd_buf[1024], rcv_buf[1024];
	size_t  snd_size, rcv_size, io_size;
	ssize_t ssize_val;

	/* Socket family */
	((struct sockaddr_in *)&socket_addr)->sin_family = AF_INET;
	/* Socket address */
	int_val = inet_pton(AF_INET, ADDR, &(((struct sockaddr_in *)&socket_addr)->sin_addr));
	assert(int_val == 1);
	/* Socket port */
	((struct sockaddr_in *)&socket_addr)->sin_port = htons(PORT);
	/* Socket size */
	socket_addr_size = sizeof(struct sockaddr_in);

	sockfd = socket(socket_addr.ss_family, SOCK_STREAM, 0);
	assert(sockfd >= 0);

	int_val = bind(sockfd, (const struct sockaddr *)&socket_addr, socket_addr_size);
	assert(int_val == 0);

	int_val = listen(sockfd, 128);
	assert(int_val == 0);

	while (1)
	{
		printf("Server: accept()\n");
		connfd = accept(sockfd, NULL, NULL);
		assert(connfd >= 0);

		/* Size of data to send/receive */
		snd_size = rcv_size = SIZE;

		/* Data copy loop */
		while (snd_size != 0 && rcv_size != 0)
		{
			/* Send data */
			io_size = (sizeof(snd_buf) <= snd_size) ? sizeof(snd_buf) : snd_size;
			ssize_val = send(connfd, snd_buf, io_size, MSG_NOSIGNAL);
			if (ssize_val < 0)
			{
				fprintf(stderr, "send() failed, error=%s\n", strerror(errno));
				goto cleanup;
			}
			snd_size -= (size_t)ssize_val;

			/* Receive data */
			io_size = (sizeof(rcv_buf) <= rcv_size) ? sizeof(rcv_buf) : rcv_size;
			ssize_val = recv(connfd, rcv_buf, io_size, MSG_NOSIGNAL | MSG_WAITALL);
			if (ssize_val < 0)
			{
				fprintf(stderr, "recv() failed, error=%s\n", strerror(errno));
				goto cleanup;
			}
			rcv_size -= (size_t)ssize_val;
		}

		/* Send server ID string to client */
		ssize_val = send(connfd, SRV_ID, SRV_ID_LEN, MSG_NOSIGNAL);
		if (ssize_val <= 0)
		{
			fprintf(stderr, "send() failed, error=%s\n", strerror(errno));
			goto cleanup;
		}

		/* No more sending, do TCP half close */
		printf("Server: shutdown()\n");
		int_val = shutdown(connfd, SHUT_WR);
		if (int_val != 0)
		{
			fprintf(stderr, "shutdown() failed, error=%s\n", strerror(errno));
			goto cleanup;
		}

		/* Receive client ID string and verify it */
		ssize_val = recv(connfd, rcv_buf, CLI_ID_LEN, MSG_NOSIGNAL | MSG_WAITALL);
		if (ssize_val < 0)
		{
			fprintf(stderr, "recv() failed, error=%s\n", strerror(errno));
			goto cleanup;
		}
		assert(ssize_val == CLI_ID_LEN);
		assert(strncmp((char *)rcv_buf, CLI_ID, CLI_ID_LEN) == 0);

		cleanup:
		printf("Server: close()\n");
		int_val = close(connfd);
		if (int_val != 0)
		{
			/* We hit https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=146845 */
			fprintf(stderr, "close() failed, error=%s\n", strerror(errno));
			exit(EXIT_FAILURE);
		}
	}
}

static void client()
{
	int int_val, connfd;
	struct sockaddr_storage socket_addr = {0};
	socklen_t               socket_addr_size;

	uint8_t snd_buf[1024], rcv_buf[1024];
	size_t  snd_size, rcv_size, io_size;
	ssize_t ssize_val;

	/* Socket family */
	((struct sockaddr_in *)&socket_addr)->sin_family = AF_INET;
	/* Socket address */
	int_val = inet_pton(AF_INET, ADDR, &(((struct sockaddr_in *)&socket_addr)->sin_addr));
	assert(int_val == 1);
	/* Socket port */
	((struct sockaddr_in *)&socket_addr)->sin_port = htons(PORT);
	/* Socket size */
	socket_addr_size = sizeof(struct sockaddr_in);

	connfd = socket(socket_addr.ss_family, SOCK_STREAM, 0);
	assert(connfd >= 0);

	printf("Client: connect()\n");
	int_val = connect(connfd, (const struct sockaddr *)&socket_addr, socket_addr_size);
	assert(int_val == 0);

	/* Size of data to send/receive */
	snd_size = rcv_size = SIZE;

	/* Data copy loop */
	while (snd_size != 0 && rcv_size != 0)
	{
		/* Send data */
		io_size = (sizeof(snd_buf) <= snd_size) ? sizeof(snd_buf) : snd_size;
		ssize_val = send(connfd, snd_buf, io_size, MSG_NOSIGNAL);
		assert(ssize_val >= 0);
		snd_size -= (size_t)ssize_val;

		/* Receive data */
		io_size = (sizeof(rcv_buf) <= rcv_size) ? sizeof(rcv_buf) : rcv_size;
		ssize_val = recv(connfd, rcv_buf, io_size, MSG_NOSIGNAL | MSG_WAITALL);
		assert(ssize_val >= 0);
		rcv_size -= (size_t)ssize_val;
	}

	/* Send client ID string to server */
	ssize_val = send(connfd, CLI_ID, CLI_ID_LEN, MSG_NOSIGNAL);
	assert(ssize_val >= 0);

	/* No more sending, do TCP half close */
	printf("Client: shutdown()\n");
	int_val = shutdown(connfd, SHUT_WR);
	assert(int_val == 0);

	/* Receive server ID string and verify it */
	ssize_val = recv(connfd, rcv_buf, SRV_ID_LEN, MSG_NOSIGNAL | MSG_WAITALL);
	assert(ssize_val == SRV_ID_LEN);
	assert(strncmp((char *)rcv_buf, SRV_ID, SRV_ID_LEN) == 0);

	printf("Client: close()\n");
	int_val = close(connfd);
	if (int_val != 0)
	{
		/* We hit https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=146845 */
		fprintf(stderr, "close() failed, error=%s\n", strerror(errno));
		exit(EXIT_FAILURE);
	}
}

int main(int argc, char *argv[])
{
	if (argc != 2)
	{
		printf("Usage: econnreset client | server\n");
		exit(EXIT_FAILURE);
	}

	if (strcmp(argv[1], "client") == 0)
	{
		client();
	}
	else if (strcmp(argv[1], "server") == 0)
	{
		server();
	}
	else
	{
		printf("Usage: econnreset client | server\n");
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.