kern/129169: Linux Emulation ENOTCONN error using non-blocking TCP

Steven Hartland steven.hartland at multiplay.co.uk
Tue Nov 25 03:00:14 PST 2008


>Number:         129169
>Category:       kern
>Synopsis:       Linux Emulation ENOTCONN error using non-blocking TCP
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue Nov 25 11:00:13 UTC 2008
>Closed-Date:
>Last-Modified:
>Originator:     Steven Hartland
>Release:        7.0 RELEASE
>Organization:
Multiplay
>Environment:
FreeBSD loncore0.multiplay.co.uk 7.0-RELEASE-p3 FreeBSD 7.0-RELEASE-p3 #8: Sat Nov 22 18:46:41 GMT 2008     root at loncore0.multiplay.co.uk:/usr/src/sys/amd64/compile/MULTIPLAY  amd64
>Description:
In the Linux ABI a send() in a socket that is set non-blocking after a connect can produce ENOTCONN. This is caused by the underlying FreeBSD kernel call sendto() which checks for a connected end point, as this is still in progress, due to socket being non-blocking.

The fix is to check this condition in the Linux ABI and instead return EAGAIN which is what linux applications are expecting.

N.B. This currently breaks running Call of Duty World at War servers, specifically the server fails to authenticate with the global master server.
>How-To-Repeat:
In a linux application
1. Create a tcp socket
2. Set socket non-blocking
3. Connect to an end point
4. Perform a non-blocking send
5. ENOTCONN is returned.
>Fix:
Apply the attached patch or derivative there of.

Patch attached with submission follows:

--- linux_socket.c.orig	2008-11-22 18:26:27.000000000 +0000
+++ linux_socket.c	2008-11-22 18:44:56.000000000 +0000
@@ -878,4 +878,6 @@
 	} */ bsd_args;
 	int error;
+	struct socket *so;
+	u_int fflag;
 
 	if ((error = copyin(args, &linux_args, sizeof(linux_args))))
@@ -888,5 +890,26 @@
 	bsd_args.to = NULL;
 	bsd_args.tolen = 0;
-	return sendto(td, &bsd_args);
+	error = sendto(td, &bsd_args);
+	if ( ENOTCONN == error )
+	{
+		/*
+		 * Linux doesn't return ENOTCONN for non-blocking sockets.
+		 * Instead it returns the EAGAIN.
+		 *
+		 * XXXRW: Instead of using fgetsock(), check that it is a
+		 * socket and use the file descriptor reference instead of
+		 * creating a new one.
+		 */
+		error = fgetsock(td, linux_args.s, &so, &fflag);
+		if ( 0 == error )
+		{
+			if ( fflag & FNONBLOCK )
+			{
+				error = EAGAIN;
+			}
+			fputsock(so);
+		}
+    }
+    return (error);
 }
 


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list