From 19755ae8971d97cfc092add10ceed6ab40f011bd Mon Sep 17 00:00:00 2001 From: max Date: Fri, 27 May 2011 18:36:26 +0000 Subject: Fix [socket -async] for DNS names with more than one address --- ChangeLog | 8 ++ tests/socket.test | 22 +++++ unix/tclUnixSock.c | 231 ++++++++++++++++++++++++++++++----------------------- 3 files changed, 163 insertions(+), 98 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5c4c72a..f7b11cc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2011-05-27 Reinhard Max + + * unix/tclUnixSock.c: Fix [socket -async], so that all addresses + returned by getaddrinfo() are tried, not just the first one. This + requires the event loop to be running while the async connection + is in progress. ***POTENTIAL INCOMPATIBILITY*** + * tests/socket.test: Add a test for the above. + 2011-05-25 Don Porter * library/msgcat/msgcat.tcl: Bump to msgcat 1.4.4. diff --git a/tests/socket.test b/tests/socket.test index 83bad09..1bb9b79 100644 --- a/tests/socket.test +++ b/tests/socket.test @@ -1699,6 +1699,28 @@ if {$remoteProcChan ne ""} { catch {close $commandSocket} catch {close $remoteProcChan} } +unset ::tcl::unsupported::socketAF +test socket-14.0 {async when server only listens on one address family} \ + -constraints [list socket supported_any] \ + -setup { + proc accept {s a p} { + global x + puts $s bye + close $s + set x ok + } + set server [socket -server accept -myaddr 127.0.0.1 0] + set port [lindex [fconfigure $server -sockname] 2] + } -body { + set client [socket -async localhost $port] + # fileevent $client readable [list set x [fconfigure $client -error]] + after 1000 {set x [fconfigure $client -error]} + vwait x + set x + } -cleanup { + close $server + close $client + } -result ok ::tcltest::cleanupTests flush stdout return diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index cb72759..f6a1bf2 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -49,9 +49,22 @@ struct TcpState { TcpFdList *fds; /* The file descriptors of the sockets. */ int flags; /* ORed combination of the bitfields defined * below. */ - Tcl_TcpAcceptProc *acceptProc; + union { + struct { + /* Only needed for server sockets */ + Tcl_TcpAcceptProc *acceptProc; /* Proc to call on accept. */ - ClientData acceptProcData; /* The data for the accept proc. */ + ClientData acceptProcData; + /* The data for the accept proc. */ + }; + struct { + /* Only needed for client sockets */ + struct addrinfo *addrlist; + struct addrinfo *myaddrlist; + struct addrinfo *addr; + struct addrinfo *myaddr; + }; + }; }; /* @@ -89,9 +102,8 @@ struct TcpState { * Static routines for this file: */ -static TcpState * CreateClientSocket(Tcl_Interp *interp, int port, - const char *host, const char *myaddr, - int myport, int async); +static int CreateClientSocket(Tcl_Interp *interp, + TcpState *state); static void TcpAccept(ClientData data, int mask); static int TcpBlockModeProc(ClientData data, int mode); static int TcpCloseProc(ClientData instanceData, @@ -829,17 +841,27 @@ TcpGetHandleProc( return TCL_OK; } +static void +TcpAsyncCallback( + ClientData clientData, /* The socket state. */ + int mask) /* Events of interest; an OR-ed combination of + * TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ +{ + CreateClientSocket(NULL, clientData); +} + /* *---------------------------------------------------------------------- * - * CreateSocket -- + * CreateClientSocket -- * - * This function opens a new socket in client or server mode and - * initializes the TcpState structure. + * This function opens a new socket in client mode. * * Results: - * Returns a new TcpState, or NULL with an error in the interp's result, - * if interp is not NULL. + * TCL_OK, if the socket was successfully connected or an asynchronous + * connection is in progress. If an error occurs, TCL_ERROR is returned + * and an error message is left in interp. * * Side effects: * Opens a socket. @@ -847,37 +869,22 @@ TcpGetHandleProc( *---------------------------------------------------------------------- */ -static TcpState * +static int CreateClientSocket( Tcl_Interp *interp, /* For error reporting; can be NULL. */ - int port, /* Port number to open. */ - const char *host, /* Name of host on which to open port. */ - const char *myaddr, /* Optional client-side address. - * NULL implies INADDR_ANY/in6addr_any */ - int myport, /* Optional client-side port */ - int async) /* If nonzero and creating a client socket, - * attempt to do an async connect. Otherwise - * do a synchronous connect or bind. */ + TcpState *state) { - int status = -1, connected = 0, sock = -1; - struct addrinfo *addrlist = NULL, *addrPtr; - /* Socket address */ - struct addrinfo *myaddrlist = NULL, *myaddrPtr; - /* Socket address for client */ - TcpState *statePtr; - const char *errorMsg = NULL; + int status = -1, connected = 0; + int async = state->flags & TCP_ASYNC_CONNECT; - if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg)) { - goto error; + if (state->addr != NULL) { + goto coro_continue; } - if (!TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, &errorMsg)) { - goto error; - } - - for (addrPtr = addrlist; addrPtr != NULL; - addrPtr = addrPtr->ai_next) { - for (myaddrPtr = myaddrlist; myaddrPtr != NULL; - myaddrPtr = myaddrPtr->ai_next) { + + for (state->addr = state->addrlist; state->addr != NULL; + state->addr = state->addr->ai_next) { + for (state->myaddr = state->myaddrlist; state->myaddr != NULL; + state->myaddr = state->myaddr->ai_next) { int reuseaddr; /* @@ -885,12 +892,12 @@ CreateClientSocket( * different families. */ - if (myaddrPtr->ai_family != addrPtr->ai_family) { + if (state->myaddr->ai_family != state->addr->ai_family) { continue; } - sock = socket(addrPtr->ai_family, SOCK_STREAM, 0); - if (sock < 0) { + state->fds->fd = socket(state->addr->ai_family, SOCK_STREAM, 0); + if (state->fds->fd < 0) { continue; } @@ -899,25 +906,26 @@ CreateClientSocket( * inherited by child processes. */ - fcntl(sock, F_SETFD, FD_CLOEXEC); + fcntl(state->fds->fd, F_SETFD, FD_CLOEXEC); /* * Set kernel space buffering */ - TclSockMinimumBuffers(INT2PTR(sock), SOCKET_BUFSIZE); + TclSockMinimumBuffers(INT2PTR(state->fds->fd), SOCKET_BUFSIZE); if (async) { - status = TclUnixSetBlockingMode(sock, TCL_MODE_NONBLOCKING); + status = TclUnixSetBlockingMode(state->fds->fd, TCL_MODE_NONBLOCKING); if (status < 0) { goto looperror; } } reuseaddr = 1; - (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (void) setsockopt(state->fds->fd, SOL_SOCKET, SO_REUSEADDR, (char *) &reuseaddr, sizeof(reuseaddr)); - status = bind(sock, myaddrPtr->ai_addr, myaddrPtr->ai_addrlen); + status = bind(state->fds->fd, state->myaddr->ai_addr, + state->myaddr->ai_addrlen); if (status < 0) { goto looperror; } @@ -929,27 +937,39 @@ CreateClientSocket( * in being informed when the connect completes. */ - status = connect(sock, addrPtr->ai_addr, addrPtr->ai_addrlen); + status = connect(state->fds->fd, state->addr->ai_addr, + state->addr->ai_addrlen); if (status < 0 && errno == EINPROGRESS) { - status = 0; - } + Tcl_CreateFileHandler(state->fds->fd, TCL_WRITABLE, + TcpAsyncCallback, state); + // fprintf(stderr, "here: %d \n", state->fds->fd); + return TCL_OK; + coro_continue: + do { + socklen_t optlen = sizeof(int); + Tcl_DeleteFileHandler(state->fds->fd); + getsockopt(state->fds->fd, SOL_SOCKET, SO_ERROR, + (char *)&status, &optlen); + // fprintf(stderr, "there: %d \n", state->fds->fd); + } while (0); + } if (status == 0) { connected = 1; break; } looperror: - if (sock != -1) { - close(sock); - sock = -1; + if (state->fds->fd != -1) { + close(state->fds->fd); + state->fds->fd = -1; } } if (connected) { break; } status = -1; - if (sock >= 0) { - close(sock); - sock = -1; + if (state->fds->fd >= 0) { + close(state->fds->fd); + state->fds->fd = -1; } } if (async) { @@ -957,42 +977,25 @@ CreateClientSocket( * Restore blocking mode. */ - status = TclUnixSetBlockingMode(sock, TCL_MODE_BLOCKING); + status = TclUnixSetBlockingMode(state->fds->fd, TCL_MODE_BLOCKING); } -error: - if (addrlist) { - freeaddrinfo(addrlist); - } - if (myaddrlist) { - freeaddrinfo(myaddrlist); - } - - if (status < 0) { - if (interp != NULL) { + freeaddrinfo(state->addrlist); + freeaddrinfo(state->myaddrlist); + + if (status < 0 && !async) { + if (interp != NULL) { Tcl_AppendResult(interp, "couldn't open socket: ", - Tcl_PosixError(interp), NULL); - if (errorMsg != NULL) { - Tcl_AppendResult(interp, " (", errorMsg, ")", NULL); - } + Tcl_PosixError(interp), NULL); } - if (sock != -1) { - close(sock); + if (state->fds->fd != -1) { + close(state->fds->fd); } - return NULL; + ckfree(state->fds); + ckfree(state); + return TCL_ERROR; } - - /* - * Allocate a new TcpState for this socket. - */ - - statePtr = ckalloc(sizeof(TcpState)); - statePtr->flags = async ? TCP_ASYNC_CONNECT : 0; - statePtr->fds = ckalloc(sizeof(TcpFdList)); - memset(statePtr->fds, (int) 0, sizeof(TcpFdList)); - statePtr->fds->fd = sock; - - return statePtr; + return TCL_OK; } /* @@ -1023,31 +1026,63 @@ Tcl_OpenTcpClient( * connect. Otherwise we do a blocking * connect. */ { - TcpState *statePtr; - char channelName[16 + TCL_INTEGER_SPACE]; + TcpState *state; + const char *errorMsg = NULL; + struct addrinfo *addrlist, *myaddrlist; + char channelName[4+16+1]; /* "sock" + up to 16 hex chars + \0 */ + /* - * Create a new client socket and wrap it in a channel. + * Do the name lookups for the local and remote addresses. */ - - statePtr = CreateClientSocket(interp, port, host, myaddr, myport, async); - if (statePtr == NULL) { - return NULL; + if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg)) { + goto error; + } + if (!TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, + &errorMsg)) { + freeaddrinfo(addrlist); + goto error; } - statePtr->acceptProc = NULL; - statePtr->acceptProcData = NULL; + /* + * Allocate a new TcpState for this socket. + */ + state = ckalloc(sizeof(TcpState)); + memset(state, 0, sizeof(TcpState)); + state->flags = async ? TCP_ASYNC_CONNECT : 0; + state->addrlist = addrlist; + state->myaddrlist = myaddrlist; + state->fds = ckalloc(sizeof(TcpFdList)); + memset(state->fds, (int) 0, sizeof(TcpFdList)); + state->fds->fd = -1; - sprintf(channelName, "sock%d", statePtr->fds->fd); + /* + * Create a new client socket and wrap it in a channel. + */ + if (CreateClientSocket(interp, state) != TCL_OK) { + goto error; + } - statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, - statePtr, (TCL_READABLE | TCL_WRITABLE)); - if (Tcl_SetChannelOption(interp, statePtr->channel, "-translation", + sprintf(channelName, "sock%lx", (long)state); + + state->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + state, (TCL_READABLE | TCL_WRITABLE)); + if (Tcl_SetChannelOption(interp, state->channel, "-translation", "auto crlf") == TCL_ERROR) { - Tcl_Close(NULL, statePtr->channel); + Tcl_Close(NULL, state->channel); return NULL; } - return statePtr->channel; + return state->channel; + +error: + if (interp != NULL) { + Tcl_AppendResult(interp, "couldn't open socket: ", + Tcl_PosixError(interp), NULL); + if (errorMsg != NULL) { + Tcl_AppendResult(interp, " (", errorMsg, ")", NULL); + } + } + return NULL; } /* -- cgit v0.12