Subject: kern/7072: linux emulation gets non-blocking connect wrong..
To: None <gnats-bugs@gnats.netbsd.org>
From: Bill Sommerfeld <sommerfeld@orchard.arlington.ma.us>
List: netbsd-bugs
Date: 03/02/1999 08:22:43
>Number:         7072
>Category:       kern
>Synopsis:       linux emulation gets non-blocking connect wrong..
>Confidential:   no
>Severity:       serious
>Priority:       low
>Responsible:    kern-bug-people (Kernel Bug People)
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Tue Mar  2 00:35:00 1999
>Last-Modified:
>Originator:     Bill Sommerfeld
>Organization:
	
>Release:        19990302
>Environment:
	
System: NetBSD orchard.arlington.ma.us 1.3J NetBSD 1.3J (ORCHARDII) #19: Tue Mar 2 02:28:15 EST 1999 root@:/d4/vol/NetBSD-current/src/sys/arch/i386/compile/ORCHARDII i386


>Description:
	linux non-blocking connect has you call connect() multiple
	times until one eventually succeeds.

	the same native syscall pattern results in connect() failing with
	EISCONN instead of succeeding.
	
>How-To-Repeat:
	try to run linux JVM binaries built on green threads; when it
	does a non-blocking connect(), the second attempt returns EISCONN,
	which the JVM appears to treat as a fatal error.

>Fix:
	Doing it "right" is hard, because linux does the 
	socket state transition from CONNECTING to CONNECTED state
	synchronously in the connect() syscall, whereas BSD does it
	asynchronously.

	To do it right we'd need to stash a state bit or something in
	the socket structure..

	The following patch kludges it somewhat (it will return
	spurious "successes" on repeated connects when a connected
	non-blocking socket is reconnected); however this will
	*probably* not screw up most apps.

Index: sys/compat/linux/common/linux_socket.c
===================================================================
RCS file: /cvsroot/src/sys/compat/linux/common/linux_socket.c,v
retrieving revision 1.18
diff -u -r1.18 linux_socket.c
--- linux_socket.c	1998/10/04 00:02:43	1.18
+++ linux_socket.c	1999/03/02 07:54:03
@@ -60,6 +60,7 @@
 #include <sys/proc.h>
 #include <sys/vnode.h>
 #include <sys/device.h>
+#include <sys/protosw.h> 
 
 #include <sys/syscallargs.h>
 
@@ -490,3 +491,46 @@
 	SCARG(&ia, data) = SCARG(uap, data);
 	return sys_ioctl(p, &ia, retval);
 }
+
+int
+linux_sys_connect(p, v, retval)
+	register struct proc *p;
+	void *v;
+	register_t *retval;
+{
+	int error;
+
+	register struct sys_connect_args /* {
+		syscallarg(int) s;
+		syscallarg(const struct sockaddr *) name;
+		syscallarg(unsigned int) namelen;
+	} */ *uap = v;
+	
+	error = sys_connect (p, v, retval);
+
+	if (error == EISCONN) {
+		struct file *fp;
+		register struct socket *so;
+		int s, state, prflags;
+		
+	    	if (getsock(p->p_fd, SCARG(uap, s), &fp)!= 0)
+		    	return EISCONN;
+
+		s = splsoftnet();
+		so = (struct socket *)fp->f_data;
+		state = so->so_state;
+		prflags = so->so_proto->pr_flags;
+		splx(s);
+		/*
+		 * We should only let this call succeed once per
+		 * non-blocking connect; however we don't have
+		 * a convenient place to keep that state..
+		 */
+		if ((state & SS_NBIO) && (state & SS_ISCONNECTED) &&
+		    (prflags & PR_CONNREQUIRED))
+			return 0;
+	}
+
+	return error;
+}
+
Index: sys/compat/linux/common/linux_socketcall.c
===================================================================
RCS file: /cvsroot/src/sys/compat/linux/common/linux_socketcall.c,v
retrieving revision 1.18
diff -u -r1.18 linux_socketcall.c
--- linux_socketcall.c	1998/10/04 00:02:44	1.18
+++ linux_socketcall.c	1999/03/02 07:54:03
@@ -130,7 +130,7 @@
 	case LINUX_SYS_bind:
 		return sys_bind(p, (void *)&lda, retval);
 	case LINUX_SYS_connect:
-		return sys_connect(p, (void *)&lda, retval);
+		return linux_sys_connect(p, (void *)&lda, retval);
 	case LINUX_SYS_listen:
 		return sys_listen(p, (void *)&lda, retval);
 	case LINUX_SYS_accept:
Index: sys/compat/linux/common/linux_socketcall.h
===================================================================
RCS file: /cvsroot/src/sys/compat/linux/common/linux_socketcall.h,v
retrieving revision 1.4
diff -u -r1.4 linux_socketcall.h
--- linux_socketcall.h	1998/10/04 00:02:44	1.4
+++ linux_socketcall.h	1999/03/02 07:54:03
@@ -235,6 +235,7 @@
 int linux_sys_recvfrom __P((struct proc *, void *, register_t *));
 int linux_sys_setsockopt __P((struct proc *, void *, register_t *));
 int linux_sys_getsockopt __P((struct proc *, void *, register_t *));
+int linux_sys_connect __P((struct proc *, void *, register_t *));
 __END_DECLS
 # endif /* !_KERNEL */
 
>Audit-Trail:
>Unformatted: