NetBSD Problem Report #56346

From www@netbsd.org  Mon Aug  2 07:52:28 2021
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.3 with cipher TLS_AES_256_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 84FE01A921F
	for <gnats-bugs@gnats.NetBSD.org>; Mon,  2 Aug 2021 07:52:28 +0000 (UTC)
Message-Id: <20210802075227.857F61A923A@mollari.NetBSD.org>
Date: Mon,  2 Aug 2021 07:52:27 +0000 (UTC)
From: szilard.parrag@gmail.com
Reply-To: szilard.parrag@gmail.com
To: gnats-bugs@NetBSD.org
Subject: Listening socket instantly closes if TCP_KEEPALIVE options are set on the server side
X-Send-Pr-Version: www-1.0

>Number:         56346
>Category:       kern
>Synopsis:       Listening socket instantly closes if TCP_KEEPALIVE options are set on the server side
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Mon Aug 02 07:55:00 +0000 2021
>Last-Modified:  Mon Aug 02 23:05:01 +0000 2021
>Originator:     Szilard Parrag
>Release:        9.2
>Organization:
>Environment:
NetBSD localhost 9.2 NetBSD 9.2 (GENERIC) #0: Wed May 12 13:15:55 UTC 2021  mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64

>Description:
When exectuing the following code the listening socket closes after setting TCP_KEEPALIVE options. This does not happen on Linux, as the values are inherited by the clients/peers.
>How-To-Repeat:
Run the following program:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <netinet/tcp.h>

void setsockopt_or_fail(int socket, int level, int options_name, const void *option_value, socklen_t option_len){
        int retval = setsockopt(socket, level, options_name, option_value, option_len); 
        if(retval < 0){
                perror("setter failed");
                exit(1);
        }
}


int main(){

        //socket prep
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in server = {0};
        server.sin_addr.s_addr = INADDR_ANY;
        server.sin_family = AF_INET;
        server.sin_port = htons(4442);


        int on = 1;
        setsockopt_or_fail(sockfd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
        unsigned int a = 10;
        setsockopt_or_fail(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &a, sizeof(a));
        a = 3;
        setsockopt_or_fail(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &a, sizeof(a));
        a = 1;
        setsockopt_or_fail(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &a, sizeof(a));

        //bind
        if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0){
                perror("bind error");
                exit(1);
        }

        //listen

        if(listen(sockfd, 10) < 0){
                perror("listen error");
                exit(1);
        }
        printf("we are listening\n");

        // accept
        for(;;){
                int clientfd = accept(sockfd, NULL, NULL);
                if(clientfd < 0){
                        perror("error during accept");
                        exit(1);
                }
                printf("accepted\n");
                char buff[50];
                while(1){
                        int retval = read(clientfd, &buff, sizeof(buff));

                        if(retval == 0){
                                printf("Connection has finished\n");
                                break;
                        }
                        else if(retval < 0){
                                perror("error reading socket");
                                exit(1);
                        }
                }
                close(clientfd);
        }
        close(sockfd);
}

>Fix:

>Audit-Trail:
From: Robert Elz <kre@munnari.OZ.AU>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: kern/56346: Listening socket instantly closes if TCP_KEEPALIVE options are set on the server side
Date: Mon, 02 Aug 2021 22:32:26 +0700

     Date:        Mon,  2 Aug 2021 07:55:01 +0000 (UTC)
     From:        szilard.parrag@gmail.com
     Message-ID:  <20210802075501.0B90F1A923B@mollari.NetBSD.org>


 For accept() POSIX is going to say:
 	It is implementation-defined which socket options, if any, on
 	the accepted socket will have a default value determined by a
 	value previously customized by setsockopt( ) on socket, rather than
 	the default value used for other new sockets.

 [...]

 APPLICATION USAGE
 [...]
             Many socket options are described as having implementation-defined
 	    default values, which may differ according to the protocol in use
 	    by the socket. Existing practice differs on whether socket options
 	    such as SO_SNDBUF that were customized on the original listening
 	    socket will impact the corresponding option on the newly returned
 	    socket. Implementations are permitted to allow inheritance of
 	    customized settings where it makes sense, although the most portable
 	    approach for applications is to limit setsockopt( ) customizations
 	    to only the accepted socket.

 ... in the next version.   The current standard just says "a new socket..."
 with no option inheritance at all, I believe the change was made to allow
 what linux does to be acceptable ... but code that takes advantage of that
 is not portable, and cannot be expected to work on different systems
 (what "implementation-defined" means).

 But that's not the ultimate cause of the problem, of setsockopt() POSIX
 says:
 	The setsockopt( ) function shall set the option specified by the
 	option_name argument, at the protocol level specified by the level
 	argument, to the value pointed to by the option_value argument for
 	the socket associated with the file descriptor specified by the
 	socket argument.

 If you turn on keepalive on a listening socket (an insane thing to do)
 that option applies to that socket (and is allowed to be inherited by
 accept()'d sockets, though that's not guaranteed).   But unless something
 is replying to the keepalive packets on the listening socket (and what could
 that possibly be, it is not connected anywhere) that socket will be shut
 down just as any other socket which fails to successfully keepalive.

 That's what is happening to your test program.

 If linux is not doing that it is broken.

 There is no bug here, nothing to fix (except the code assuming this behaviour).

 kre

 ps: it does not close "instantly", it closes after the keepalive fails, which
 for me typically takes 0.5 seconds for that test program.   All the sockets
 here are "server side", the client would be the code that connects to port
 4442, and there's none of that here.


From: RVP <rvp@SDF.ORG>
To: gnats-bugs@netbsd.org
Cc: szilard.parrag@gmail.com, Robert Elz <kre@munnari.OZ.AU>
Subject: Re: kern/56346: Listening socket instantly closes if TCP_KEEPALIVE
 options are set on the server side
Date: Mon, 2 Aug 2021 23:02:50 +0000 (UTC)

 Some observations:

 1. As kre@ explained, setting keepalive parameters on a listening
     socket is decidedly odd. You set them only after the connection
     is established.

 2. The code seems to work of you don't set TCP_KEEPINTVL.

 3. The code works unchanged on FreeBSD. Probably because FreeBSD
     doesn't set a TCP_KEEPIDLE timer for listening sockets. Maybe
     NetBSD should do the same in change_keepalive().

 4. There is a typo in tcp_usrreq.c:tcp_ctloutput() (-HEAD & 9.x):

     case PRCO_GETOPT:
     ...
             case TCP_KEEPINIT:
             optval = tp->t_keepcnt;

    That should be `t_keepinit'. Someone please fix this.

 -RVP

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.46 2020/01/03 16:35:01 leot Exp $
$NetBSD: gnats_config.sh,v 1.9 2014/08/02 14:16:04 spz Exp $
Copyright © 1994-2020 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.