diff options
author | Alon Zakai <alonzakai@gmail.com> | 2013-03-25 18:03:02 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2013-03-25 18:03:02 -0700 |
commit | 926a1f21a06442dbcabfce97fef2257641b5c13c (patch) | |
tree | f257237389f7052518dfb6229058a43eac2d716d | |
parent | 5cedd415bd1b379f96af4e2487b75fecc407759a (diff) | |
parent | 1fecc4b3072c6a0cfef5c629f50cbaa647ea98b1 (diff) |
Merge pull request #898 from MichaelRiss/selectFix
select function: return error condition when network connection fails
-rw-r--r-- | src/library.js | 19 | ||||
-rwxr-xr-x | tests/runner.py | 65 | ||||
-rw-r--r-- | tests/websockets_select.c | 95 | ||||
-rw-r--r-- | tests/websockets_select_server_closes_connection.c | 126 | ||||
-rw-r--r-- | tests/websockets_select_server_closes_connection_rw.c | 213 |
5 files changed, 516 insertions, 2 deletions
diff --git a/src/library.js b/src/library.js index 166a015f..b63ac955 100644 --- a/src/library.js +++ b/src/library.js @@ -7297,11 +7297,17 @@ LibraryManager.library = { // exceptfds not supported // timeout is always 0 - fully async assert(!exceptfds); + + var errorCondition = 0; function canRead(info) { // make sure hasData exists. // we do create it when the socket is connected, // but other implementations may create it lazily + if ((info.socket.readyState == WebSocket.CLOSING || info.socket.readyState == WebSocket.CLOSED) && info.inQueue.length == 0) { + errorCondition = -1; + return false; + } return info.hasData && info.hasData(); } @@ -7309,6 +7315,10 @@ LibraryManager.library = { // make sure socket exists. // we do create it when the socket is connected, // but other implementations may create it lazily + if ((info.socket.readyState == WebSocket.CLOSING || info.socket.readyState == WebSocket.CLOSED)) { + errorCondition = -1; + return false; + } return info.socket && (info.socket.readyState == info.socket.OPEN); } @@ -7340,8 +7350,13 @@ LibraryManager.library = { return bitsSet; } - return checkfds(nfds, readfds, canRead) - + checkfds(nfds, writefds, canWrite); + var totalHandles = checkfds(nfds, readfds, canRead) + checkfds(nfds, writefds, canWrite); + if (errorCondition) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } else { + return totalHandles; + } }, // pty.h diff --git a/tests/runner.py b/tests/runner.py index b0338855..a3c74f03 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -11862,6 +11862,71 @@ elif 'browser' in str(sys.argv): finally: self.clean_pids() + def test_websockets_select_server_down(self): + def closedServer(q): + import socket + + q.put(None) # No sub-process to start + ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock.bind(("127.0.0.1", 8994)) + try: + with self.WebsockHarness(8994, closedServer): + self.btest('websockets_select.c', expected='266') + finally: + self.clean_pids() + + def test_websockets_select_server_closes_connection(self): + def closingServer(q): + import socket + + q.put(None) # No sub-process to start + ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock.bind(("127.0.0.1", 8994)) + ssock.listen(2) + while True: + csock, addr = ssock.accept() + print "Connection from %s" % repr(addr) + csock.send("1234567") + csock.close() + + try: + with self.WebsockHarness(8994, closingServer): + self.btest('websockets_select_server_closes_connection.c', expected='266') + finally: + self.clean_pids() + + def test_websockets_select_server_closes_connection_rw(self): + def closingServer_rw(q): + import socket + + q.put(None) # No sub-process to start + ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock.bind(("127.0.0.1", 8998)) + ssock.listen(2) + while True: + csock, addr = ssock.accept() + print "Connection from %s" % repr(addr) + readArray = bytearray(10) + #readBuffer = buffer(readArray) + bytesRead = 0 + # Let the client start to write data + while (bytesRead < 10): + (readBytes, address) = csock.recvfrom_into( readArray, 10 ) + bytesRead += readBytes + print "server: 10 bytes read" + # Now we write a message on our own ... + csock.send("0123456789") + print "server: 10 bytes written" + # And immediately close the connection + csock.close() + print "server: connection closed" + + try: + with self.WebsockHarness(8998, closingServer_rw): + self.btest('websockets_select_server_closes_connection_rw.c', expected='266') + finally: + self.clean_pids() + def test_enet(self): try_delete(self.in_dir('enet')) shutil.copytree(path_from_root('tests', 'enet'), self.in_dir('enet')) diff --git a/tests/websockets_select.c b/tests/websockets_select.c new file mode 100644 index 00000000..b8ab9091 --- /dev/null +++ b/tests/websockets_select.c @@ -0,0 +1,95 @@ +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <assert.h> +#if EMSCRIPTEN +#include <emscripten.h> +#endif + +#define EXPECTED_BYTES 5 + +int SocketFD; + +int done = 0; + +void iter(void *arg) { + fd_set sett; + FD_ZERO(&sett); + FD_SET(SocketFD, &sett); + + // The error should happen here + int select_says_yes = select(64, &sett, NULL, NULL, NULL); + if( select_says_yes == -1 ){ + printf( "Connection to websocket server failed as expected." ); + perror( "Error message" ); + int result = 266; + REPORT_RESULT(); + done = 1; + } + + assert(!select_says_yes); + done = 1; +} + +// This is for testing a websocket connection to a closed server port. +// The connect call will succeed (due to the asynchronous websocket +// behavior) but once the underlying websocket system realized that +// the connection cannot be established, the next select call will fail. +int main(void) +{ + struct sockaddr_in stSockAddr; + int Res; + SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (-1 == SocketFD) + { + perror("cannot create socket"); + exit(EXIT_FAILURE); + } + + memset(&stSockAddr, 0, sizeof(stSockAddr)); + + stSockAddr.sin_family = AF_INET; + stSockAddr.sin_port = htons( +#if EMSCRIPTEN + 8995 +#else + 8994 +#endif + ); + Res = inet_pton(AF_INET, "127.0.0.1", &stSockAddr.sin_addr); + + if (0 > Res) { + perror("error: first parameter is not a valid address family"); + close(SocketFD); + exit(EXIT_FAILURE); + } else if (0 == Res) { + perror("char string (second parameter does not contain valid ipaddress)"); + close(SocketFD); + exit(EXIT_FAILURE); + } + + // This call should succeed (even if the server port is closed) + if (-1 == connect(SocketFD, (struct sockaddr *)&stSockAddr, sizeof(stSockAddr))) { + perror("connect failed"); + close(SocketFD); + exit(EXIT_FAILURE); + + } + +#if EMSCRIPTEN + emscripten_set_main_loop(iter, 0, 0); +#else + while (!done) iter(NULL); +#endif + + return EXIT_SUCCESS; +} + diff --git a/tests/websockets_select_server_closes_connection.c b/tests/websockets_select_server_closes_connection.c new file mode 100644 index 00000000..6ce6d311 --- /dev/null +++ b/tests/websockets_select_server_closes_connection.c @@ -0,0 +1,126 @@ +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <assert.h> +#if EMSCRIPTEN +#include <emscripten.h> +#endif + +#define EXPECTED_BYTES 5 + +int SocketFD; + +int done = 0; + +void iter(void *arg) { + static char readbuf[1024]; + static int readPos = 0; + + fd_set sett; + FD_ZERO(&sett); + FD_SET(SocketFD, &sett); + + if( readPos < 7 ){ + // still reading + int selectRes = select(64, &sett, NULL, NULL, NULL); + + if( selectRes == 0 ) + return; + + if( selectRes == -1 ){ + perror( "Connection to websocket server failed" ); + exit(EXIT_FAILURE); + } + if( selectRes > 0 ){ + assert(FD_ISSET(SocketFD, &sett)); + + int bytesRead = recv( SocketFD, readbuf+readPos, 7-readPos, 0 ); + readPos += bytesRead; + } + } else { + // here the server should have closed the connection + int selectRes = select(64, &sett, NULL, NULL, NULL); + + if( selectRes == 0 ) + return; + + if( selectRes == -1 ){ + perror( "Connection to websocket server failed as expected" ); + int result = 266; + REPORT_RESULT(); + emscripten_cancel_main_loop(); + done = 1; + } + + if( selectRes > 0 ){ + printf( "Error: socket should not show up on select call anymore.\n" ); + exit(EXIT_FAILURE); + } + } + + return; +} + +// Scenario: the server sends data and closes the connection after 7 bytes. +// This test should provoke the situation in which the underlying +// tcp connection has been torn down already but there is still data +// in the inQueue. The select call has to succeed as long the queue +// still contains data and only then start to throw errors. +int main(void) +{ + struct sockaddr_in stSockAddr; + int Res; + SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (-1 == SocketFD) + { + perror("cannot create socket"); + exit(EXIT_FAILURE); + } + + memset(&stSockAddr, 0, sizeof(stSockAddr)); + + stSockAddr.sin_family = AF_INET; + stSockAddr.sin_port = htons( +#if EMSCRIPTEN + 8995 +#else + 8994 +#endif + ); + Res = inet_pton(AF_INET, "127.0.0.1", &stSockAddr.sin_addr); + + if (0 > Res) { + perror("error: first parameter is not a valid address family"); + close(SocketFD); + exit(EXIT_FAILURE); + } else if (0 == Res) { + perror("char string (second parameter does not contain valid ipaddress)"); + close(SocketFD); + exit(EXIT_FAILURE); + } + + // This call should succeed (even if the server port is closed) + if (-1 == connect(SocketFD, (struct sockaddr *)&stSockAddr, sizeof(stSockAddr))) { + perror("connect failed"); + close(SocketFD); + exit(EXIT_FAILURE); + + } + +#if EMSCRIPTEN + emscripten_set_main_loop(iter, 0, 0); +#else + while (!done) iter(NULL); +#endif + + return EXIT_SUCCESS; +} + diff --git a/tests/websockets_select_server_closes_connection_rw.c b/tests/websockets_select_server_closes_connection_rw.c new file mode 100644 index 00000000..dd0913bf --- /dev/null +++ b/tests/websockets_select_server_closes_connection_rw.c @@ -0,0 +1,213 @@ +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <assert.h> +#if EMSCRIPTEN +#include <emscripten.h> +#endif + +#define EXPECTED_BYTES 5 + +int SocketFD; + +int done = 0; + +void iter(void *arg) { + static int state = 0; + static char writebuf[] = "01234567890123456789"; + static int writePos = 0; + static char readbuf[1024]; + static int readPos = 0; + int selectRes; + ssize_t transferAmount; + fd_set sett; + + + switch( state ){ + case 0: + // writing 10 bytes to the server + + // the socket in the read file descriptors has to result in a 0 return value + // because the connection exists, but there is no data yet + FD_ZERO( &sett ); + FD_SET(SocketFD, &sett); + selectRes = select(64, &sett, NULL, NULL, NULL); + if( selectRes != 0 ){ + printf( "case 0: read select != 0\n" ); + exit(EXIT_FAILURE); + } + + // the socket in the write file descriptors has to result in either a 0 or 1 + // the connection either is setting up or is established and writing is possible + FD_ZERO( &sett ); + FD_SET(SocketFD, &sett); + selectRes = select(64, NULL, &sett, NULL, NULL); + if( selectRes == -1 ){ + printf( "case 0: write select == -1\n" ); + exit(EXIT_FAILURE); + } + if( selectRes == 0 ){ + return; + } + + // send a single byte + transferAmount = send( SocketFD, writebuf+writePos, 1, 0 ); + writePos += transferAmount; + + // after 10 bytes switch to next state + if( writePos >= 10 ){ + state = 1; + } + break; + + case 1: + // wait until we can read one byte to make sure the server + // has sent the data and then closed the connection + FD_ZERO( &sett ); + FD_SET(SocketFD, &sett); + selectRes = select(64, &sett, NULL, NULL, NULL); + if( selectRes == -1 ){ + printf( "case 1: read selectRes == -1\n" ); + exit(EXIT_FAILURE); + } + if( selectRes == 0 ) + return; + + // read a single byte + transferAmount = recv( SocketFD, readbuf+readPos, 1, 0 ); + readPos += transferAmount; + + // if successfully reading 1 byte, switch to next state + if( readPos >= 1 ){ + state = 2; + } + break; + + case 2: + // calling select with the socket in the write file descriptors has + // to fail because the tcp network connection is already down + FD_ZERO( &sett ); + FD_SET(SocketFD, &sett); + selectRes = select(64, NULL, &sett, NULL, NULL); + if( selectRes != -1 ){ + printf( "case 2: write selectRes != -1\n" ); + exit(EXIT_FAILURE); + } + + // calling select with the socket in the read file descriptors + // has to succeed because there is still data in the inQueue + FD_ZERO( &sett ); + FD_SET(SocketFD, &sett); + selectRes = select(64, &sett, NULL, NULL, NULL); + if( selectRes != 1 ){ + printf( "case 2: read selectRes != 1\n" ); + exit(EXIT_FAILURE); + } + if( selectRes == 0 ) + return; + + // read a single byte + transferAmount = recv( SocketFD, readbuf+readPos, 1, 0 ); + readPos += transferAmount; + + // with 10 bytes read the inQueue is empty => switch state + if( readPos >= 10 ){ + state = 3; + } + break; + + case 3: + // calling select with the socket in the read file descriptors + // now also has to fail as the inQueue is empty + FD_ZERO( &sett ); + FD_SET(SocketFD, &sett); + selectRes = select(64, &sett, NULL, NULL, NULL); + if( selectRes != -1 ){ + printf( "case 3: read selectRes != -1\n" ); + exit(EXIT_FAILURE); + } + + // report back success, the 266 is just an arbitrary value without + // deeper meaning + int result = 266; + REPORT_RESULT(); + break; + + default: + printf( "Impossible state!\n" ); + exit(EXIT_FAILURE); + break; + } + + return; +} + +// This test checks for an intended asymmetry in the behavior of the select function. +// Scenario: the client sends data to the server. After 10 received bytes the +// server sends 10 bytes on its own and immediately afterwards closes the connection. +// This mimics a typical connect-request-response-disconnect situation. +// After the server closed the connection select calls with the socket in the write file +// descriptors have to fail as the tcp connection is already down and there is no way +// anymore to send data. +// Select calls with the socket in the read file descriptor list still have to succeed +// as there are still 10 bytes to read from the inQueue. So, for the same socket the +// select call behaves differently depending on whether the socket is listed in the +// read or write file descriptors. +int main(void) +{ + struct sockaddr_in stSockAddr; + int Res; + SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (-1 == SocketFD) + { + perror("cannot create socket"); + exit(EXIT_FAILURE); + } + + memset(&stSockAddr, 0, sizeof(stSockAddr)); + + stSockAddr.sin_family = AF_INET; + stSockAddr.sin_port = htons( +#if EMSCRIPTEN + 8999 +#else + 8998 +#endif + ); + Res = inet_pton(AF_INET, "127.0.0.1", &stSockAddr.sin_addr); + + if (0 > Res) { + perror("error: first parameter is not a valid address family"); + close(SocketFD); + exit(EXIT_FAILURE); + } else if (0 == Res) { + perror("char string (second parameter does not contain valid ipaddress)"); + close(SocketFD); + exit(EXIT_FAILURE); + } + + // This call should succeed (even if the server port is closed) + if (-1 == connect(SocketFD, (struct sockaddr *)&stSockAddr, sizeof(stSockAddr))) { + perror("connect failed"); + close(SocketFD); + exit(EXIT_FAILURE); + + } + +#if EMSCRIPTEN + emscripten_set_main_loop(iter, 0, 0); +#else + while (!done) iter(NULL); +#endif + + return EXIT_SUCCESS; +} + |