NetBSD Problem Report #54650

From www@netbsd.org  Sat Oct 26 21:49:11 2019
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
	(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id B5B677A158
	for <gnats-bugs@gnats.NetBSD.org>; Sat, 26 Oct 2019 21:49:11 +0000 (UTC)
Message-Id: <20191026214911.06ED67A253@mollari.NetBSD.org>
Date: Sat, 26 Oct 2019 21:49:11 +0000 (UTC)
From: cryintothebluesky@gmail.com
Reply-To: cryintothebluesky@gmail.com
To: gnats-bugs@NetBSD.org
Subject: Calling accept() with a non-blocking listening socket, returns a non-blocking connected socket, which is incorrect behaviour
X-Send-Pr-Version: www-1.0

>Number:         54650
>Category:       kern
>Synopsis:       Calling accept() with a non-blocking listening socket, returns a non-blocking connected socket, which is incorrect behaviour
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat Oct 26 21:50:00 +0000 2019
>Last-Modified:  Sun Oct 27 13:25:01 +0000 2019
>Originator:     Sad Clouds
>Release:        8.1
>Organization:
>Environment:
NetBSD atom510 8.1 NetBSD 8.1 (GENERIC) #0: Fri May 31 14:17:14 BST 2019  roman@z600:/netbsd_build/obj.amd64/sys/arch/amd64/compile/GENERIC amd64
>Description:
Calling accept() with a non-blocking listening socket, returns a non-blocking connected socket, which is incorrect behaviour. The new returned connected socket should be blocking.
>How-To-Repeat:

>Fix:

>Audit-Trail:
From: Sad Clouds <cryintothebluesky@gmail.com>
To: gnats-bugs@netbsd.org
Cc: gnats-admin@netbsd.org
Subject: Re: kern/54650: Calling accept() with a non-blocking listening
 socket, returns a non-blocking connected socket, which is incorrect
 behaviour
Date: Sat, 26 Oct 2019 22:51:45 +0100

 This is a multi-part message in MIME format.

 --Multipart=_Sat__26_Oct_2019_22_51_45_+0100_yuQIk.oe0XeU2Ov6
 Content-Type: text/plain; charset=US-ASCII
 Content-Transfer-Encoding: 7bit

 Attached is a test program, this is the output I get on Linux and NetBSD

 On Linux:
 $ ./a.out
 Create server thread
 Create client thread
 Accept connection, fd=5, addr=127.0.0.1, port=47746
 Connected socket is blocking

 On NetBSD:
 $ ./a.out
 Create server thread
 Create client thread
 Accept connection, fd=5, addr=127.0.0.1, port=65534
 Connected socket is non-blocking

 --Multipart=_Sat__26_Oct_2019_22_51_45_+0100_yuQIk.oe0XeU2Ov6
 Content-Type: text/x-csrc;
  name="test.c"
 Content-Disposition: attachment;
  filename="test.c"
 Content-Transfer-Encoding: 7bit

 #include <errno.h>
 #include <fcntl.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <arpa/inet.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <unistd.h>
 #include <pthread.h>
 #include <poll.h>

 #define ADDR "127.0.0.1"
 #define PORT 9999


 /* Set fd blocking or non-blocking */
 void set_fd_nonblock(int fd, bool set_flag)
 {
 	int ret_int, fcntl_flags;

 	/* Get current flags */
 	fcntl_flags = fcntl(fd, F_GETFL, 0);
 	if (fcntl_flags == -1)
 	{
 		fprintf(stderr, "Error %s:%d: fcntl() with F_GETFL failed, %s\n",
 			__FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}

 	if (set_flag)
 	{
 		/* Set non-blocking */
 		fcntl_flags |= O_NONBLOCK;
 	}
 	else
 	{
 		/* Set blocking */
 		fcntl_flags &= ~O_NONBLOCK;
 	}

 	ret_int = fcntl(fd, F_SETFL, fcntl_flags);
 	if (ret_int == -1)
 	{
 		fprintf(stderr, "Error %s:%d: fcntl() with F_SETFL failed, %s\n",
 			__FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}
 }

 /* Check fd is non-blocking */
 bool is_fd_nonblock(int fd)
 {
 	int fcntl_flags;

 	/* Get current flags */
 	fcntl_flags = fcntl(fd, F_GETFL, 0);
 	if (fcntl_flags == -1)
 	{
 		fprintf(stderr, "Error %s:%d: fcntl() with F_GETFL failed, %s\n",
 			__FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}

 	return (fcntl_flags & O_NONBLOCK);
 }


 /* Client thread */
 static void *cli(void *arg)
 {
 	struct sockaddr_in addr;
 	int conn_fd;

 	memset(&addr, 0, sizeof(addr));
 	if (inet_pton(AF_INET, ADDR, &(addr.sin_addr)) != 1)
 	{
 		fprintf(stderr, "Error %s:%d: inet_pton() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}
 	addr.sin_family = AF_INET;
 	addr.sin_port = htons(PORT);

 	conn_fd = socket(AF_INET, SOCK_STREAM, 0);
 	if (conn_fd < 0)
 	{
 		fprintf(stderr, "Error %s:%d: socket() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}

 	if (connect(conn_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
 	{
 		fprintf(stderr, "Error %s:%d: connect() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}

 	return NULL;
 }

 /* Server thread */
 static void *srv(void *arg)
 {
 	struct sockaddr_in addr, cli_addr;
 	socklen_t cli_addr_len;
 	char addr_str[INET6_ADDRSTRLEN];
 	int ret_int, listen_fd, conn_fd;
 	ssize_t ret_ssize;
 	struct pollfd pollfd_array[1];

 	memset(&addr, 0, sizeof(addr));
 	addr.sin_family = AF_INET;
 	addr.sin_addr.s_addr = htonl(INADDR_ANY);
 	addr.sin_port = htons(PORT);

 	/* The usual - socket, bind, listen */
 	listen_fd = socket(AF_INET, SOCK_STREAM, 0);
 	if (listen_fd < 0)
 	{
 		fprintf(stderr, "Error %s:%d: socket() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}
 	if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
 	{
 		fprintf(stderr, "Error %s:%d: bind() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}
 	if (listen(listen_fd, 128) < 0)
 	{
 		fprintf(stderr, "Error %s:%d: listen() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}

 	/* Set listening socket non-blocking */
 	set_fd_nonblock(listen_fd, true);

 	/* Set poll events */
 	pollfd_array[0].fd = listen_fd;
 	pollfd_array[0].events = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI);

 	/* Block in poll until socket is ready for I/O */
 	ret_int = poll(pollfd_array, 1, -1);
 	if (ret_int < 0)
 	{
 		fprintf(stderr, "Error %s:%d: poll() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}

 	/* Accept new connection */
 	cli_addr_len = sizeof(cli_addr);
 	conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_addr_len);
 	if (conn_fd < 0)
 	{
 			fprintf(stderr, "Error %s:%d: accept() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 			exit(EXIT_FAILURE);
 	}

 	if (inet_ntop(AF_INET, &(cli_addr.sin_addr), addr_str, sizeof(addr_str)) == NULL)
 	{
 		fprintf(stderr, "Error %s:%d: inet_ntop() failed, %s\n", __FILE__, __LINE__, strerror(errno));
 		exit(EXIT_FAILURE);
 	}
 	fprintf(stdout, "Accept connection, fd=%d, addr=%s, port=%hu\n",
 			conn_fd, addr_str, ntohs(cli_addr.sin_port));

 	/* Check if connected socket is non-blocking or blocking */
 	if (is_fd_nonblock(conn_fd))
 	{
 		fprintf(stdout, "Connected socket is non-blocking\n");
 	}
 	else
 	{
 		fprintf(stdout, "Connected socket is blocking\n");
 	}

 	return NULL;
 }


 int main(void)
 {
 	int ret_int;
 	pthread_t cli_tid, srv_tid;

 	fprintf(stdout, "Create server thread\n");
 	ret_int = pthread_create(&srv_tid, NULL, &srv, NULL);
 	if (ret_int != 0)
 	{
 		fprintf(stderr, "Error %s:%d: pthread_create() failed, %s\n", __FILE__, __LINE__, strerror(ret_int));
 		exit(EXIT_FAILURE);
 	}

 	sleep(1); /* Give server thread time to setup listening socket */

 	fprintf(stdout, "Create client thread\n");
 	ret_int = pthread_create(&cli_tid, NULL, &cli, NULL);
 	if (ret_int != 0)
 	{
 		fprintf(stderr, "Error %s:%d: pthread_create() failed, %s\n", __FILE__, __LINE__, strerror(ret_int));
 		exit(EXIT_FAILURE);
 	}


 	/* Wait for client to exit */
 	ret_int = pthread_join(cli_tid, NULL);
 	if (ret_int != 0)
 	{
 		fprintf(stderr, "Error %s:%d: pthread_join() failed, %s\n", __FILE__, __LINE__, strerror(ret_int));
 		exit(EXIT_FAILURE);
 	}

 	/* Wait for server to exit */
 	ret_int = pthread_join(srv_tid, NULL);
 	if (ret_int != 0)
 	{
 		fprintf(stderr, "Error %s:%d: pthread_join() failed, %s\n", __FILE__, __LINE__, strerror(ret_int));
 		exit(EXIT_FAILURE);
 	}

 }

 --Multipart=_Sat__26_Oct_2019_22_51_45_+0100_yuQIk.oe0XeU2Ov6--

From: Christos Zoulas <christos@zoulas.com>
To: gnats-bugs@netbsd.org
Cc: kern-bug-people@netbsd.org,
 gnats-admin@netbsd.org,
 netbsd-bugs@netbsd.org,
 cryintothebluesky@gmail.com
Subject: Re: kern/54650: Calling accept() with a non-blocking listening
 socket, returns a non-blocking connected socket, which is incorrect behaviour
Date: Sat, 26 Oct 2019 18:37:47 -0400

 Linux is unique in that behavior. All the BSDs (including MacOS) and =
 Solaris return a non-blocking socket.

 christos=

From: Martin Husemann <martin@duskware.de>
To: gnats-bugs@NetBSD.org
Cc: 
Subject: Re: kern/54650: Calling accept() with a non-blocking listening
 socket, returns a non-blocking connected socket, which is incorrect
 behaviour
Date: Sun, 27 Oct 2019 08:27:29 +0100

 On Sat, Oct 26, 2019 at 06:37:47PM -0400, Christos Zoulas wrote:
 > Linux is unique in that behavior. All the BSDs (including MacOS) and Solaris return a non-blocking socket.

 I think it is also documented in accept(4):

 	... creates a new socket with the same
      properties of s and allocates a new file descriptor for the socket.

 Where I read "same properties" as including the socket options.

 Martin

From: Sad Clouds <cryintothebluesky@gmail.com>
To: gnats-bugs@netbsd.org
Cc: Martin Husemann <martin@duskware.de>, kern-bug-people@netbsd.org,
 gnats-admin@netbsd.org, netbsd-bugs@netbsd.org
Subject: Re: kern/54650: Calling accept() with a non-blocking listening
 socket, returns a non-blocking connected socket, which is incorrect
 behaviour
Date: Sun, 27 Oct 2019 07:59:47 +0000

 On Sun, 27 Oct 2019 07:30:02 +0000 (UTC)
 Martin Husemann <martin@duskware.de> wrote:

 >  I think it is also documented in accept(4):
 >  
 >  	... creates a new socket with the same
 >       properties of s and allocates a new file descriptor for the
 > socket. 
 >  Where I read "same properties" as including the socket options.
 >  
 >  Martin
 >  

 OK, if that is the case, then please close this bug. Just had a look at
 OpenBSD accept() man page and it is much more clear there:

 "The accept() call extracts the first connection request on the queue of
 pending connections, creates a new socket with the same non-blocking
 I/O mode as s, and allocates a new file descriptor for the socket with
 the close-on-exec flag clear."

 The way Linux behaves makes more sense to me, just because the
 listening socket is non-blocking, does not mean I want all the accepted
 sockets to be also non-blocking. But it looks like another portability
 issue we have to deal with on different platforms. 

From: Jason Thorpe <thorpej@me.com>
To: Sad Clouds <cryintothebluesky@gmail.com>
Cc: gnats-bugs@netbsd.org,
 Martin Husemann <martin@duskware.de>,
 kern-bug-people@netbsd.org,
 gnats-admin@netbsd.org,
 netbsd-bugs@netbsd.org
Subject: Re: kern/54650: Calling accept() with a non-blocking listening
 socket, returns a non-blocking connected socket, which is incorrect behaviour
Date: Sun, 27 Oct 2019 06:20:26 -0700

 > On Oct 27, 2019, at 12:59 AM, Sad Clouds <cryintothebluesky@gmail.com> =
 wrote:
 >=20
 > On Sun, 27 Oct 2019 07:30:02 +0000 (UTC)
 > Martin Husemann <martin@duskware.de> wrote:
 >=20
 >> I think it is also documented in accept(4):
 >>=20
 >> 	... creates a new socket with the same
 >>      properties of s and allocates a new file descriptor for the
 >> socket.=20
 >> Where I read "same properties" as including the socket options.
 >>=20
 >> Martin
 >>=20

 There is a lot less text in the SUSv4 description of accept(2):

 <quote>
 The accept() function shall extract the first connection on the queue of =
 pending connections, create a new socket with the same socket type =
 protocol and address family as the specified socket, and allocate a new =
 file descriptor for that socket. The file descriptor shall be allocated =
 as described in File Descriptor Allocation.
 </quote>

 It does not explicitly allow the historic BSD behavior, nor does it =
 explicitly forbid it.

 >=20
 > OK, if that is the case, then please close this bug. Just had a look =
 at
 > OpenBSD accept() man page and it is much more clear there:
 >=20
 > "The accept() call extracts the first connection request on the queue =
 of
 > pending connections, creates a new socket with the same non-blocking
 > I/O mode as s, and allocates a new file descriptor for the socket with
 > the close-on-exec flag clear."
 >=20
 > The way Linux behaves makes more sense to me, just because the
 > listening socket is non-blocking, does not mean I want all the =
 accepted
 > sockets to be also non-blocking. But it looks like another portability
 > issue we have to deal with on different platforms.=20

 -- thorpej

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.43 2018/01/16 07:36:43 maya Exp $
$NetBSD: gnats_config.sh,v 1.9 2014/08/02 14:16:04 spz Exp $
Copyright © 1994-2017 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.