netwerk/base/IOActivityMonitor.cpp
author Dennis Jackson <djackson@mozilla.com>
Sun, 26 Mar 2023 07:31:40 +0000
changeset 657950 dee1eb3308521b4cb7c8a3afe44520efcf582650
parent 635981 c5004298ec0288332b684cad732c4cc08c8fa909
permissions -rw-r--r--
Bug 1822876: Add H3 ECH Telemetry. r=kershaw,necko-reviewers This patch adds telemetry which records when H3 connections succeed / fail and what kind of ECH they used. Our H3 ECH tests are extended to test these different modes and that the telemetry is recorded correctly. Differential Revision: https://phabricator.services.mozilla.com/D172813

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "IOActivityMonitor.h"
#include "nsPrintfCString.h"
#include "nsSocketTransport2.h"
#include "nsSocketTransportService2.h"
#include "nsThreadUtils.h"
#include "mozilla/Atomics.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/Promise.h"
#include "prerror.h"
#include "prio.h"
#include "prmem.h"
#include <vector>

using namespace mozilla;
using namespace mozilla::net;

mozilla::StaticRefPtr<IOActivityMonitor> gInstance;
static Atomic<bool> gActivated(false);
static PRDescIdentity sNetActivityMonitorLayerIdentity;
static PRIOMethods sNetActivityMonitorLayerMethods;
static PRIOMethods* sNetActivityMonitorLayerMethodsPtr = nullptr;

// Maximum number of activities entries in the monitoring class
#define MAX_ACTIVITY_ENTRIES 1000

// ActivityMonitorSecret is stored in the activity monitor layer
// and provides a method to get the location.
//
// A location can be :
// - a TCP or UDP socket. The form will be socket://ip:port
// - a File. The form will be file://path
//
// For other cases, the location will be fd://number
class ActivityMonitorSecret final {
 public:
  // constructor used for sockets
  explicit ActivityMonitorSecret(PRFileDesc* aFd) {
    mFd = aFd;
    mLocationSet = false;
  }

  // constructor used for files
  explicit ActivityMonitorSecret(PRFileDesc* aFd, const char* aLocation) {
    mFd = aFd;
    mLocation.AppendPrintf("file://%s", aLocation);
    mLocationSet = true;
  }

  nsCString getLocation() {
    if (!mLocationSet) {
      LazySetLocation();
    }
    return mLocation;
  }

 private:
  // Called to set the location using the FD on the first getLocation() usage
  // which is typically when a socket is opened. If done earlier, at
  // construction time, the host won't be bound yet.
  //
  // If the location is a file, it needs to be initialized in the
  // constructor.
  void LazySetLocation() {
    mLocationSet = true;
    PRFileDesc* extract = mFd;
    while (PR_GetDescType(extract) == PR_DESC_LAYERED) {
      if (!extract->lower) {
        break;
      }
      extract = extract->lower;
    }

    PRDescType fdType = PR_GetDescType(extract);
    // we should not use LazySetLocation for files
    MOZ_ASSERT(fdType != PR_DESC_FILE);

    switch (fdType) {
      case PR_DESC_SOCKET_TCP:
      case PR_DESC_SOCKET_UDP: {
        mLocation.AppendPrintf("socket://");
        PRNetAddr addr;
        PRStatus status = PR_GetSockName(mFd, &addr);
        if (NS_WARN_IF(status == PR_FAILURE)) {
          mLocation.AppendPrintf("unknown");
          break;
        }

        // grabbing the host
        char netAddr[mozilla::net::kNetAddrMaxCStrBufSize] = {0};
        status = PR_NetAddrToString(&addr, netAddr, sizeof(netAddr) - 1);
        if (NS_WARN_IF(status == PR_FAILURE) || netAddr[0] == 0) {
          mLocation.AppendPrintf("unknown");
          break;
        }
        mLocation.Append(netAddr);

        // adding the port
        uint16_t port;
        if (addr.raw.family == PR_AF_INET) {
          port = addr.inet.port;
        } else {
          port = addr.ipv6.port;
        }
        mLocation.AppendPrintf(":%d", port);
      } break;

      // for all other cases, we just send back fd://<value>
      default: {
        mLocation.AppendLiteral("fd://");
        mLocation.AppendInt(PR_FileDesc2NativeHandle(mFd));
      }
    }  // end switch
  }

 private:
  nsCString mLocation;
  bool mLocationSet;
  PRFileDesc* mFd;
};

// FileDesc2Location converts a PRFileDesc into a "location" by
// grabbing the ActivityMonitorSecret in layer->secret
static nsAutoCString FileDesc2Location(PRFileDesc* fd) {
  nsAutoCString location;
  PRFileDesc* monitorLayer =
      PR_GetIdentitiesLayer(fd, sNetActivityMonitorLayerIdentity);
  if (!monitorLayer) {
    location.AppendPrintf("unknown");
    return location;
  }

  ActivityMonitorSecret* secret = (ActivityMonitorSecret*)monitorLayer->secret;
  location.AppendPrintf("%s", secret->getLocation().get());
  return location;
}

//
// Wrappers around the socket APIS
//
static PRStatus nsNetMon_Connect(PRFileDesc* fd, const PRNetAddr* addr,
                                 PRIntervalTime timeout) {
  return fd->lower->methods->connect(fd->lower, addr, timeout);
}

static PRStatus nsNetMon_Close(PRFileDesc* fd) {
  if (!fd) {
    return PR_FAILURE;
  }
  PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
  MOZ_RELEASE_ASSERT(
      layer && layer->identity == sNetActivityMonitorLayerIdentity,
      "NetActivityMonitor Layer not on top of stack");

  if (layer->secret) {
    delete (ActivityMonitorSecret*)layer->secret;
    layer->secret = nullptr;
  }
  layer->dtor(layer);
  return fd->methods->close(fd);
}

static int32_t nsNetMon_Read(PRFileDesc* fd, void* buf, int32_t len) {
  int32_t ret = fd->lower->methods->read(fd->lower, buf, len);
  if (ret >= 0) {
    IOActivityMonitor::Read(fd, len);
  }
  return ret;
}

static int32_t nsNetMon_Write(PRFileDesc* fd, const void* buf, int32_t len) {
  int32_t ret = fd->lower->methods->write(fd->lower, buf, len);
  if (ret > 0) {
    IOActivityMonitor::Write(fd, len);
  }
  return ret;
}

static int32_t nsNetMon_Writev(PRFileDesc* fd, const PRIOVec* iov, int32_t size,
                               PRIntervalTime timeout) {
  int32_t ret = fd->lower->methods->writev(fd->lower, iov, size, timeout);
  if (ret > 0) {
    IOActivityMonitor::Write(fd, size);
  }
  return ret;
}

static int32_t nsNetMon_Recv(PRFileDesc* fd, void* buf, int32_t amount,
                             int flags, PRIntervalTime timeout) {
  int32_t ret =
      fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout);
  if (ret > 0) {
    IOActivityMonitor::Read(fd, amount);
  }
  return ret;
}

static int32_t nsNetMon_Send(PRFileDesc* fd, const void* buf, int32_t amount,
                             int flags, PRIntervalTime timeout) {
  int32_t ret =
      fd->lower->methods->send(fd->lower, buf, amount, flags, timeout);
  if (ret > 0) {
    IOActivityMonitor::Write(fd, amount);
  }
  return ret;
}

static int32_t nsNetMon_RecvFrom(PRFileDesc* fd, void* buf, int32_t amount,
                                 int flags, PRNetAddr* addr,
                                 PRIntervalTime timeout) {
  int32_t ret = fd->lower->methods->recvfrom(fd->lower, buf, amount, flags,
                                             addr, timeout);
  if (ret > 0) {
    IOActivityMonitor::Read(fd, amount);
  }
  return ret;
}

static int32_t nsNetMon_SendTo(PRFileDesc* fd, const void* buf, int32_t amount,
                               int flags, const PRNetAddr* addr,
                               PRIntervalTime timeout) {
  int32_t ret =
      fd->lower->methods->sendto(fd->lower, buf, amount, flags, addr, timeout);
  if (ret > 0) {
    IOActivityMonitor::Write(fd, amount);
  }
  return ret;
}

static int32_t nsNetMon_AcceptRead(PRFileDesc* listenSock,
                                   PRFileDesc** acceptedSock,
                                   PRNetAddr** peerAddr, void* buf,
                                   int32_t amount, PRIntervalTime timeout) {
  int32_t ret = listenSock->lower->methods->acceptread(
      listenSock->lower, acceptedSock, peerAddr, buf, amount, timeout);
  if (ret > 0) {
    IOActivityMonitor::Read(listenSock, amount);
  }
  return ret;
}

//
// Class IOActivityMonitor
//
NS_IMPL_ISUPPORTS(IOActivityMonitor, nsINamed)

IOActivityMonitor::IOActivityMonitor() : mLock("IOActivityMonitor::mLock") {
  RefPtr<IOActivityMonitor> mon(gInstance);
  MOZ_ASSERT(!mon, "multiple IOActivityMonitor instances!");
}

// static
void IOActivityMonitor::RequestActivities(dom::Promise* aPromise) {
  MOZ_ASSERT(aPromise);
  RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
  if (!mon) {
    aPromise->MaybeReject(NS_ERROR_FAILURE);
    return;
  }

  mon->RequestActivitiesInternal(aPromise);
}

void IOActivityMonitor::RequestActivitiesInternal(dom::Promise* aPromise) {
  nsresult result = NS_OK;
  FallibleTArray<dom::IOActivityDataDictionary> activities;

  {
    mozilla::MutexAutoLock lock(mLock);
    // Remove inactive activities
    for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
      dom::IOActivityDataDictionary* activity = &iter.Data();
      if (activity->mRx == 0 && activity->mTx == 0) {
        iter.Remove();
      } else {
        if (NS_WARN_IF(!activities.AppendElement(iter.Data(), fallible))) {
          result = NS_ERROR_OUT_OF_MEMORY;
          break;
        }
      }
    }
  }

  if (NS_WARN_IF(NS_FAILED(result))) {
    aPromise->MaybeReject(result);
    return;
  }
  aPromise->MaybeResolve(activities);
}

NS_IMETHODIMP
IOActivityMonitor::GetName(nsACString& aName) {
  aName.AssignLiteral("IOActivityMonitor");
  return NS_OK;
}

// static
bool IOActivityMonitor::IsActive() { return gActivated; }

// static
already_AddRefed<IOActivityMonitor> IOActivityMonitor::Get() {
  if (!gActivated) {
    return nullptr;
  }

  RefPtr<IOActivityMonitor> mon = gInstance;
  return mon.forget();
}

nsresult IOActivityMonitor::Init() {
  if (IsActive()) {
    return NS_ERROR_ALREADY_INITIALIZED;
  }
  RefPtr<IOActivityMonitor> mon = new IOActivityMonitor();
  nsresult rv = mon->InitInternal();
  if (NS_SUCCEEDED(rv)) {
    gInstance = mon;
    ClearOnShutdown(&gInstance);
    gActivated = true;
  }
  return rv;
}

nsresult IOActivityMonitor::InitInternal() {
  // wraps the socket APIs
  if (!sNetActivityMonitorLayerMethodsPtr) {
    sNetActivityMonitorLayerIdentity =
        PR_GetUniqueIdentity("network activity monitor layer");
    sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods();
    sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect;
    sNetActivityMonitorLayerMethods.read = nsNetMon_Read;
    sNetActivityMonitorLayerMethods.write = nsNetMon_Write;
    sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev;
    sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv;
    sNetActivityMonitorLayerMethods.send = nsNetMon_Send;
    sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom;
    sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo;
    sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead;
    sNetActivityMonitorLayerMethods.close = nsNetMon_Close;
    sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods;
  }

  return NS_OK;
}

nsresult IOActivityMonitor::Shutdown() {
  RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
  if (!mon) {
    return NS_ERROR_NOT_INITIALIZED;
  }
  return mon->ShutdownInternal();
}

nsresult IOActivityMonitor::ShutdownInternal() {
  mozilla::MutexAutoLock lock(mLock);
  mActivities.Clear();
  gActivated = false;
  return NS_OK;
}

nsresult IOActivityMonitor::MonitorSocket(PRFileDesc* aFd) {
  RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
  if (!mon) {
    return NS_OK;
  }
  PRFileDesc* layer;
  PRStatus status;
  layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
                               sNetActivityMonitorLayerMethodsPtr);
  if (!layer) {
    return NS_ERROR_FAILURE;
  }

  ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd);
  layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
  status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);

  if (status == PR_FAILURE) {
    delete secret;
    PR_Free(layer);  // PR_CreateIOLayerStub() uses PR_Malloc().
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

nsresult IOActivityMonitor::MonitorFile(PRFileDesc* aFd, const char* aPath) {
  RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
  if (!mon) {
    return NS_OK;
  }
  PRFileDesc* layer;
  PRStatus status;
  layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
                               sNetActivityMonitorLayerMethodsPtr);
  if (!layer) {
    return NS_ERROR_FAILURE;
  }

  ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd, aPath);
  layer->secret = reinterpret_cast<PRFilePrivate*>(secret);

  status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
  if (status == PR_FAILURE) {
    delete secret;
    PR_Free(layer);  // PR_CreateIOLayerStub() uses PR_Malloc().
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

bool IOActivityMonitor::IncrementActivity(const nsACString& aLocation,
                                          uint32_t aRx, uint32_t aTx) {
  mLock.AssertCurrentThreadOwns();
  return mActivities.WithEntryHandle(aLocation, fallible, [&](auto&& entry) {
    if (!entry) return false;

    if (*entry) {
      // already registered
      entry->Data().mTx += aTx;
      entry->Data().mRx += aRx;
    } else {
      // Creating a new IOActivity. Notice that mActivities
      // will grow indefinitely, which is OK since we won't
      // have but a few hundreds entries at the most, but we
      // want to assert we have at the most 1000 entries
      MOZ_ASSERT(mActivities.Count() <= MAX_ACTIVITY_ENTRIES);

      dom::IOActivityDataDictionary activity;
      activity.mLocation.Assign(aLocation);
      activity.mTx = aTx;
      activity.mRx = aRx;

      entry->Insert(std::move(activity));
    }

    return true;
  });
}

nsresult IOActivityMonitor::Write(const nsACString& aLocation,
                                  uint32_t aAmount) {
  RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
  if (!mon) {
    return NS_ERROR_FAILURE;
  }
  return mon->WriteInternal(aLocation, aAmount);
}

nsresult IOActivityMonitor::Write(PRFileDesc* fd, uint32_t aAmount) {
  RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
  if (!mon) {
    return NS_ERROR_FAILURE;
  }
  return mon->Write(FileDesc2Location(fd), aAmount);
}

nsresult IOActivityMonitor::WriteInternal(const nsACString& aLocation,
                                          uint32_t aAmount) {
  mozilla::MutexAutoLock lock(mLock);
  if (!IncrementActivity(aLocation, aAmount, 0)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

nsresult IOActivityMonitor::Read(PRFileDesc* fd, uint32_t aAmount) {
  RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
  if (!mon) {
    return NS_ERROR_FAILURE;
  }
  return mon->Read(FileDesc2Location(fd), aAmount);
}

nsresult IOActivityMonitor::Read(const nsACString& aLocation,
                                 uint32_t aAmount) {
  RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
  if (!mon) {
    return NS_ERROR_FAILURE;
  }
  return mon->ReadInternal(aLocation, aAmount);
}

nsresult IOActivityMonitor::ReadInternal(const nsACString& aLocation,
                                         uint32_t aAmount) {
  mozilla::MutexAutoLock lock(mLock);
  if (!IncrementActivity(aLocation, 0, aAmount)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}