/*********************************************************
- * Copyright (C) 2016-2020 VMware, Inc. All rights reserved.
+ * Copyright (C) 2016-2021 VMware, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
}
+/*
+ *----------------------------------------------------------------------------
+ *
+ * AsyncSocket_Peek --
+ *
+ * Similar to AsyncSocket_RecvPartial, AsyncSocket_Peek reads the socket
+ * buffer contents into the provided buffer by registering a callback that
+ * will fire when data becomes available.
+ *
+ * Due to underying poll implementation, peeks are always "partial" ie.
+ * callback returns when less than or equal amount of requested length is
+ * available to read. Peek callers may use recv() to drain smaller amounts
+ * notified by peek-callback and then peek for more data if that helps.
+ *
+ * There are some noteworthy differences compared to Recv():
+ *
+ * - By definition, Recv() drains the socket buffer while Peek() does not
+ *
+ * - Asyncsocket Recv() is post-SSL since it internally calls SSL_Read()
+ * so application always gets decrypted data when entire SSL record is
+ * decrypted. Peek() on the other hand is SSL agnostic; it reads
+ * directly from the underlying host socket and makes no attempt to
+ * decrypt it or check for any data buffered within SSL. So asyncsocket
+ * user doing a recv() followed by peek() may get different results.
+ * That is why is it most safe to use peek() before SSL is setup on the
+ * TCP connection.
+ *
+ * - Peek is one-shot in nature, meaning that peek callbacks are
+ * unregistered from poll once fired.
+ *
+ * Results:
+ * ASOCKERR_*.
+ *
+ * Side effects:
+ * Could register poll callback.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+int
+AsyncSocket_Peek(AsyncSocket *asock, // IN
+ void *buf, // IN (buffer to fill)
+ int len, // IN
+ void *cb, // IN
+ void *cbData) // IN
+{
+ int ret;
+ if (VALID(asock, recv)) {
+ AsyncSocketLock(asock);
+ ret = VT(asock)->peek(asock, buf, len, cb, cbData);
+ AsyncSocketUnlock(asock);
+ } else {
+ ret = ASOCKERR_INVAL;
+ }
+ return ret;
+}
+
+
/*
*----------------------------------------------------------------------------
*
*/
#define ADDR_STRING_LEN (INET6_ADDRSTRLEN + 2 + PORT_STRING_LEN)
+typedef enum {
+ ASOCK_FLAG_PEEK = 1
+} AsockFlags;
+
+#define ASOCK_PEEK(a) (a->flags & ASOCK_FLAG_PEEK)
/* Local types. */
int fd;
} passFd;
+ AsockFlags flags;
+
} AsyncTCPSocket;
static int AsyncTCPSocketRecv(AsyncSocket *asock,
void *buf, int len, Bool partial, void *cb, void *cbData);
+static int AsyncTCPSocketPeek(AsyncSocket *asock, void *buf, int len, void *cb,
+ void *cbData);
static int AsyncTCPSocketRecvPassedFd(AsyncSocket *asock, void *buf, int len,
void *cb, void *cbData);
static int AsyncTCPSocketGetReceivedFd(AsyncSocket *asock);
AsyncTCPSocketDoOneMsg,
AsyncTCPSocketWaitForConnection,
AsyncTCPSocketWaitForReadMultiple,
+ AsyncTCPSocketPeek,
AsyncTCPSocketDestroy
};
AsyncTCPSocketRegisterRecvCb(AsyncTCPSocket *asock) // IN:
{
int retVal = ASOCKERR_SUCCESS;
+ Bool peek = asock->flags & ASOCK_FLAG_PEEK;
if (!asock->recvCb) {
VMwareStatus pollStatus;
* Register the Poll callback
*/
- TCPSOCKLOG(3, asock, "installing recv periodic poll callback\n");
+ TCPSOCKLOG(3, asock, "installing %s periodic poll callback\n",
+ peek ? "peek" : "recv");
pollStatus = AsyncTCPSocketPollAdd(asock, TRUE,
POLL_FLAG_READ | POLL_FLAG_PERIODIC,
asock->internalRecvFn);
if (pollStatus != VMWARE_STATUS_SUCCESS) {
- TCPSOCKWARN(asock, "failed to install recv callback!\n");
+ TCPSOCKWARN(asock, "failed to install %s callback!\n",
+ peek ? "peek" : "recv");
retVal = ASOCKERR_POLL;
goto out;
}
asock->recvCb = TRUE;
}
- if (AsyncTCPSocketHasDataPending(asock) && !asock->inRecvLoop) {
+ /*
+ * !peek comes before other checks because SSL may not be initialized for
+ * peeks, and also because peek ignores data buffered in SSL.
+ */
+ if (!peek && AsyncTCPSocketHasDataPending(asock) && !asock->inRecvLoop) {
TCPSOCKLOG(0, asock, "installing recv RTime poll callback\n");
if (AsyncTCPSocketPollAdd(asock, FALSE, 0, asock->internalRecvFn, 0) !=
VMWARE_STATUS_SUCCESS) {
return ASOCKERR_INVAL;
}
+ /*
+ * Reset peek flag which may be set from previous peek(), to stop peeking.
+ * This is the only place we need to reset it.
+ */
+ asock->flags &= ~ASOCK_FLAG_PEEK;
+
retVal = AsyncTCPSocketRegisterRecvCb(asock);
if (retVal != ASOCKERR_SUCCESS) {
return retVal;
AsyncSocketSetRecvBuf(BaseSocket(asock), buf, len, fireOnPartial,
cb, cbData);
+
return ASOCKERR_SUCCESS;
}
*
* AsyncTCPSocketRecvPassedFd --
*
- * See AsyncTCPSocket_Recv. Besides that it allows for receiving one
- * file descriptor...
+ * See AsyncTCPSocketRecv. Besides that it allows for receiving one file
+ * descriptor...
*
* Results:
* ASOCKERR_*.
}
+/*
+ *----------------------------------------------------------------------------
+ *
+ * AsyncTCPSocketPeek --
+ *
+ * Peek into the socket buffer. Similar to AsyncTCPSocketRecv except that
+ * this routine does not assume SSL state is initialized for the socket.
+ * Unlike recv, peek does not consider data buffered in the SSL layer. So
+ * peek() is only useful pre-SSL. This is in contrast to tcp asyncsocket
+ * recv() that only does SSL_Read; hence recv() is only suitable post-SSL.
+ *
+ * ASOCK_FLAG_PEEK flag is reset in recv() prior to callback registration.
+ *
+ * Results:
+ * ASOCKERR_*.
+ *
+ * Side effects:
+ * Could register poll callback.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static int
+AsyncTCPSocketPeek(AsyncSocket *base, // IN:
+ void *buf, // IN: unused
+ int len, // IN: unused
+ void *cb, // IN:
+ void *cbData) // IN:
+{
+ AsyncTCPSocket *asock = TCPSocket(base);
+ int retVal;
+
+ ASSERT(AsyncTCPSocketIsLocked(asock));
+
+ // Disallow peek with pending blocking recv
+ if (UNLIKELY(asock->inBlockingRecv && !asock->inRecvLoop)) {
+ TCPSOCKWARN(asock, "%s: Cannot peek due to blocking recv\n",
+ __FUNCTION__);
+ return ASOCKERR_BUSY;
+ }
+ /*
+ * Disallow peek while in recv callback. We can support this if needed in
+ * future just like we allow the reverse today (recv from peek callback).
+ */
+ if (asock->inRecvLoop && !ASOCK_PEEK(asock)) {
+ TCPSOCKWARN(asock, "%s: Cannot peek from recv callback\n", __FUNCTION__);
+ return ASOCKERR_BUSY;
+ }
+
+ if (base->pollParams.iPoll != NULL) {
+ Warning(ASOCKPREFIX "Peek not supported for IVmdbPoll!\n");
+ return ASOCKERR_INVAL;
+ }
+
+ if (buf == NULL || cb == NULL || len <= 0) {
+ Warning(ASOCKPREFIX "Peek called with invalid arguments!\n");
+ return ASOCKERR_INVAL;
+ }
+
+ if (AsyncTCPSocketGetState(asock) != AsyncSocketConnected) {
+ TCPSOCKWARN(asock, "peek called but state is not connected!\n");
+ return ASOCKERR_NOTCONNECTED;
+ }
+
+ /*
+ * Set ASOCK_FLAG_PEEK flag to differentiate peek from recv in the common
+ * callback. The flag will be reset on next recv in AsyncTCPSocketRecv.
+ */
+ asock->flags |= ASOCK_FLAG_PEEK;
+
+ retVal = AsyncTCPSocketRegisterRecvCb(asock);
+ if (retVal == ASOCKERR_SUCCESS) {
+ AsyncSocketSetRecvBuf(BaseSocket(asock), buf, len, TRUE, cb, cbData);
+ } else {
+ TCPSOCKWARN(asock, "%s: Peek failed with error %d: %s \n", __FUNCTION__,
+ retVal, Err_Errno2String(retVal));
+ asock->flags &= ~ASOCK_FLAG_PEEK;
+ }
+ return retVal;
+}
+
+
/*
*----------------------------------------------------------------------------
*
s->inRecvLoop = TRUE;
do {
-
/*
* Try to read the remaining bytes to complete the current recv request.
*/
}
+/*
+ *----------------------------------------------------------------------------
+ *
+ * AsyncTCPSocketFillPeekBuffer --
+ *
+ * Called when asock has data ready to peek via the poll callback.
+ * Internally calls recv system call with MSG_PEEK flag.
+ *
+ * Similar to AsyncTCPSocketFillRecvBuffer with key differences:
+ *
+ * - peek does non-SSL (clear-text pre-SSL or encrypted) retrieval from the
+ * socket, whereas TCPSocketFillRecvBuffer does an SSL_Read. peek does
+ * not take into account any data buffered at the SSL layer, so only
+ * makes sense prior to SSL setup.
+ *
+ * - peek reads from the head of socket buffer, but does not drain it so
+ * subsequent recv/peek will still read the same data.
+ *
+ * - if peek and recv async requests overlap, the latest requests
+ * implicitly cancels the existing one.
+ *
+ * - nested peeks of same or lesser length into the same buffer are
+ * pointless and dropped. Nested recv() transition to recv callback.
+ *
+ * Results:
+ * Same as AsyncTCPSocketFillRecvBuffer().
+ *
+ * Side effects:
+ * Reads data but does not remove it from the socket buffer, could fire
+ * recv completion or trigger socket destruction.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static int
+AsyncTCPSocketFillPeekBuffer(AsyncTCPSocket *s, // IN
+ Bool *retry) // OUT
+{
+ int recvd;
+ int needed;
+ int result;
+ int sysErr;
+ int length;
+ void *buffer;
+ int loopCount = 0;
+
+#define MAX_NESTED_PEEKS 8
+
+ ASSERT(AsyncTCPSocketIsLocked(s));
+ ASSERT(AsyncTCPSocketGetState(s) == AsyncSocketConnected);
+ ASSERT(s->flags & ASOCK_FLAG_PEEK);
+
+ needed = s->base.recvLen - s->base.recvPos;
+ if (!s->base.recvBuf && needed == 0) {
+ s->flags &= ~ASOCK_FLAG_PEEK;
+ return ASOCKERR_SUCCESS;
+ }
+
+ ASSERT(needed > 0);
+
+ AsyncTCPSocketAddRef(s);
+
+ s->inRecvLoop = TRUE;
+
+ // peek loop to service nested peeks
+ do {
+ TCPSOCKLOG(1, s, "peeking for %d bytes\n", needed);
+ length = s->base.recvLen;
+ buffer = s->base.recvBuf;
+
+ // recv() with MSG_PEEK
+ recvd = recv(SSL_GetFd(s->sslSock),
+ (uint8 *) s->base.recvBuf + s->base.recvPos, needed,
+ MSG_PEEK);
+ TCPSOCKLOG(0, s, "peek fetched %d of %d bytes requested\n", recvd,
+ needed);
+
+ if (recvd > 0) {
+ s->base.recvPos += recvd;
+ if (AsyncSocketCheckAndDispatchRecv(&s->base, &result)) {
+ goto exit;
+ }
+ ASSERT(s->base.recvPos == 0);
+ } else if (recvd == 0) {
+ TCPSOCKLG0(s, "peek detected client closed connection\n");
+ /*
+ * We treat this as an error so that the owner can detect closing
+ * of connection by peer (via the error handler callback).
+ */
+ result = ASOCKERR_REMOTE_DISCONNECT;
+ goto exit;
+ } else if ((sysErr = ASOCK_LASTERROR()) == ASOCK_EWOULDBLOCK) {
+ TCPSOCKLOG(4, s, "peek would block\n");
+ result = ASOCKERR_SUCCESS;
+ break;
+ } else {
+ TCPSOCKLG0(s, "peek error %d: %s\n", sysErr, Err_Errno2String(sysErr));
+ s->genericErrno = sysErr;
+ result = ASOCKERR_GENERIC;
+ goto exit;
+ }
+
+ needed = s->base.recvLen - s->base.recvPos;
+
+ // Handle recv from peek callback using retry loop of the caller
+ if (!ASOCK_PEEK(s)) {
+ TCPSOCKLOG(0, s, "recv during peek, retry recv callback");
+ *retry = TRUE;
+ break;
+ }
+
+ /*
+ * Nested peeks for same or lesser lengths into the same buffer are
+ * pointless and unexpected, so we use it as a terminating condition to
+ * break the loop and also unregister peek callback.
+ */
+ if (s->base.recvLen <= length && s->base.recvBuf == buffer) {
+ TCPSOCKLG0(s, "cancelling one-shot peek op");
+ s->flags &= ~ASOCK_FLAG_PEEK;
+ AsyncTCPSocketCancelRecvCb(s);
+ break;
+ }
+ } while (needed && (++loopCount < MAX_NESTED_PEEKS));
+
+ // flag heavy peek nesting
+ ASSERT(loopCount < MAX_NESTED_PEEKS);
+
+ result = ASOCKERR_SUCCESS;
+
+exit:
+ s->inRecvLoop = FALSE;
+ AsyncTCPSocketRelease(s);
+ return result;
+}
+
+
/*
*----------------------------------------------------------------------------
*
{
AsyncTCPSocket *asock = clientData;
int error;
+ Bool recv = TRUE;
ASSERT(asock);
ASSERT(AsyncTCPSocketIsLocked(asock));
AsyncTCPSocketAddRef(asock);
- error = AsyncTCPSocketFillRecvBuffer(asock);
+ if (UNLIKELY(ASOCK_PEEK(asock))) {
+ recv = FALSE;
+ error = AsyncTCPSocketFillPeekBuffer(asock, &recv);
+ }
+
+ if (LIKELY(recv)) {
+ error = AsyncTCPSocketFillRecvBuffer(asock);
+ }
+
if (error == ASOCKERR_GENERIC || error == ASOCKERR_REMOTE_DISCONNECT) {
AsyncTCPSocketHandleError(asock, error);
}
!MXUser_IsCurThreadHoldingRecLock(
AsyncTCPSocketPollParams(asock)->lock));
+ /*
+ * peek() has not been needed for IVmdbPoll so it is not tested/supported
+ * in this callback, unlike the regular socket poll recv callback
+ * (AsyncTCPSocketRecvCallback). ASSERT added for caution.
+ */
+ ASSERT(!(asock->flags & ASOCK_FLAG_PEEK));
+
AsyncTCPSocketLock(asock);
if (asock->recvCbTimer) {
/* IVmdbPoll only has periodic timer callbacks. */