/* $Id: PDMAllNetShaper.cpp $ */ /** @file * PDM Network Shaper - Limit network traffic according to bandwidth group settings. */ /* * Copyright (C) 2011-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_NET_SHAPER #include #include "PDMInternal.h" #include #include #include #include /** * Obtain bandwidth in a bandwidth group. * * @returns True if bandwidth was allocated, false if not. * @param pVM The cross context VM structure. * @param pFilter Pointer to the filter that allocates bandwidth. * @param cbTransfer Number of bytes to allocate. */ VMM_INT_DECL(bool) PDMNetShaperAllocateBandwidth(PVMCC pVM, PPDMNSFILTER pFilter, size_t cbTransfer) { AssertPtrReturn(pFilter, true); /* * If we haven't got a valid bandwidth group, we always allow the traffic. */ bool fAllowed = true; uint32_t iGroup = ASMAtomicUoReadU32(&pFilter->iGroup); if (iGroup != 0) { if (iGroup <= RT_MIN(pVM->pdm.s.cNsGroups, RT_ELEMENTS(pVM->pdm.s.aNsGroups))) { PPDMNSBWGROUP pGroup = &pVM->pdm.s.aNsGroups[iGroup - 1]; int rc = PDMCritSectEnter(pVM, &pGroup->Lock, VINF_TRY_AGAIN); if (rc == VINF_SUCCESS) { uint64_t const cbPerSecMax = pGroup->cbPerSecMax; if (cbPerSecMax > 0) { /* * Re-fill the bucket first * * Note! We limit the cTokensAdded calculation to 1 second, since it's really * pointless to calculate much beyond PDM_NETSHAPER_MAX_LATENCY (100ms) * let alone 1 sec. This makes it possible to use ASMMultU64ByU32DivByU32 * as the cNsDelta is less than 30 bits wide now, which means we don't get * into overflow issues when multiplying two 64-bit values. */ uint64_t const nsNow = RTTimeSystemNanoTS(); uint64_t const cNsDelta = nsNow - pGroup->tsUpdatedLast; uint64_t const cTokensAdded = cNsDelta < RT_NS_1SEC ? ASMMultU64ByU32DivByU32(cbPerSecMax, (uint32_t)cNsDelta, RT_NS_1SEC) : cbPerSecMax; uint32_t const cbBucket = pGroup->cbBucket; uint32_t const cbTokensLast = pGroup->cbTokensLast; uint32_t const cTokens = (uint32_t)RT_MIN(cbBucket, cTokensAdded + cbTokensLast); /* * Allowed? */ if (cbTransfer <= cTokens) { pGroup->cbTokensLast = cTokens - (uint32_t)cbTransfer; pGroup->tsUpdatedLast = nsNow; Log2(("pdmNsAllocateBandwidth/%s: allowed - cbTransfer=%#zx cTokens=%#x cTokensAdded=%#x\n", pGroup->szName, cbTransfer, cTokens, cTokensAdded)); } else { /* * No, we're choked. Arm the unchoke timer for the next period. * Just do this on a simple PDM_NETSHAPER_MAX_LATENCY clock granularity. * ASSUMES the timer uses millisecond resolution clock. */ ASMAtomicWriteBool(&pFilter->fChoked, true); if (ASMAtomicCmpXchgBool(&pVM->pdm.s.fNsUnchokeTimerArmed, true, false)) { Assert(TMTimerGetFreq(pVM, pVM->pdm.s.hNsUnchokeTimer) == RT_MS_1SEC); uint64_t const msNow = TMTimerGet(pVM, pVM->pdm.s.hNsUnchokeTimer); uint64_t const msExpire = (msNow / PDM_NETSHAPER_MAX_LATENCY + 1) * PDM_NETSHAPER_MAX_LATENCY; rc = TMTimerSet(pVM, pVM->pdm.s.hNsUnchokeTimer, msExpire); AssertRC(rc); Log2(("pdmNsAllocateBandwidth/%s: refused - cbTransfer=%#zx cTokens=%#x cTokensAdded=%#x cMsExpire=%u\n", pGroup->szName, cbTransfer, cTokens, cTokensAdded, msExpire - msNow)); } else Log2(("pdmNsAllocateBandwidth/%s: refused - cbTransfer=%#zx cTokens=%#x cTokensAdded=%#x\n", pGroup->szName, cbTransfer, cTokens, cTokensAdded)); ASMAtomicIncU64(&pGroup->cTotalChokings); fAllowed = false; } } else Log2(("pdmNsAllocateBandwidth/%s: disabled\n", pGroup->szName)); rc = PDMCritSectLeave(pVM, &pGroup->Lock); AssertRCSuccess(rc); } else if (rc == VINF_TRY_AGAIN) /* (accounted for by the critsect stats) */ Log2(("pdmNsAllocateBandwidth/%s: allowed - lock contention\n", pGroup->szName)); else PDM_CRITSECT_RELEASE_ASSERT_RC(pVM, &pGroup->Lock, rc); } else AssertMsgFailed(("Invalid iGroup=%d\n", iGroup)); } return fAllowed; }