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
(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.