summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormax <max@tclers.tk>2011-05-27 18:36:26 (GMT)
committermax <max@tclers.tk>2011-05-27 18:36:26 (GMT)
commit19755ae8971d97cfc092add10ceed6ab40f011bd (patch)
tree5b12cad417abf5ef6fef83bebf42dca448b10e0f
parentba5939ea3bf47fc00db9172391b3d68e24539921 (diff)
downloadtcl-19755ae8971d97cfc092add10ceed6ab40f011bd.zip
tcl-19755ae8971d97cfc092add10ceed6ab40f011bd.tar.gz
tcl-19755ae8971d97cfc092add10ceed6ab40f011bd.tar.bz2
Fix [socket -async] for DNS names with more than one address
-rw-r--r--ChangeLog8
-rw-r--r--tests/socket.test22
-rw-r--r--unix/tclUnixSock.c231
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 <max@suse.de>
+
+ * 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 <dgp@users.sourceforge.net>
* 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;
}
/*