]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
Common source file change not directly applicable to open-vm-tools.
authorJohn Wolfe <jwolfe@vmware.com>
Thu, 20 May 2021 18:38:38 +0000 (11:38 -0700)
committerJohn Wolfe <jwolfe@vmware.com>
Thu, 20 May 2021 18:38:38 +0000 (11:38 -0700)
Introduce peek() to the asyncsocket API, only supported by TCP vtable.

Peeks are similar to recv(), except that they do not drain the socket
after reading.  Subsequent peek/recv reads the same data back.  However
since recv does SSL_Read, a recv() following a peek() may not get the same
data as peek() after SSL is initialized.  This is not a problem when
peeks are done before SSL setup.

Implementation notes:

- peek is a one-shot operation. The poll callback is unregistered
  once it fires (recv keeps the callback until recv is cancelled).
- non-partial peek is not supported, so the peek callback will be fired when
  any amount of data less than or equal amount of the requested length is
  available in the socket buffer.
- It is possible to invoke recv() or peek() recursively from within the peek()
  callback.  A peek is disallowed from within the recv() callback.

open-vm-tools/lib/asyncsocket/asyncSocketBase.c
open-vm-tools/lib/asyncsocket/asyncSocketInterface.c
open-vm-tools/lib/asyncsocket/asyncSocketVTable.h
open-vm-tools/lib/asyncsocket/asyncsocket.c
open-vm-tools/lib/include/asyncsocket.h

index f2f00cd361b73ece6baf8738c89c166aa1e6ad47..f8e0cbbbf04658c76a358bc91ec864ac1301f6e7 100644 (file)
@@ -548,7 +548,7 @@ AsyncSocketCheckAndDispatchRecv(AsyncSocket *s,  // IN
        * We do this dance in case the handler frees the buffer (so
        * that there's no possible window where there are dangling
        * references here.  Obviously if the handler frees the buffer,
-       * but them fails to register a new one, we'll put back the
+       * but then fails to register a new one, we'll put back the
        * dangling reference in the automatic reset case below, but
        * there's currently a limit to how far we go to shield clients
        * who use our API in a broken way.
@@ -650,7 +650,7 @@ AsyncSocketSetRecvBuf(AsyncSocket *asock,  // IN:
 /*
  *-----------------------------------------------------------------------------
  *
- * WebSocketCancelRecv --
+ * AsyncSocketCancelRecv --
  *
  *    Call this function if you know what you are doing. This should
  *    be called if you want to synchronously receive the outstanding
index 77e69249f92ebcaa1fd09d62dafff8aafd7b4424..fbf8d5a8411832a182f5868f09ed3189f8d5fa96 100644 (file)
@@ -1,5 +1,5 @@
 /*********************************************************
- * 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
@@ -984,6 +984,64 @@ AsyncSocket_RecvPartial(AsyncSocket *asock,         // IN
 }
 
 
+/*
+ *----------------------------------------------------------------------------
+ *
+ * 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;
+}
+
+
 /*
  *----------------------------------------------------------------------------
  *
index 7d0c2cf3677bd8b66d944c1343158ee83c3403f6..a69b6567733110e1878f53037d00c0f9619e3767 100644 (file)
@@ -1,5 +1,5 @@
 /*********************************************************
- * Copyright (C) 2011,2014-2017,2019-2020 VMware, Inc. All rights reserved.
+ * Copyright (C) 2011,2014-2017,2019-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
@@ -133,7 +133,7 @@ typedef struct AsyncSocketVTable {
    int (*waitForConnection)(AsyncSocket *s, int timeoutMS);
    int (*waitForReadMultiple)(AsyncSocket **asock, int numSock, int timeoutMS,
                               int *outIdx);
-
+   int (*peek)(AsyncSocket *asock, void *buf, int len, void *cb, void *cbData);
 
    /*
     * Internal function, called when refcount drops to zero:
index 0d3fab55155967f623efdbcaa2fdeffa4a2c5239..05147d2e561d31effacb5f22bbc3116e2042fdf8 100644 (file)
  */
 #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. */
 
@@ -232,6 +237,8 @@ typedef struct AsyncTCPSocket {
       int fd;
    } passFd;
 
+   AsockFlags flags;
+
 } AsyncTCPSocket;
 
 
@@ -332,6 +339,8 @@ static void AsyncTCPSocketCancelRecvCb(AsyncTCPSocket *asock);
 
 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);
@@ -420,6 +429,7 @@ static const AsyncSocketVTable asyncTCPSocketVTable = {
    AsyncTCPSocketDoOneMsg,
    AsyncTCPSocketWaitForConnection,
    AsyncTCPSocketWaitForReadMultiple,
+   AsyncTCPSocketPeek,
    AsyncTCPSocketDestroy
 };
 
@@ -2499,6 +2509,7 @@ static int
 AsyncTCPSocketRegisterRecvCb(AsyncTCPSocket *asock) // IN:
 {
    int retVal = ASOCKERR_SUCCESS;
+   Bool peek = asock->flags & ASOCK_FLAG_PEEK;
 
    if (!asock->recvCb) {
       VMwareStatus pollStatus;
@@ -2507,21 +2518,27 @@ AsyncTCPSocketRegisterRecvCb(AsyncTCPSocket *asock) // IN:
        * 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) {
@@ -2621,6 +2638,12 @@ AsyncTCPSocketRecv(AsyncSocket *base,   // IN:
       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;
@@ -2628,6 +2651,7 @@ AsyncTCPSocketRecv(AsyncSocket *base,   // IN:
 
    AsyncSocketSetRecvBuf(BaseSocket(asock), buf, len, fireOnPartial,
                          cb, cbData);
+
    return ASOCKERR_SUCCESS;
 }
 
@@ -2637,8 +2661,8 @@ AsyncTCPSocketRecv(AsyncSocket *base,   // IN:
  *
  * 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_*.
@@ -2681,6 +2705,88 @@ AsyncTCPSocketRecvPassedFd(AsyncSocket *base,   // IN/OUT: socket
 }
 
 
+/*
+ *----------------------------------------------------------------------------
+ *
+ * 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;
+}
+
+
 /*
  *----------------------------------------------------------------------------
  *
@@ -3578,7 +3684,6 @@ AsyncTCPSocketFillRecvBuffer(AsyncTCPSocket *s)         // IN
    s->inRecvLoop = TRUE;
 
    do {
-
       /*
        * Try to read the remaining bytes to complete the current recv request.
        */
@@ -3668,6 +3773,142 @@ exit:
 }
 
 
+/*
+ *----------------------------------------------------------------------------
+ *
+ * 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;
+}
+
+
 /*
  *----------------------------------------------------------------------------
  *
@@ -5215,13 +5456,22 @@ AsyncTCPSocketRecvCallback(void *clientData)         // IN
 {
    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);
    }
@@ -5263,6 +5513,13 @@ AsyncTCPSocketIPollRecvCallback(void *clientData)  // IN:
           !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. */
index 1d5bedc0ab1ffa207e1ea63d6768866964fcbbca..47b5b873f8635e18f28bc71fc13cb86a960f9eb2 100644 (file)
@@ -657,6 +657,11 @@ int AsyncSocket_Recv(AsyncSocket *asock, void *buf, int len, void *cb, void *cbD
 int AsyncSocket_RecvPartial(AsyncSocket *asock, void *buf, int len,
                             void *cb, void *cbData);
 
+int AsyncSocket_Peek(AsyncSocket *asock, void *buf, int len, void *cb, void *cbData);
+
+int AsyncSocket_PeekPartial(AsyncSocket *asock, void *buf, int len,
+                            void *cb, void *cbData);
+
 /*
  * Specify the amount of data to receive and the receive function to call.
  */