diff options
| author | dkf <donal.k.fellows@manchester.ac.uk> | 2018-09-04 19:47:55 (GMT) |
|---|---|---|
| committer | dkf <donal.k.fellows@manchester.ac.uk> | 2018-09-04 19:47:55 (GMT) |
| commit | fbef9aa84089336e767f0dafe410df51b4f1d3b3 (patch) | |
| tree | ad9c157389b8b2213ce0693d89ccfcf48a1509c6 /unix/tclUnixSock.c | |
| parent | 24197ad684cf243d80448a14b0aead5099299150 (diff) | |
| parent | 2f2b7f6ac7122f3b6be07e793e1658cdb5791aa2 (diff) | |
| download | tcl-fbef9aa84089336e767f0dafe410df51b4f1d3b3.zip tcl-fbef9aa84089336e767f0dafe410df51b4f1d3b3.tar.gz tcl-fbef9aa84089336e767f0dafe410df51b4f1d3b3.tar.bz2 | |
merge core-8-branch
Diffstat (limited to 'unix/tclUnixSock.c')
| -rw-r--r-- | unix/tclUnixSock.c | 727 |
1 files changed, 519 insertions, 208 deletions
diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index 9c3d7eb..2793d3b 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -19,6 +19,7 @@ #define SET_BITS(var, bits) ((var) |= (bits)) #define CLEAR_BITS(var, bits) ((var) &= ~(bits)) +#define GOT_BITS(var, bits) (((var) & (bits)) != 0) /* "sock" + a pointer in hex + \0 */ #define SOCK_CHAN_LENGTH (4 + sizeof(void *) * 2 + 1) @@ -52,9 +53,13 @@ typedef struct TcpFdList { struct TcpState { Tcl_Channel channel; /* Channel associated with this file. */ + int testFlags; /* bit field for tests. Is set by testsocket + * test procedure */ TcpFdList fds; /* The file descriptors of the sockets. */ int flags; /* ORed combination of the bitfields defined * below. */ + int interest; /* Event types of interest */ + /* * Only needed for server sockets */ @@ -73,7 +78,7 @@ struct TcpState { struct addrinfo *myaddr; /* Iterator over myaddrlist. */ int filehandlers; /* Caches FileHandlers that get set up while * an async socket is not yet connected. */ - int status; /* Cache status of async socket. */ + int connectError; /* Cache SO_ERROR of async socket. */ int cachedBlocking; /* Cache blocking mode of async socket. */ }; @@ -82,8 +87,22 @@ struct TcpState { * structure. */ -#define TCP_ASYNC_SOCKET (1<<0) /* Asynchronous socket. */ +#define TCP_NONBLOCKING (1<<0) /* Socket with non-blocking I/O */ #define TCP_ASYNC_CONNECT (1<<1) /* Async connect in progress. */ +#define TCP_ASYNC_PENDING (1<<4) /* TcpConnect was called to + * process an async connect. This + * flag indicates that reentry is + * still pending */ +#define TCP_ASYNC_FAILED (1<<5) /* An async connect finally failed */ + +/* + * These bits may be ORed together into the "testFlags" field of a TcpState + * structure. + */ + +#define TCP_ASYNC_TEST_MODE (1<<0) /* Async testing activated. Do not + * automatically continue connection + * process. */ /* * The following defines the maximum length of the listen queue. This is the @@ -110,8 +129,7 @@ struct TcpState { * Static routines for this file: */ -static int CreateClientSocket(Tcl_Interp *interp, - TcpState *state); +static int TcpConnect(Tcl_Interp *interp, TcpState *state); static void TcpAccept(ClientData data, int mask); static int TcpBlockModeProc(ClientData data, int mode); static int TcpCloseProc(ClientData instanceData, @@ -129,6 +147,7 @@ static int TcpOutputProc(ClientData instanceData, const char *buf, int toWrite, int *errorCode); static void TcpWatchProc(ClientData instanceData, int mask); static int WaitForConnect(TcpState *statePtr, int *errorCodePtr); +static void WrapNotify(ClientData clientData, int mask); /* * This structure describes the channel type structure for TCP socket @@ -163,8 +182,26 @@ static TclInitProcessGlobalValueProc InitializeHostName; static ProcessGlobalValue hostName = {0, 0, NULL, NULL, InitializeHostName, NULL, NULL}; +#if 0 +/* printf debugging */ +void +printaddrinfo( + struct addrinfo *addrlist, + char *prefix) +{ + char host[NI_MAXHOST], port[NI_MAXSERV]; + struct addrinfo *ai; + + for (ai = addrlist; ai != NULL; ai = ai->ai_next) { + getnameinfo(ai->ai_addr, ai->ai_addrlen, + host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST|NI_NUMERICSERV); + fprintf(stderr,"%s: %s:%s\n", prefix, host, port); + } +} +#endif /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * InitializeHostName -- * @@ -174,13 +211,13 @@ static ProcessGlobalValue hostName = * Results: * None. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ static void InitializeHostName( char **valuePtr, - int *lengthPtr, + unsigned int *lengthPtr, Tcl_Encoding *encodingPtr) { const char *native = NULL; @@ -217,7 +254,7 @@ InitializeHostName( } } if (native == NULL) { - native = tclEmptyStringRep; + native = &tclEmptyString; } #else /* !NO_UNAME */ /* @@ -248,12 +285,12 @@ InitializeHostName( *encodingPtr = Tcl_GetEncoding(NULL, NULL); *lengthPtr = strlen(native); - *valuePtr = ckalloc((*lengthPtr) + 1); - memcpy(*valuePtr, native, (size_t)(*lengthPtr)+1); + *valuePtr = ckalloc(*lengthPtr + 1); + memcpy(*valuePtr, native, *lengthPtr + 1); } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * Tcl_GetHostName -- * @@ -267,7 +304,7 @@ InitializeHostName( * Side effects: * Caches the name to return for future calls. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ const char * @@ -277,7 +314,7 @@ Tcl_GetHostName(void) } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * TclpHasSockets -- * @@ -289,7 +326,7 @@ Tcl_GetHostName(void) * Side effects: * None. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ int @@ -300,7 +337,7 @@ TclpHasSockets( } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * TclpFinalizeSockets -- * @@ -312,7 +349,7 @@ TclpHasSockets( * Side effects: * None. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ void @@ -322,7 +359,7 @@ TclpFinalizeSockets(void) } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * TcpBlockModeProc -- * @@ -335,7 +372,7 @@ TclpFinalizeSockets(void) * Side effects: * Sets the device into blocking or nonblocking mode. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ /* ARGSUSED */ @@ -349,11 +386,11 @@ TcpBlockModeProc( TcpState *statePtr = instanceData; if (mode == TCL_MODE_BLOCKING) { - CLEAR_BITS(statePtr->flags, TCP_ASYNC_SOCKET); + CLEAR_BITS(statePtr->flags, TCP_NONBLOCKING); } else { - SET_BITS(statePtr->flags, TCP_ASYNC_SOCKET); + SET_BITS(statePtr->flags, TCP_NONBLOCKING); } - if (statePtr->flags & TCP_ASYNC_CONNECT) { + if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { statePtr->cachedBlocking = mode; return 0; } @@ -364,17 +401,32 @@ TcpBlockModeProc( } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * WaitForConnect -- * - * Wait for a connection on an asynchronously opened socket to be - * completed. In nonblocking mode, just test if the connection - * has completed without blocking. + * Check the state of an async connect process. If a connection attempt + * terminated, process it, which may finalize it or may start the next + * attempt. If a connect error occures, it is saved in + * statePtr->connectError to be reported by 'fconfigure -error'. + * + * There are two modes of operation, defined by errorCodePtr: + * * non-NULL: Called by explicite read/write command. Blocks if the + * socket is blocking. + * May return two error codes: + * * EWOULDBLOCK: if connect is still in progress + * * ENOTCONN: if connect failed. This would be the error message + * of a rect or sendto syscall so this is emulated here. + * * NULL: Called by a backround operation. Do not block and do not + * return any error code. * * Results: - * 0 if the connection has completed, -1 if still in progress - * or there is an error. + * 0 if the connection has completed, -1 if still in progress or there is + * an error. + * + * Side effects: + * Processes socket events off the system queue. May process + * asynchroneous connects. * *---------------------------------------------------------------------- */ @@ -382,34 +434,67 @@ TcpBlockModeProc( static int WaitForConnect( TcpState *statePtr, /* State of the socket. */ - int *errorCodePtr) /* Where to store errors? */ + int *errorCodePtr) { - int timeOut; /* How long to wait. */ - int state; /* Of calling TclWaitForFile. */ + int timeout; /* - * If an asynchronous connect is in progress, attempt to wait for it to - * complete before reading. + * Check if an async connect failed already and error reporting is + * demanded, return the error ENOTCONN */ - if (statePtr->flags & TCP_ASYNC_CONNECT) { - if (statePtr->flags & TCP_ASYNC_SOCKET) { - timeOut = 0; - } else { - timeOut = -1; - } - errno = 0; - state = TclUnixWaitForFile(statePtr->fds.fd, - TCL_WRITABLE | TCL_EXCEPTION, timeOut); - if (state & TCL_EXCEPTION) { - return -1; - } - if (state & TCL_WRITABLE) { - CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); - } else if (timeOut == 0) { - *errorCodePtr = errno = EWOULDBLOCK; - return -1; - } + if (errorCodePtr != NULL && GOT_BITS(statePtr->flags, TCP_ASYNC_FAILED)) { + *errorCodePtr = ENOTCONN; + return -1; + } + + /* + * Check if an async connect is running. If not return ok + */ + + if (!GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { + return 0; + } + + /* + * In socket test mode do not continue with the connect. + * Exceptions are: + * - Call by recv/send and blocking socket + * (errorCodePtr != NULL && !GOT_BITS(flags, TCP_NONBLOCKING)) + */ + + if (GOT_BITS(statePtr->testFlags, TCP_ASYNC_TEST_MODE) + && !(errorCodePtr != NULL + && !GOT_BITS(statePtr->flags, TCP_NONBLOCKING))) { + *errorCodePtr = EWOULDBLOCK; + return -1; + } + + if (errorCodePtr == NULL || GOT_BITS(statePtr->flags, TCP_NONBLOCKING)) { + timeout = 0; + } else { + timeout = -1; + } + do { + if (TclUnixWaitForFile(statePtr->fds.fd, + TCL_WRITABLE | TCL_EXCEPTION, timeout) != 0) { + TcpConnect(NULL, statePtr); + } + + /* + * Do this only once in the nonblocking case and repeat it until the + * socket is final when blocking. + */ + } while (timeout == -1 && GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)); + + if (errorCodePtr != NULL) { + if (GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { + *errorCodePtr = EAGAIN; + return -1; + } else if (statePtr->connectError != 0) { + *errorCodePtr = ENOTCONN; + return -1; + } } return 0; } @@ -503,6 +588,7 @@ TcpOutputProc( return -1; } written = send(statePtr->fds.fd, buf, (size_t) toWrite, 0); + if (written > -1) { return written; } @@ -545,7 +631,7 @@ TcpCloseProc( * handlers are already deleted in the generic IO channel closing code * that called this function, so we do not have to delete them here. */ - + for (fds = &statePtr->fds; fds != NULL; fds = fds->next) { if (fds->fd < 0) { continue; @@ -554,12 +640,13 @@ TcpCloseProc( if (close(fds->fd) < 0) { errorCode = errno; } - + } fds = statePtr->fds.next; while (fds != NULL) { TcpFdList *next = fds->next; - ckfree(fds); + + ckfree(fds); fds = next; } if (statePtr->addrlist != NULL) { @@ -629,10 +716,9 @@ TcpClose2Proc( * * TcpHostPortList -- * - * This function is called by the -gethostname and -getpeername - * switches of TcpGetOptionProc() to add three list elements - * with the textual representation of the given address to the - * given DString. + * This function is called by the -gethostname and -getpeername switches + * of TcpGetOptionProc() to add three list elements with the textual + * representation of the given address to the given DString. * * Results: * None. @@ -642,6 +728,37 @@ TcpClose2Proc( * *---------------------------------------------------------------------- */ + +#ifndef NEED_FAKE_RFC2553 +#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif +static inline int +IPv6AddressNeedsNumericRendering( + struct in6_addr addr) +{ + if (IN6_ARE_ADDR_EQUAL(&addr, &in6addr_any)) { + return 1; + } + + /* + * The IN6_IS_ADDR_V4MAPPED macro has a problem with aliasing warnings on + * at least some versions of OSX. + */ + + if (!IN6_IS_ADDR_V4MAPPED(&addr)) { + return 0; + } + + return (addr.s6_addr[12] == 0 && addr.s6_addr[13] == 0 + && addr.s6_addr[14] == 0 && addr.s6_addr[15] == 0); +} +#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic pop +#endif +#endif /* NEED_FAKE_RFC2553 */ + static void TcpHostPortList( Tcl_Interp *interp, @@ -653,40 +770,47 @@ TcpHostPortList( char host[NI_MAXHOST], nhost[NI_MAXHOST], nport[NI_MAXSERV]; int flags = 0; - getnameinfo(&addr.sa, salen, - nhost, sizeof(nhost), nport, sizeof(nport), - NI_NUMERICHOST | NI_NUMERICSERV); + getnameinfo(&addr.sa, salen, nhost, sizeof(nhost), nport, sizeof(nport), + NI_NUMERICHOST | NI_NUMERICSERV); Tcl_DStringAppendElement(dsPtr, nhost); + /* - * We don't want to resolve INADDR_ANY and sin6addr_any; they - * can sometimes cause problems (and never have a name). + * We don't want to resolve INADDR_ANY and sin6addr_any; they can + * sometimes cause problems (and never have a name). */ + if (addr.sa.sa_family == AF_INET) { if (addr.sa4.sin_addr.s_addr == INADDR_ANY) { flags |= NI_NUMERICHOST; } #ifndef NEED_FAKE_RFC2553 } else if (addr.sa.sa_family == AF_INET6) { - if ((IN6_ARE_ADDR_EQUAL(&addr.sa6.sin6_addr, - &in6addr_any)) - || (IN6_IS_ADDR_V4MAPPED(&addr.sa6.sin6_addr) && - addr.sa6.sin6_addr.s6_addr[12] == 0 && - addr.sa6.sin6_addr.s6_addr[13] == 0 && - addr.sa6.sin6_addr.s6_addr[14] == 0 && - addr.sa6.sin6_addr.s6_addr[15] == 0)) { + if (IPv6AddressNeedsNumericRendering(addr.sa6.sin6_addr)) { flags |= NI_NUMERICHOST; } #endif /* NEED_FAKE_RFC2553 */ } - /* Check if reverse DNS has been switched off globally */ - if (interp != NULL && Tcl_GetVar(interp, SUPPRESS_RDNS_VAR, 0) != NULL) { + + /* + * Check if reverse DNS has been switched off globally. + */ + + if (interp != NULL && + Tcl_GetVar2(interp, SUPPRESS_RDNS_VAR, NULL, 0) != NULL) { flags |= NI_NUMERICHOST; } - if (getnameinfo(&addr.sa, salen, host, sizeof(host), NULL, 0, flags) == 0) { - /* Reverse mapping worked */ + if (getnameinfo(&addr.sa, salen, host, sizeof(host), NULL, 0, + flags) == 0) { + /* + * Reverse mapping worked. + */ + Tcl_DStringAppendElement(dsPtr, host); } else { - /* Reverse mappong failed - use the numeric rep once more */ + /* + * Reverse mapping failed - use the numeric rep once more. + */ + Tcl_DStringAppendElement(dsPtr, nhost); } Tcl_DStringAppendElement(dsPtr, nport); @@ -726,6 +850,8 @@ TcpGetOptionProc( TcpState *statePtr = instanceData; size_t len = 0; + WaitForConnect(statePtr, NULL); + if (optionName != NULL) { len = strlen(optionName); } @@ -733,30 +859,57 @@ TcpGetOptionProc( if ((len > 1) && (optionName[1] == 'e') && (strncmp(optionName, "-error", len) == 0)) { socklen_t optlen = sizeof(int); - int err, ret; - if (statePtr->status == 0) { - ret = getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR, - (char *) &err, &optlen); - if (ret < 0) { - err = errno; - } + if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { + /* + * Suppress errors as long as we are not done. + */ + + errno = 0; + } else if (statePtr->connectError != 0) { + errno = statePtr->connectError; + statePtr->connectError = 0; } else { - err = statePtr->status; - statePtr->status = 0; + int err; + + getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR, (char *) &err, + &optlen); + errno = err; + } + if (errno != 0) { + Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(errno), -1); } - if (err != 0) { - Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(err), -1); - } return TCL_OK; } + if ((len > 1) && (optionName[1] == 'c') && + (strncmp(optionName, "-connecting", len) == 0)) { + Tcl_DStringAppend(dsPtr, + GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT) ? "1" : "0", -1); + return TCL_OK; + } + if ((len == 0) || ((len > 1) && (optionName[1] == 'p') && (strncmp(optionName, "-peername", len) == 0))) { address peername; socklen_t size = sizeof(peername); - if (getpeername(statePtr->fds.fd, &peername.sa, &size) >= 0) { + if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { + /* + * In async connect output an empty string + */ + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-peername"); + Tcl_DStringAppendElement(dsPtr, ""); + } else { + return TCL_OK; + } + } else if (getpeername(statePtr->fds.fd, &peername.sa, &size) >= 0) { + /* + * Peername fetch succeeded - output list + */ + if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-peername"); Tcl_DStringStartSublist(dsPtr); @@ -796,11 +949,19 @@ TcpGetOptionProc( Tcl_DStringAppendElement(dsPtr, "-sockname"); Tcl_DStringStartSublist(dsPtr); } - for (fds = &statePtr->fds; fds != NULL; fds = fds->next) { - size = sizeof(sockname); - if (getsockname(fds->fd, &(sockname.sa), &size) >= 0) { - found = 1; - TcpHostPortList(interp, dsPtr, sockname, size); + if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { + /* + * In async connect output an empty string + */ + + found = 1; + } else { + for (fds = &statePtr->fds; fds != NULL; fds = fds->next) { + size = sizeof(sockname); + if (getsockname(fds->fd, &(sockname.sa), &size) >= 0) { + found = 1; + TcpHostPortList(interp, dsPtr, sockname, size); + } } } if (found) { @@ -818,14 +979,15 @@ TcpGetOptionProc( } if (len > 0) { - return Tcl_BadChannelOption(interp, optionName, "peername sockname"); + return Tcl_BadChannelOption(interp, optionName, + "connecting peername sockname"); } return TCL_OK; } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * TcpWatchProc -- * @@ -838,10 +1000,39 @@ TcpGetOptionProc( * Sets up the notifier so that a future event on the channel will be * seen by Tcl. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ static void +WrapNotify( + ClientData clientData, + int mask) +{ + TcpState *statePtr = (TcpState *) clientData; + int newmask = mask & statePtr->interest; + + if (newmask == 0) { + /* + * There was no overlap between the states the channel is interested + * in notifications for, and the states that are reported present on + * the file descriptor by select(). The only way that can happen is + * when the channel is interested in a writable condition, and only a + * readable state is reported present (see TcpWatchProc() below). In + * that case, signal back to the caller the writable state, which is + * really an error condition. As an extra check on that assumption, + * check for a non-zero value of errno before reporting an artificial + * writable state. + */ + + if (errno == 0) { + return; + } + newmask = TCL_WRITABLE; + } + Tcl_NotifyChannel(statePtr->channel, newmask); +} + +static void TcpWatchProc( ClientData instanceData, /* The socket state. */ int mask) /* Events of interest; an OR-ed combination of @@ -856,23 +1047,48 @@ TcpWatchProc( * be readable or writable at the Tcl level. This keeps Tcl scripts * from interfering with the -accept behavior (bug #3394732). */ + return; } - - if (statePtr->flags & TCP_ASYNC_CONNECT) { - /* Async sockets use a FileHandler internally while connecting, so we - * need to cache this request until the connection has succeeded. */ + + if (GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { + /* + * Async sockets use a FileHandler internally while connecting, so we + * need to cache this request until the connection has succeeded. + */ + statePtr->filehandlers = mask; } else if (mask) { - Tcl_CreateFileHandler(statePtr->fds.fd, mask, - (Tcl_FileProc *) Tcl_NotifyChannel, statePtr->channel); + + /* + * Whether it is a bug or feature or otherwise, it is a fact of life + * that on at least some Linux kernels select() fails to report that a + * socket file descriptor is writable when the other end of the socket + * is closed. This is in contrast to the guarantees Tcl makes that + * its channels become writable and fire writable events on an error + * conditon. This has caused a leak of file descriptors in a state of + * background flushing. See Tcl ticket 1758a0b603. + * + * As a workaround, when our caller indicates an interest in writable + * notifications, we must tell the notifier built around select() that + * we are interested in the readable state of the file descriptor as + * well, as that is the only reliable means to get notified of error + * conditions. Then it is the task of WrapNotify() above to untangle + * the meaning of these channel states and report the chan events as + * best it can. We save a copy of the mask passed in to assist with + * that. + */ + + statePtr->interest = mask; + Tcl_CreateFileHandler(statePtr->fds.fd, mask|TCL_READABLE, + (Tcl_FileProc *) WrapNotify, statePtr); } else { Tcl_DeleteFileHandler(statePtr->fds.fd); } } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * TcpGetHandleProc -- * @@ -886,7 +1102,7 @@ TcpWatchProc( * Side effects: * None. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ /* ARGSUSED */ @@ -903,16 +1119,17 @@ TcpGetHandleProc( } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * * TcpAsyncCallback -- * - * Called by the event handler that CreateClientSocket sets up - * internally for [socket -async] to get notified when the - * asyncronous connection attempt has succeeded or failed. + * Called by the event handler that TcpConnect sets up internally for + * [socket -async] to get notified when the asynchronous connection + * attempt has succeeded or failed. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ + static void TcpAsyncCallback( ClientData clientData, /* The socket state. */ @@ -920,13 +1137,13 @@ TcpAsyncCallback( * TCL_READABLE, TCL_WRITABLE and * TCL_EXCEPTION. */ { - CreateClientSocket(NULL, clientData); + TcpConnect(NULL, clientData); } /* - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- * - * CreateClientSocket -- + * TcpConnect -- * * This function opens a new socket in client mode. * @@ -940,7 +1157,7 @@ TcpAsyncCallback( * * Remarks: * A single host name may resolve to more than one IP address, e.g. for - * an IPv4/IPv6 dual stack host. For handling asyncronously connecting + * an IPv4/IPv6 dual stack host. For handling asynchronously connecting * sockets in the background for such hosts, this function can act as a * coroutine. On the first call, it sets up the control variables for the * two nested loops over the local and remote addresses. Once the first @@ -948,39 +1165,38 @@ TcpAsyncCallback( * event handler for that socket, and returns. When the callback occurs, * control is transferred to the "reenter" label, right after the initial * return and the loops resume as if they had never been interrupted. - * For syncronously connecting sockets, the loops work the usual way. + * For synchronously connecting sockets, the loops work the usual way. * - *---------------------------------------------------------------------- + * ---------------------------------------------------------------------- */ static int -CreateClientSocket( +TcpConnect( Tcl_Interp *interp, /* For error reporting; can be NULL. */ - TcpState *state) + TcpState *statePtr) { socklen_t optlen; - int async_callback = (state->addr != NULL); - int status; - int async = state->flags & TCP_ASYNC_CONNECT; + int async_callback = GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING); + int ret = -1, error = EHOSTUNREACH; + int async = GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT); if (async_callback) { goto reenter; } - for (state->addr = state->addrlist; state->addr != NULL; - state->addr = state->addr->ai_next) { - status = -1; + for (statePtr->addr = statePtr->addrlist; statePtr->addr != NULL; + statePtr->addr = statePtr->addr->ai_next) { + for (statePtr->myaddr = statePtr->myaddrlist; + statePtr->myaddr != NULL; + statePtr->myaddr = statePtr->myaddr->ai_next) { + int reuseaddr = 1; - for (state->myaddr = state->myaddrlist; state->myaddr != NULL; - state->myaddr = state->myaddr->ai_next) { - int reuseaddr; - /* * No need to try combinations of local and remote addresses of * different families. */ - if (state->myaddr->ai_family != state->addr->ai_family) { + if (statePtr->myaddr->ai_family != statePtr->addr->ai_family) { continue; } @@ -989,13 +1205,15 @@ CreateClientSocket( * iteration. */ - if (state->fds.fd >= 0) { - close(state->fds.fd); - state->fds.fd = -1; + if (statePtr->fds.fd >= 0) { + close(statePtr->fds.fd); + statePtr->fds.fd = -1; + errno = 0; } - state->fds.fd = socket(state->addr->ai_family, SOCK_STREAM, 0); - if (state->fds.fd < 0) { + statePtr->fds.fd = socket(statePtr->addr->ai_family, SOCK_STREAM, + 0); + if (statePtr->fds.fd < 0) { continue; } @@ -1003,29 +1221,36 @@ CreateClientSocket( * Set the close-on-exec flag so that the socket will not get * inherited by child processes. */ - - fcntl(state->fds.fd, F_SETFD, FD_CLOEXEC); - + + fcntl(statePtr->fds.fd, F_SETFD, FD_CLOEXEC); + /* * Set kernel space buffering */ - - TclSockMinimumBuffers(INT2PTR(state->fds.fd), SOCKET_BUFSIZE); - + + TclSockMinimumBuffers(INT2PTR(statePtr->fds.fd), SOCKET_BUFSIZE); + if (async) { - status = TclUnixSetBlockingMode(state->fds.fd, + ret = TclUnixSetBlockingMode(statePtr->fds.fd, TCL_MODE_NONBLOCKING); - if (status < 0) { + if (ret < 0) { continue; - } - } + } + } + + /* + * Must reset the error variable here, before we use it for the + * first time in this iteration. + */ + + error = 0; - reuseaddr = 1; - (void) setsockopt(state->fds.fd, SOL_SOCKET, SO_REUSEADDR, + (void) setsockopt(statePtr->fds.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &reuseaddr, sizeof(reuseaddr)); - status = bind(state->fds.fd, state->myaddr->ai_addr, - state->myaddr->ai_addrlen); - if (status < 0) { + ret = bind(statePtr->fds.fd, statePtr->myaddr->ai_addr, + statePtr->myaddr->ai_addrlen); + if (ret < 0) { + error = errno; continue; } @@ -1035,16 +1260,23 @@ CreateClientSocket( * will set up a file handler on the socket if she is interested * in being informed when the connect completes. */ - - status = connect(state->fds.fd, state->addr->ai_addr, - state->addr->ai_addrlen); - if (status < 0 && errno == EINPROGRESS) { - Tcl_CreateFileHandler(state->fds.fd, - TCL_WRITABLE|TCL_EXCEPTION, TcpAsyncCallback, state); + + ret = connect(statePtr->fds.fd, statePtr->addr->ai_addr, + statePtr->addr->ai_addrlen); + if (ret < 0) { + error = errno; + } + if (ret < 0 && errno == EINPROGRESS) { + Tcl_CreateFileHandler(statePtr->fds.fd, + TCL_WRITABLE | TCL_EXCEPTION, TcpAsyncCallback, + statePtr); + errno = EWOULDBLOCK; + SET_BITS(statePtr->flags, TCP_ASYNC_PENDING); return TCL_OK; reenter: - Tcl_DeleteFileHandler(state->fds.fd); + CLEAR_BITS(statePtr->flags, TCP_ASYNC_PENDING); + Tcl_DeleteFileHandler(statePtr->fds.fd); /* * Read the error state from the socket to see if the async @@ -1054,26 +1286,31 @@ CreateClientSocket( */ optlen = sizeof(int); - getsockopt(state->fds.fd, SOL_SOCKET, SO_ERROR, - (char *) &status, &optlen); - state->status = status; + + getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR, + (char *) &error, &optlen); + errno = error; } - if (status == 0) { - CLEAR_BITS(state->flags, TCP_ASYNC_CONNECT); + if (error == 0) { goto out; } } } -out: - + out: + statePtr->connectError = error; + CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); if (async_callback) { /* * An asynchonous connection has finally succeeded or failed. */ - TcpWatchProc(state, state->filehandlers); - TclUnixSetBlockingMode(state->fds.fd, state->cachedBlocking); + TcpWatchProc(statePtr, statePtr->filehandlers); + TclUnixSetBlockingMode(statePtr->fds.fd, statePtr->cachedBlocking); + + if (error != 0) { + SET_BITS(statePtr->flags, TCP_ASYNC_FAILED); + } /* * We need to forward the writable event that brought us here, bcasue @@ -1084,8 +1321,11 @@ out: * the event mechanism one roundtrip through select(). */ - Tcl_NotifyChannel(state->channel, TCL_WRITABLE); - } else if (status != 0) { + if (statePtr->cachedBlocking == TCL_MODE_NONBLOCKING) { + Tcl_NotifyChannel(statePtr->channel, TCL_WRITABLE); + } + } + if (error != 0) { /* * Failure for either a synchronous connection, or an async one that * failed before it could enter background mode, e.g. because an @@ -1093,6 +1333,7 @@ out: */ if (interp != NULL) { + errno = error; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't open socket: %s", Tcl_PosixError(interp))); } @@ -1129,9 +1370,9 @@ Tcl_OpenTcpClient( * connect. Otherwise we do a blocking * connect. */ { - TcpState *state; + TcpState *statePtr; const char *errorMsg = NULL; - void *addrlist = NULL, *myaddrlist = NULL; + struct addrinfo *addrlist = NULL, *myaddrlist = NULL; char channelName[SOCK_CHAN_LENGTH]; /* @@ -1154,32 +1395,34 @@ Tcl_OpenTcpClient( /* * Allocate a new TcpState for this socket. */ - state = ckalloc(sizeof(TcpState)); - memset(state, 0, sizeof(TcpState)); - state->flags = async ? TCP_ASYNC_CONNECT : 0; - state->cachedBlocking = TCL_MODE_BLOCKING; - state->addrlist = addrlist; - state->myaddrlist = myaddrlist; - state->fds.fd = -1; + + statePtr = ckalloc(sizeof(TcpState)); + memset(statePtr, 0, sizeof(TcpState)); + statePtr->flags = async ? TCP_ASYNC_CONNECT : 0; + statePtr->cachedBlocking = TCL_MODE_BLOCKING; + statePtr->addrlist = addrlist; + statePtr->myaddrlist = myaddrlist; + statePtr->fds.fd = -1; /* * Create a new client socket and wrap it in a channel. */ - if (CreateClientSocket(interp, state) != TCL_OK) { - TcpCloseProc(state, NULL); + + if (TcpConnect(interp, statePtr) != TCL_OK) { + TcpCloseProc(statePtr, NULL); return NULL; } - sprintf(channelName, SOCK_TEMPLATE, (long) state); + sprintf(channelName, SOCK_TEMPLATE, (long) statePtr); - state->channel = Tcl_CreateChannel(&tcpChannelType, channelName, state, - (TCL_READABLE | TCL_WRITABLE)); - if (Tcl_SetChannelOption(interp, state->channel, "-translation", + statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + statePtr, TCL_READABLE | TCL_WRITABLE); + if (Tcl_SetChannelOption(interp, statePtr->channel, "-translation", "auto crlf") == TCL_ERROR) { - Tcl_Close(NULL, state->channel); + Tcl_Close(NULL, statePtr->channel); return NULL; } - return state->channel; + return statePtr->channel; } /* @@ -1202,7 +1445,8 @@ Tcl_Channel Tcl_MakeTcpClientChannel( ClientData sock) /* The socket to wrap up into a channel. */ { - return (Tcl_Channel) TclpMakeTcpClientChannelMode(sock, (TCL_READABLE | TCL_WRITABLE)); + return (Tcl_Channel) TclpMakeTcpClientChannelMode(sock, + TCL_READABLE | TCL_WRITABLE); } /* @@ -1251,7 +1495,7 @@ TclpMakeTcpClientChannelMode( /* *---------------------------------------------------------------------- * - * Tcl_OpenTcpServer -- + * Tcl_OpenTcpServerEx -- * * Opens a TCP server socket and creates a channel around it. * @@ -1266,18 +1510,18 @@ TclpMakeTcpClientChannelMode( */ Tcl_Channel -Tcl_OpenTcpServer( +Tcl_OpenTcpServerEx( Tcl_Interp *interp, /* For error reporting - may be NULL. */ - int port, /* Port number to open. */ + const char *service, /* Port number to open. */ const char *myHost, /* Name of local host. */ + unsigned int flags, /* Flags. */ Tcl_TcpAcceptProc *acceptProc, /* Callback for accepting connections from new * clients. */ ClientData acceptProcData) /* Data for the callback. */ { - int status = 0, sock = -1, reuseaddr = 1, chosenport = 0; - void *addrlist = NULL; - struct addrinfo *addrPtr; /* socket address */ + int status = 0, sock = -1, optvalue, port, chosenport; + struct addrinfo *addrlist = NULL, *addrPtr; /* socket address */ TcpState *statePtr = NULL; char channelName[SOCK_CHAN_LENGTH]; const char *errorMsg = NULL; @@ -1291,7 +1535,45 @@ Tcl_OpenTcpServer( enum { LOOKUP, SOCKET, BIND, LISTEN } howfar = LOOKUP; int my_errno = 0; - if (!TclCreateSocketAddress(interp, &addrlist, myHost, port, 1, &errorMsg)) { + /* + * If we were called with port 0 to listen on a random port number, we + * copy the port number from the first member of the addrinfo list to all + * subsequent members, so that IPv4 and IPv6 listen on the same port. This + * might fail to bind() with EADDRINUSE if a port is free on the first + * address family in the list but already used on the other. In this case + * we revert everything we've done so far and start from scratch hoping + * that next time we'll find a port number that is usable on all address + * families. We try this at most MAXRETRY times to avoid an endless loop + * if all ports are taken. + */ + + int retry = 0; +#define MAXRETRY 10 + + repeat: + if (retry > 0) { + if (statePtr != NULL) { + TcpCloseProc(statePtr, NULL); + statePtr = NULL; + } + if (addrlist != NULL) { + freeaddrinfo(addrlist); + addrlist = NULL; + } + if (retry >= MAXRETRY) { + goto error; + } + } + retry++; + chosenport = 0; + + if (TclSockGetPort(interp, service, "tcp", &port) != TCL_OK) { + errorMsg = "invalid port number"; + goto error; + } + + if (!TclCreateSocketAddress(interp, &addrlist, myHost, port, 1, + &errorMsg)) { my_errno = errno; goto error; } @@ -1306,28 +1588,46 @@ Tcl_OpenTcpServer( } continue; } - + /* * Set the close-on-exec flag so that the socket will not get * inherited by child processes. */ - + fcntl(sock, F_SETFD, FD_CLOEXEC); - + /* * Set kernel space buffering */ - + TclSockMinimumBuffers(INT2PTR(sock), SOCKET_BUFSIZE); - + /* - * Set up to reuse server addresses automatically and bind to the - * specified port. + * Set up to reuse server addresses and/or ports if requested. */ - - (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (char *) &reuseaddr, sizeof(reuseaddr)); - + + if (GOT_BITS(flags, TCL_TCPSERVER_REUSEADDR)) { + optvalue = 1; + (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *) &optvalue, sizeof(optvalue)); + } + + if (GOT_BITS(flags, TCL_TCPSERVER_REUSEPORT)) { +#ifndef SO_REUSEPORT + /* + * If the platform doesn't support the SO_REUSEPORT flag we can't + * do much beside erroring out. + */ + + errorMsg = "SO_REUSEPORT isn't supported by this platform"; + goto error; +#else + optvalue = 1; + (void) setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + (char *) &optvalue, sizeof(optvalue)); +#endif + } + /* * Make sure we use the same port number when opening two server * sockets for IPv4 and IPv6 on a random port. @@ -1342,7 +1642,10 @@ Tcl_OpenTcpServer( } #ifdef IPV6_V6ONLY - /* Missing on: Solaris 2.8 */ + /* + * Missing on: Solaris 2.8 + */ + if (addrPtr->ai_family == AF_INET6) { int v6only = 1; @@ -1356,8 +1659,12 @@ Tcl_OpenTcpServer( if (howfar < BIND) { howfar = BIND; my_errno = errno; - } + } close(sock); + sock = -1; + if (port == 0 && errno == EADDRINUSE) { + goto repeat; + } continue; } if (port == 0 && chosenport == 0) { @@ -1380,13 +1687,17 @@ Tcl_OpenTcpServer( my_errno = errno; } close(sock); + sock = -1; + if (port == 0 && errno == EADDRINUSE) { + goto repeat; + } continue; } if (statePtr == NULL) { /* * Allocate a new TcpState for this socket. */ - + statePtr = ckalloc(sizeof(TcpState)); memset(statePtr, 0, sizeof(TcpState)); statePtr->acceptProc = acceptProc; @@ -1401,12 +1712,12 @@ Tcl_OpenTcpServer( newfds->fd = sock; newfds->statePtr = statePtr; fds = newfds; - + /* * Set up the callback mechanism for accepting connections from new * clients. */ - + Tcl_CreateFileHandler(sock, TCL_READABLE, TcpAccept, fds); } @@ -1465,7 +1776,7 @@ TcpAccept( socklen_t len; /* For accept interface */ char channelName[SOCK_CHAN_LENGTH]; char host[NI_MAXHOST], port[NI_MAXSERV]; - + len = sizeof(addr); newsock = accept(fds->fd, &addr.sa, &len); if (newsock < 0) { @@ -1486,7 +1797,7 @@ TcpAccept( sprintf(channelName, SOCK_TEMPLATE, (long) newSockState); newSockState->channel = Tcl_CreateChannel(&tcpChannelType, channelName, - newSockState, (TCL_READABLE | TCL_WRITABLE)); + newSockState, TCL_READABLE | TCL_WRITABLE); Tcl_SetChannelOption(NULL, newSockState->channel, "-translation", "auto crlf"); |
