Subject: kern/22522: connect(2) may fail with EINVAL which isn't undocumented.
To: None <gnats-bugs@gnats.netbsd.org>
From: Takahiro Kambe <taca@back-street.net>
List: netbsd-bugs
Date: 08/18/2003 12:16:03
>Number:         22522
>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 UTC 2003
>Closed-Date:
>Last-Modified:
>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:
>Unformatted: