Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Socket.read(...) unexpectedly returns 0 #79

Open
pitfield opened this issue Jul 20, 2019 · 3 comments
Open

Socket.read(...) unexpectedly returns 0 #79

pitfield opened this issue Jul 20, 2019 · 3 comments

Comments

@pitfield
Copy link

pitfield commented Jul 20, 2019

On a "blocking" client socket, Socket.read(into data: inout Data) sometimes unexpectedly returns 0, even when the remote connection has not closed (Socket.remoteConnectionClosed == false).

This only reproduces on macOS.

This only happens using SSL/TLS (BlueSSLService).

Environment

  • Client on macOS (10.14.5), using Swift 5.0.1 (Xcode 10.2.1), BlueSocket 1.0.47, BlueSSLService 1.0.47. (The client software is a Swift database driver for the PostgreSQL database.)

  • Server is a PostgreSQL database server (PostgreSQL 10.9) running on Ubuntu 18.04 LTS.

The issue reproduces in multiple client and server environments. The issue reproduces more consistently on "distant" servers (e.g. running on AWS EC2 or Linode) than on "nearby" servers (e.g. on the same LAN as the client).

(I tried to reproduce the problem with the client running against a simple standalone Swift server, but was not able to reproduce the problem that way. So I'm not able to provide code for a standalone reproducible test case.)

Analysis

The Xcode debugger was used to analyze why Socket.read(...) is returning 0.

In SSLService.swift, the function sslReadCallback(...) is the callback function for SSLRead. In line 1406, sslReadCallback(...) invokes the read(...) system call, which sometimes returns fewer bytes than requested.

(lldb) po bytesRequested
8216

(lldb) po bytesRead
4339

When this occurs, sslReadCallback(...) returns errSSLWouldBlock. When control returns to the SSLRead call site (SSLService.swift, line 687), we see:

(lldb) po status
-9803

(lldb) po errSSLWouldBlock
-9803

(lldb) po processed
0

(I suspect processed == 0 because SSLRead requires its callback to return all requested bytes before SSLRead decrypts the received message.)

SSLService.recv(...) then returns -1 with errno set to EWOULDBLOCK (line 695).

Socket.readDataIntoStore() detects this error and returns 0 (Socket.swift, line 3589).

Socket.read(...) then returns 0 (line 2772).

Potential fix

In the sslReadCallback(...) function, I changed the "read" system call to request MSG_WAITALL.

Line 1406 changed from:

    let bytesRead = read(socketfd, data, bytesRequested)

to:

    let bytesRead = recv(socketfd, data, bytesRequested, MSG_WAITALL)

MSG_WAITALL forces the socket read to block until the requested number of bytes are available (or an error occurs).

This one-line change appears to resolve this issue. However, I don't have sufficient familiarity with BlueSocket/BlueSSLService internals (or low level network programming, for that matter) to assess whether it would cause other problems.

Please let me know if there is other information I can provide.

[edited to correct typo]

@NullandKale
Copy link

This is still an issue and the potential fix still works, just ran into it attempting to read 90k bytes from an android client.

@dannys42
Copy link
Contributor

Are you sure the socket is blocking? EWOULDBLOCK should only return for a non-blocking socket.

@pitfield
Copy link
Author

Yes, the socket is definitely configured to be blocking (isBlocking in Socket.swift).

EWOULDBLOCK is being raised in the SSL layer upon reading at least one byte but fewer bytes than requested. Excerpts from SSLService.swift:

	private func sslReadCallback(...) -> OSStatus {
		...
		// Read the data from the socket...
		let bytesRead = read(socketfd, data, bytesRequested)
		if bytesRead > 0 {
			
			dataLength.initialize(to: bytesRead)
			if bytesRequested > bytesRead {
				
				return OSStatus(errSSLWouldBlock)
				
			} else {
				
				return noErr
			}
		...
	}

	public func recv(...) throws -> Int {
		...
				if status == errSSLWouldBlock {
				
					errno = EWOULDBLOCK
					return -1
				}
		...
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants