NetBSD Problem Report #22522
Received: (qmail 1529 invoked by uid 605); 18 Aug 2003 03:16:16 -0000
Message-Id: <200308180316.h7I3G3pv004465@edge.back-street.net>
Date: Mon, 18 Aug 2003 12:16:03 +0900 (JST)
From: Takahiro Kambe <taca@back-street.net>
Sender: gnats-bugs-owner@NetBSD.org
Reply-To: taca@back-street.net
To: gnats-bugs@gnats.netbsd.org
Subject: connect(2) may fail with EINVAL which isn't undocumented.
X-Send-Pr-Version: 3.95
>Number: 22522
>Notify-List: gson@gson.org, gutteridge
>Category: kern
>Synopsis: connect(2) may fail with EINVAL which isn't undocumented.
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Mon Aug 18 03:17:00 +0000 2003
>Closed-Date:
>Last-Modified: Sun Sep 27 10:50:01 +0000 2026
>Originator: Takahiro Kambe
>Release: NetBSD 1.6W
>Organization:
Takahiro Kambe
>Environment:
System: NetBSD edge.back-street.net 1.6W NetBSD 1.6W (CF-R1) #0: Tue Aug 12 22:19:46 JST 2003 taca@edge.back-street.net:/var/obj/sys/arch/i386/compile.i386/CF-R1 i386
Architecture: i386
Machine: i386
>Description:
connect(2) may fail with EINVAL when using non-blocking I/O, but it
isn't documented connect(2).
When using non-blocking I/O, connect(2) may return EINPROGRESS and
after that repeating connect(2) will is expected to return with::
1. Still in inprogress as EALREADY error.
2. Already connected as EISCONN error.
But when remote host refused connection, following connect(2)
may returns EINVAL. And in this case, real error can't fetch with
getsockopt(2).
>How-To-Repeat:
Here is test program and specify host and unused (not listened) port
as argument.
% cc test.c
% ./a.out 192.168.32.10 12345
connect to family = 2, port = 234, addr = 192.168.32.10
connect: Operation now in progress
getsockopt => Connection refused
connect to family = 2, port = 234, addr = 192.168.32.10
connect: Invalid argument
getsockopt => 0
Don't test with localhost.
-------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
static int connect_test(int, struct sockaddr_in *, int, int);
#define GETSOCKOPT_AFTER_EINPROGRESS 1
#define GETSOCKOPT_AFTER_EINVAL 1
static int exit_after_getsockopt = 0;
int
main(int argc, char **argv)
{
char *host, *port;
struct hostent *ht;
unsigned short p;
int fd, status, n;
struct sockaddr_in sin;
if (argc == 3) {
host = argv[1];
port = argv[2];
} else {
fputs("Need host and port as parameter.\n", stderr);
exit(1);
}
n = atoi(port);
if (n <= 0 || n > 65535) {
fputs("Invalid port number.\n", stderr);
exit(1);
}
p = n;
ht = gethostbyname(host);
if (ht == NULL) {
herror(host);
}
memset(&sin, '\0', sizeof sin);
sin.sin_family = AF_INET;
sin.sin_port = htons(p);
memcpy(&sin.sin_addr, ht->h_addr_list[0], ht->h_length);
fd = socket(PF_INET, SOCK_STREAM, 0);
status = connect_test(fd, &sin, sizeof sin, 0);
return(status);
}
static int
connect_test(fd, sockaddr, len, socks)
int fd;
struct sockaddr_in *sockaddr;
int len;
int socks;
{
int status;
int mode;
#if (GETSOCKOPT_AFTER_EINPROGRESS == 1) || (GETSOCKOPT_AFTER_EINVAL == 1)
int value;
#endif
mode = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, mode | O_NONBLOCK);
for (;;) {
printf("connect to family = %d, port = %d, addr = %s\n",
sockaddr->sin_family, ntohs(sockaddr->sin_port),
inet_ntoa(sockaddr->sin_addr));
status = connect(fd, (struct sockaddr *)sockaddr, len);
if (status < 0) {
perror("connect");
switch (errno) {
case EAGAIN:
case EINPROGRESS:
#if (GETSOCKOPT_AFTER_EINPROGRESS == 1)
len = sizeof(value);
getsockopt(fd, SOL_SOCKET, SO_ERROR, &value, &len);
printf("getsockopt => %s\n",
(value != 0) ? strerror(value): "0");
if (exit_after_getsockopt)
goto error;
#endif
continue;
case EISCONN:
status = 0;
break;
case EINVAL:
#if (GETSOCKOPT_AFTER_EINVAL == 1)
len = sizeof(value);
getsockopt(fd, SOL_SOCKET, SO_ERROR, &value, &len);
printf("getsockopt => %s\n",
(value != 0) ? strerror(value): "0");
if (exit_after_getsockopt)
goto error;
#endif
break;
default:
break;
}
}
#if (GETSOCKOPT_AFTER_EINPROGRESS == 1) || (GETSOCKOPT_AFTER_EINVAL == 1)
error: ;
#endif
fcntl(fd, F_SETFL, mode);
return status;
}
}
-------------------------------------------------------------------------
>Fix:
Though I don't know EINVAL is really valid errno for connect(2),
etheir modification will be needed.
o Document EINVAL to connect(2).
o Fix connect(2) not to return EINVAL.
>Release-Note:
>Audit-Trail:
State-Changed-From-To: open->closed
State-Changed-By: gutteridge@NetBSD.org
State-Changed-When: Sat, 26 Sep 2026 22:40:03 +0000
State-Changed-Why:
This was addressed in r. 1.29 of connect.2 by dholland@.
State-Changed-From-To: closed->open
State-Changed-By: riastradh@NetBSD.org
State-Changed-When: Sun, 27 Sep 2026 00:17:17 +0000
State-Changed-Why:
This was actually not really addressed. If you don't trash len (e.g.,
use a local variable `int optlen' for the socket option length), and if
you add a case for EALREADY to continue the loop, you hit two problems:
1. Eventually, connect() fails with EINVAL.
2. getsockopt(SO_ERROR) _does not_ return ECONNREFUSED after that.
connect to family = 2, port = 12345, addr = 192.168.1.1
connect: Operation now in progress
getsockopt => 0
connect to family = 2, port = 12345, addr = 192.168.1.1
connect: Operation already in progress
getsockopt => 0
...
connect to family = 2, port = 12345, addr = 192.168.1.1
connect: Operation already in progress
getsockopt => Connection refused
connect to family = 2, port = 12345, addr = 192.168.1.1
connect: Invalid argument
getsockopt => 0
See also:
PR kern/58874: nonblocking connect() and getsockname() error conditions
https://gnats.netbsd.org/58874
(OK, I guess we only need one of these PRs open, so we could
legitimately close the other as a duplicate.)
From: mlelstv@serpens.de (Michael van Elst)
To: gnats-bugs@netbsd.org
Cc:
Subject: Re: kern/22522 (connect(2) may fail with EINVAL which isn't undocumented.)
Date: Sun, 27 Sep 2026 10:46:31 -0000 (UTC)
riastradh@NetBSD.org writes:
>Synopsis: connect(2) may fail with EINVAL which isn't undocumented.
>This was actually not really addressed. If you don't trash len (e.g.,
>use a local variable `int optlen' for the socket option length), and if
>you add a case for EALREADY to continue the loop, you hit two problems:
>1. Eventually, connect() fails with EINVAL.
>2. getsockopt(SO_ERROR) _does not_ return ECONNREFUSED after that.
tcp_connect() and other protocol functions will return EINVAL
when the inpcb is detached. A refused connection will close the
TCP session and detach the inpcb.
In earlier code, this already happened in tcp_usrreq(), e.g.:
https://github.com/dank101/4.4BSD-Lite2/blob/master/sys/netinet/tcp_usrreq.c#L116
FreeBSD keeps the inpcb until the socket is freed and the protocol
is detached. Until then the tcpcb also exists and when in disconnected
state causes ECONNREFUSED to be returned.
>Unformatted:
Man pages rarely document all possible errno values for a system call.
In this case, the error EINVAL comes from calling connect() with bad
arguments, because the test loop trashes the len parameter.
There are other errors that connect() may return, like
ENOBUFS or EAFNOSUPPORT. All of them are documented on
intro(2).
(Contact us)
$NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.