VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMR3/PDMNetShaper.cpp@ 93628

Last change on this file since 93628 was 93628, checked in by vboxsync, 3 years ago

VMM/PDMNetShaper,Main,DrvNetShaper: Moved the network shaper data off the hyper heap and into the VM structure. bugref:10093 bugref:5582

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.7 KB
Line 
1/* $Id: PDMNetShaper.cpp 93628 2022-02-06 23:44:05Z vboxsync $ */
2/** @file
3 * PDM Network Shaper - Limit network traffic according to bandwidth group settings.
4 */
5
6/*
7 * Copyright (C) 2011-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_NET_SHAPER
23#include <VBox/vmm/pdm.h>
24#include "PDMInternal.h"
25#include <VBox/vmm/vm.h>
26#include <VBox/vmm/uvm.h>
27#include <VBox/err.h>
28
29#include <VBox/log.h>
30#include <iprt/asm.h>
31#include <iprt/assert.h>
32#include <iprt/thread.h>
33#include <iprt/mem.h>
34#include <iprt/critsect.h>
35#include <iprt/tcp.h>
36#include <iprt/path.h>
37#include <iprt/string.h>
38
39#include <VBox/vmm/pdmnetshaper.h>
40
41
42
43
44/**
45 * Looks up a network bandwidth group by it's name.
46 *
47 * @returns Pointer to the group if found, NULL if not.
48 * @param pVM The cross context VM structure.
49 * @param pszName The name of the group to find.
50 */
51static PPDMNSBWGROUP pdmNsBwGroupFindByName(PVM pVM, const char *pszName)
52{
53 AssertPtrReturn(pszName, NULL);
54 AssertReturn(*pszName != '\0', NULL);
55
56 size_t const cGroups = RT_MIN(pVM->pdm.s.cNsGroups, RT_ELEMENTS(pVM->pdm.s.aNsGroups));
57 for (size_t i = 0; i < cGroups; i++)
58 if (RTStrCmp(pVM->pdm.s.aNsGroups[i].szName, pszName) == 0)
59 return &pVM->pdm.s.aNsGroups[i];
60 return NULL;
61}
62
63
64#ifdef VBOX_STRICT
65/**
66 * Checks if pFilter is attached to the given group by walking the list.
67 */
68DECLINLINE(bool) pdmR3NsIsFilterAttached(PPDMNSBWGROUP pGroup, PPDMNSFILTER pFilter)
69{
70 PPDMNSFILTER pCur;
71 RTListForEach(&pGroup->FilterList, pCur, PDMNSFILTER, ListEntry)
72 {
73 if (pCur == pFilter)
74 return true;
75 }
76 return false;
77}
78#endif
79
80/**
81 * Attaches a network filter driver to the named bandwidth group.
82 *
83 * @returns VBox status code.
84 * @retval VERR_ALREADY_INITIALIZED if already attached.
85 * @retval VERR_NOT_FOUND if the bandwidth wasn't found.
86 *
87 * @param pVM The cross context VM structure.
88 * @param pDrvIns The driver instance.
89 * @param pszName Name of the bandwidth group to attach to.
90 * @param pFilter Pointer to the filter to attach.
91 */
92VMMR3_INT_DECL(int) PDMR3NsAttach(PVM pVM, PPDMDRVINS pDrvIns, const char *pszName, PPDMNSFILTER pFilter)
93{
94 /*
95 * Validate input.
96 */
97 RT_NOREF(pDrvIns);
98 VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
99 AssertPtrReturn(pFilter, VERR_INVALID_POINTER);
100
101 uint32_t iGroup = pFilter->iGroup;
102 AssertMsgReturn(iGroup == 0, ("iGroup=%d\n", iGroup), VERR_ALREADY_INITIALIZED);
103 Assert(pFilter->ListEntry.pNext == NULL);
104 Assert(pFilter->ListEntry.pPrev == NULL);
105
106 /* Resolve the group. */
107 PPDMNSBWGROUP pGroup = pdmNsBwGroupFindByName(pVM, pszName);
108 AssertMsgReturn(pGroup, ("'%s'\n", pszName), VERR_NOT_FOUND);
109
110 /*
111 * The attach is protected by PDM::NsLock and by updating iGroup atomatically.
112 */
113 int rc = RTCritSectEnter(&pVM->pdm.s.NsLock);
114 if (RT_SUCCESS(rc))
115 {
116 if (ASMAtomicCmpXchgU32(&pFilter->iGroup, (uint32_t)(pGroup - &pVM->pdm.s.aNsGroups[0]) + 1, 0))
117 {
118 Assert(pFilter->ListEntry.pNext == NULL);
119 Assert(pFilter->ListEntry.pPrev == NULL);
120 RTListAppend(&pGroup->FilterList, &pFilter->ListEntry);
121
122 uint32_t cRefs = ASMAtomicIncU32(&pGroup->cRefs);
123 AssertMsg(cRefs > 0 && cRefs < _16K, ("%u\n", cRefs));
124
125 LogFlow(("PDMR3NsAttach: Attached '%s'/%u to %s (cRefs=%u)\n",
126 pDrvIns->pReg->szName, pDrvIns->iInstance, pGroup->szName, cRefs));
127 rc = VINF_SUCCESS;
128 }
129 else
130 {
131 AssertMsgFailed(("iGroup=%d (attach race)\n", pFilter->iGroup));
132 rc = VERR_ALREADY_INITIALIZED;
133 }
134
135 int rc2 = RTCritSectLeave(&pVM->pdm.s.NsLock);
136 AssertRC(rc2);
137 }
138
139 return rc;
140}
141
142
143/**
144 * Detaches a network filter driver from its current bandwidth group (if any).
145 *
146 * @returns VBox status code.
147 * @param pVM The cross context VM structure.
148 * @param pDrvIns The driver instance.
149 * @param pFilter Pointer to the filter to detach.
150 */
151VMMR3_INT_DECL(int) PDMR3NsDetach(PVM pVM, PPDMDRVINS pDrvIns, PPDMNSFILTER pFilter)
152{
153 /*
154 * Validate input.
155 */
156 RT_NOREF(pDrvIns);
157 VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
158 AssertPtrReturn(pFilter, VERR_INVALID_POINTER);
159
160 /* Now, return quietly if the filter isn't attached since driver/device
161 destructors are called on constructor failure. */
162 uint32_t const iGroup = ASMAtomicUoReadU32(&pFilter->iGroup);
163 if (!iGroup)
164 return VINF_SUCCESS;
165 AssertMsgReturn(iGroup - 1 < RT_MIN(pVM->pdm.s.cNsGroups, RT_ELEMENTS(pVM->pdm.s.aNsGroups)), ("iGroup=%#x\n", iGroup),
166 VERR_INVALID_HANDLE);
167 PPDMNSBWGROUP const pGroup = &pVM->pdm.s.aNsGroups[iGroup - 1];
168
169 /*
170 * The detaching is protected by PDM::NsLock and by atomically updating iGroup.
171 */
172 int rc = RTCritSectEnter(&pVM->pdm.s.NsLock);
173 if (RT_SUCCESS(rc))
174 {
175 if (ASMAtomicCmpXchgU32(&pFilter->iGroup, 0, iGroup))
176 {
177 Assert(pdmR3NsIsFilterAttached(pGroup, pFilter));
178 RTListNodeRemove(&pFilter->ListEntry);
179 Assert(pFilter->ListEntry.pNext == NULL);
180 Assert(pFilter->ListEntry.pPrev == NULL);
181 ASMAtomicWriteU32(&pFilter->iGroup, 0);
182
183 uint32_t cRefs = ASMAtomicDecU32(&pGroup->cRefs);
184 Assert(cRefs < _16K);
185
186 LogFlow(("PDMR3NsDetach: Detached '%s'/%u from %s (cRefs=%u)\n",
187 pDrvIns->pReg->szName, pDrvIns->iInstance, pGroup->szName, cRefs));
188 rc = VINF_SUCCESS;
189 }
190 else
191 AssertFailedStmt(rc = VERR_WRONG_ORDER);
192
193 int rc2 = RTCritSectLeave(&pVM->pdm.s.NsLock);
194 AssertRC(rc2);
195 }
196 else
197 AssertRC(rc);
198 return rc;
199}
200
201
202/**
203 * This is used both by pdmR3NsTxThread and PDMR3NsBwGroupSetLimit,
204 * the latter only when setting cbPerSecMax to zero.
205 *
206 * @param pGroup The group which filters should be unchoked.
207 * @note Caller owns the PDM::NsLock critsect.
208 */
209static void pdmR3NsUnchokeGroupFilters(PPDMNSBWGROUP pGroup)
210{
211 PPDMNSFILTER pFilter;
212 RTListForEach(&pGroup->FilterList, pFilter, PDMNSFILTER, ListEntry)
213 {
214 bool fChoked = ASMAtomicXchgBool(&pFilter->fChoked, false);
215 if (fChoked)
216 {
217 PPDMINETWORKDOWN pIDrvNet = pFilter->pIDrvNetR3;
218 if (pIDrvNet && pIDrvNet->pfnXmitPending != NULL)
219 {
220 Log3(("pdmR3NsUnchokeGroupFilters: Unchoked %p in %s, calling %p\n",
221 pFilter, pGroup->szName, pIDrvNet->pfnXmitPending));
222 pIDrvNet->pfnXmitPending(pIDrvNet);
223 }
224 else
225 Log3(("pdmR3NsUnchokeGroupFilters: Unchoked %p in %s (no callback)\n", pFilter, pGroup->szName));
226 }
227 }
228}
229
230
231/**
232 * Worker for PDMR3NsBwGroupSetLimit and pdmR3NetShaperInit.
233 *
234 * @returns New bucket size.
235 * @param pGroup The group to update.
236 * @param cbPerSecMax The new max bytes per second.
237 */
238static uint32_t pdmNsBwGroupSetLimit(PPDMNSBWGROUP pGroup, uint64_t cbPerSecMax)
239{
240 uint32_t const cbRet = RT_MAX(PDM_NETSHAPER_MIN_BUCKET_SIZE, cbPerSecMax * PDM_NETSHAPER_MAX_LATENCY / RT_MS_1SEC);
241 pGroup->cbBucket = cbRet;
242 pGroup->cbPerSecMax = cbPerSecMax;
243 LogFlow(("pdmNsBwGroupSetLimit: New rate limit is %#RX64 bytes per second, adjusted bucket size to %#x bytes\n",
244 cbPerSecMax, cbRet));
245 return cbRet;
246}
247
248
249/**
250 * Adjusts the maximum rate for the bandwidth group.
251 *
252 * @returns VBox status code.
253 * @param pUVM The user mode VM handle.
254 * @param pszName Name of the bandwidth group to attach to.
255 * @param cbPerSecMax Maximum number of bytes per second to be transmitted.
256 */
257VMMR3DECL(int) PDMR3NsBwGroupSetLimit(PUVM pUVM, const char *pszName, uint64_t cbPerSecMax)
258{
259 /*
260 * Validate input.
261 */
262 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
263 PVM const pVM = pUVM->pVM;
264 VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE);
265
266 int rc;
267 PPDMNSBWGROUP pGroup = pdmNsBwGroupFindByName(pVM, pszName);
268 if (pGroup)
269 {
270 /*
271 * Lock the group while we effect the changes.
272 */
273 rc = PDMCritSectEnter(pVM, &pGroup->Lock, VERR_IGNORED);
274 if (RT_SUCCESS(rc))
275 {
276 uint32_t const cbBucket = pdmNsBwGroupSetLimit(pGroup, cbPerSecMax);
277
278 /* Drop extra tokens */
279 if (pGroup->cbTokensLast > cbBucket)
280 pGroup->cbTokensLast = cbBucket;
281 Log(("PDMR3NsBwGroupSetLimit/%s: cbBucket=%#x cbPerSecMax=%#RX64\n", pGroup->szName, cbBucket, cbPerSecMax));
282
283 int rc2 = PDMCritSectLeave(pVM, &pGroup->Lock);
284 AssertRC(rc2);
285
286 /*
287 * If we disabled the group, we must make sure to unchoke all filter
288 * as the thread will ignore the group from now on.
289 *
290 * We do this after leaving the group lock to keep the locking simple.
291 * Extra pfnXmitPending calls should be harmless, of course ASSUMING
292 * nobody take offence to being called on this thread.
293 */
294 if (cbPerSecMax == 0)
295 {
296 Log(("PDMR3NsBwGroupSetLimit: cbPerSecMax was set to zero, so unchoking filters...\n"));
297 rc = RTCritSectEnter(&pVM->pdm.s.NsLock);
298 AssertRC(rc);
299
300 pdmR3NsUnchokeGroupFilters(pGroup);
301
302 rc2 = RTCritSectLeave(&pVM->pdm.s.NsLock);
303 AssertRC(rc2);
304 }
305 }
306 else
307 AssertRC(rc);
308 }
309 else
310 rc = VERR_NOT_FOUND;
311 return rc;
312}
313
314
315/**
316 * I/O thread for pending TX.
317 *
318 * @returns VINF_SUCCESS (ignored).
319 * @param pVM The cross context VM structure.
320 * @param pThread The PDM thread data.
321 */
322static DECLCALLBACK(int) pdmR3NsTxThread(PVM pVM, PPDMTHREAD pThread)
323{
324 LogFlow(("pdmR3NsTxThread: pVM=%p\n", pVM));
325 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
326 {
327 /** @todo r=bird: This sleep is horribly crude and wasteful! (Michael would go nuts if he knew) */
328 RTThreadSleep(PDM_NETSHAPER_MAX_LATENCY);
329
330 /*
331 * Go over all bandwidth groups/filters and unchoke their filters.
332 *
333 * We take the main lock here to prevent any detaching or attaching
334 * from taking place while we're traversing the filter lists.
335 */
336 int rc = RTCritSectEnter(&pVM->pdm.s.NsLock);
337 AssertRC(rc);
338
339 size_t const cGroups = RT_MIN(pVM->pdm.s.cNsGroups, RT_ELEMENTS(pVM->pdm.s.aNsGroups));
340 for (size_t i = 0; i < cGroups; i++)
341 {
342 PPDMNSBWGROUP const pGroup = &pVM->pdm.s.aNsGroups[i];
343 if ( pGroup->cRefs > 0
344 && pGroup->cbPerSecMax > 0)
345 pdmR3NsUnchokeGroupFilters(pGroup);
346 }
347
348 rc = RTCritSectLeave(&pVM->pdm.s.NsLock);
349 AssertRC(rc);
350 }
351 return VINF_SUCCESS;
352}
353
354
355/**
356 * @copydoc FNPDMTHREADWAKEUPINT
357 */
358static DECLCALLBACK(int) pdmR3NsTxWakeUp(PVM pVM, PPDMTHREAD pThread)
359{
360 RT_NOREF2(pVM, pThread);
361 LogFlow(("pdmR3NsTxWakeUp: pShaper=%p\n", pThread->pvUser));
362 /* Nothing to do */
363 /** @todo r=bird: use a semaphore, this'll cause a PDM_NETSHAPER_MAX_LATENCY/2
364 * delay every time we pause the VM! Stupid stupid stupid. */
365 return VINF_SUCCESS;
366}
367
368
369/**
370 * Terminate the network shaper, groups, lock and everything.
371 *
372 * @returns VBox error code.
373 * @param pVM The cross context VM structure.
374 */
375void pdmR3NetShaperTerm(PVM pVM)
376{
377 size_t const cGroups = RT_MIN(pVM->pdm.s.cNsGroups, RT_ELEMENTS(pVM->pdm.s.aNsGroups));
378 for (size_t i = 0; i < cGroups; i++)
379 {
380 PPDMNSBWGROUP const pGroup = &pVM->pdm.s.aNsGroups[i];
381 AssertMsg(pGroup->cRefs == 0, ("cRefs=%s '%s'\n", pGroup->cRefs, pGroup->szName));
382 AssertContinue(PDMCritSectIsInitialized(&pGroup->Lock));
383 PDMR3CritSectDelete(pVM, &pGroup->Lock);
384 }
385
386 RTCritSectDelete(&pVM->pdm.s.NsLock);
387}
388
389
390/**
391 * Initialize the network shaper.
392 *
393 * @returns VBox status code
394 * @param pVM The cross context VM structure.
395 */
396int pdmR3NetShaperInit(PVM pVM)
397{
398 LogFlow(("pdmR3NetShaperInit: pVM=%p\n", pVM));
399 VM_ASSERT_EMT(pVM);
400
401 /*
402 * Initialize the critical section protecting attaching, detaching and unchoking.
403 *
404 * This is a non-recursive lock to make sure nobody tries to mess with the groups
405 * from the pfnXmitPending callback.
406 */
407 int rc = RTCritSectInitEx(&pVM->pdm.s.NsLock, RTCRITSECT_FLAGS_NO_NESTING,
408 NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, "PDMNetShaper");
409 AssertRCReturn(rc, rc);
410
411 /*
412 * Initialize all bandwidth groups.
413 */
414 PCFGMNODE pCfgNetShaper = CFGMR3GetChild(CFGMR3GetChild(CFGMR3GetRoot(pVM), "PDM"), "NetworkShaper");
415 PCFGMNODE pCfgBwGrp = CFGMR3GetChild(pCfgNetShaper, "BwGroups");
416 if (pCfgBwGrp)
417 {
418 uint32_t iGroup = 0;
419 for (PCFGMNODE pCur = CFGMR3GetFirstChild(pCfgBwGrp); pCur; pCur = CFGMR3GetNextChild(pCur))
420 {
421 /*
422 * Get the config data.
423 */
424 size_t cchName = CFGMR3GetNameLen(pCur);
425 AssertBreakStmt(cchName <= PDM_NET_SHAPER_MAX_NAME_LEN,
426 rc = VMR3SetError(pVM->pUVM, VERR_INVALID_NAME, RT_SRC_POS,
427 N_("Network shaper group name #%u is too long: %zu, max %u"),
428 iGroup, cchName, PDM_NET_SHAPER_MAX_NAME_LEN));
429 char szName[PDM_NET_SHAPER_MAX_NAME_LEN + 1];
430 rc = CFGMR3GetName(pCur, szName, sizeof(szName));
431 AssertRCBreak(rc);
432 AssertBreakStmt(szName[0] != '\0',
433 rc = VMR3SetError(pVM->pUVM, VERR_INVALID_NAME, RT_SRC_POS,
434 N_("Empty network shaper group name #%u"), iGroup));
435
436 uint64_t cbMax;
437 rc = CFGMR3QueryU64(pCur, "Max", &cbMax);
438 AssertRCBreakStmt(rc, rc = VMR3SetError(pVM->pUVM, rc, RT_SRC_POS,
439 N_("Failed to read 'Max' value for network shaper group '%s': %Rrc"),
440 szName, rc));
441
442 /*
443 * Initialize the group table entry.
444 */
445 AssertBreakStmt(iGroup < RT_ELEMENTS(pVM->pdm.s.aNsGroups),
446 rc = VMR3SetError(pVM->pUVM, VERR_TOO_MUCH_DATA, RT_SRC_POS, N_("Too many bandwidth groups (max %zu)"),
447 RT_ELEMENTS(pVM->pdm.s.aNsGroups)));
448
449 rc = PDMR3CritSectInit(pVM, &pVM->pdm.s.aNsGroups[iGroup].Lock, RT_SRC_POS, "BWGRP%02u-%s", iGroup, szName);
450 AssertRCBreak(rc);
451
452 RTListInit(&pVM->pdm.s.aNsGroups[iGroup].FilterList);
453 pVM->pdm.s.aNsGroups[iGroup].cRefs = 0;
454 RTStrCopy(pVM->pdm.s.aNsGroups[iGroup].szName, sizeof(pVM->pdm.s.aNsGroups[iGroup].szName), szName);
455 pVM->pdm.s.aNsGroups[iGroup].cbTokensLast = pdmNsBwGroupSetLimit(&pVM->pdm.s.aNsGroups[iGroup], cbMax);
456 pVM->pdm.s.aNsGroups[iGroup].tsUpdatedLast = RTTimeSystemNanoTS();
457 LogFlowFunc(("PDM NetShaper Group #%u: %s - cbPerSecMax=%#RU64 cbBucket=%#x\n",
458 iGroup, pVM->pdm.s.aNsGroups[iGroup].szName, pVM->pdm.s.aNsGroups[iGroup].cbPerSecMax,
459 pVM->pdm.s.aNsGroups[iGroup].cbBucket));
460
461 pVM->pdm.s.cNsGroups = ++iGroup;
462 }
463 }
464 if (RT_SUCCESS(rc))
465 {
466 /*
467 * Create the transmit thread.
468 */
469 rc = PDMR3ThreadCreate(pVM, &pVM->pdm.s.pNsTxThread, NULL, pdmR3NsTxThread, pdmR3NsTxWakeUp,
470 0 /*cbStack*/, RTTHREADTYPE_IO, "PDMNsTx");
471 if (RT_SUCCESS(rc))
472 {
473 LogFlowFunc(("returns VINF_SUCCESS\n"));
474 return VINF_SUCCESS;
475 }
476 }
477
478 RTCritSectDelete(&pVM->pdm.s.NsLock);
479 LogRel(("pdmR3NetShaperInit: failed rc=%Rrc\n", rc));
480 return rc;
481}
482
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette