VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/NetLib/IntNetIf.cpp@ 98352

Last change on this file since 98352 was 98103, checked in by vboxsync, 21 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.9 KB
Line 
1/* $Id: IntNetIf.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * IntNetIfCtx - Abstract API implementing an IntNet connection using the R0 support driver or some R3 IPC variant.
4 */
5
6/*
7 * Copyright (C) 2022-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
33# if defined(RT_OS_DARWIN)
34# include <xpc/xpc.h> /* This needs to be here because it drags PVM in and cdefs.h needs to undefine it... */
35# else
36# error "R3 internal networking not implemented for this platform yet!"
37# endif
38#endif
39
40#include <iprt/cdefs.h>
41#include <iprt/path.h>
42#include <iprt/semaphore.h>
43
44#include <VBox/err.h>
45#include <VBox/sup.h>
46#include <VBox/intnetinline.h>
47#include <VBox/vmm/pdmnetinline.h>
48
49#include "IntNetIf.h"
50
51
52/*********************************************************************************************************************************
53* Defined Constants And Macros *
54*********************************************************************************************************************************/
55
56
57/*********************************************************************************************************************************
58* Structures and Typedefs *
59*********************************************************************************************************************************/
60
61
62/**
63 * Internal network interface context instance data.
64 */
65typedef struct INTNETIFCTXINT
66{
67 /** The support driver session handle. */
68 PSUPDRVSESSION pSupDrvSession;
69 /** Interface handle. */
70 INTNETIFHANDLE hIf;
71 /** The internal network buffer. */
72 PINTNETBUF pBuf;
73#if defined (VBOX_WITH_INTNET_SERVICE_IN_R3)
74 /** Flag whether this interface is using the internal network switch in userspace path. */
75 bool fIntNetR3Svc;
76 /** Receive event semaphore. */
77 RTSEMEVENT hEvtRecv;
78# if defined(RT_OS_DARWIN)
79 /** XPC connection handle to the R3 internal network switch service. */
80 xpc_connection_t hXpcCon;
81 /** Size of the communication buffer in bytes. */
82 size_t cbBuf;
83# endif
84#endif
85} INTNETIFCTXINT;
86/** Pointer to the internal network interface context instance data. */
87typedef INTNETIFCTXINT *PINTNETIFCTXINT;
88
89
90/*********************************************************************************************************************************
91* Internal Functions *
92*********************************************************************************************************************************/
93
94/**
95 * Calls the internal networking switch service living in either R0 or in another R3 process.
96 *
97 * @returns VBox status code.
98 * @param pThis The internal network driver instance data.
99 * @param uOperation The operation to execute.
100 * @param pReqHdr Pointer to the request header.
101 */
102static int intnetR3IfCallSvc(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMMR0REQHDR pReqHdr)
103{
104#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
105 if (pThis->fIntNetR3Svc)
106 {
107# if defined(RT_OS_DARWIN)
108 size_t cbReq = pReqHdr->cbReq;
109 xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0);
110 xpc_dictionary_set_uint64(hObj, "req-id", uOperation);
111 xpc_dictionary_set_data(hObj, "req", pReqHdr, pReqHdr->cbReq);
112 xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj);
113 int rc = (int)xpc_dictionary_get_int64(hObjReply, "rc");
114
115 size_t cbReply = 0;
116 const void *pvData = xpc_dictionary_get_data(hObjReply, "reply", &cbReply);
117 AssertRelease(cbReply == cbReq);
118 memcpy(pReqHdr, pvData, cbReq);
119 xpc_release(hObjReply);
120
121 return rc;
122# endif
123 }
124 else
125#else
126 RT_NOREF(pThis);
127#endif
128 return SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, uOperation, 0, pReqHdr);
129}
130
131
132#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
133/**
134 * Calls the internal networking switch service living in either R0 or in another R3 process.
135 *
136 * @returns VBox status code.
137 * @param pThis The internal network driver instance data.
138 * @param uOperation The operation to execute.
139 * @param pReqHdr Pointer to the request header.
140 */
141static int intnetR3IfCallSvcAsync(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMMR0REQHDR pReqHdr)
142{
143 if (pThis->fIntNetR3Svc)
144 {
145 xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0);
146 xpc_dictionary_set_uint64(hObj, "req-id", uOperation);
147 xpc_dictionary_set_data(hObj, "req", pReqHdr, pReqHdr->cbReq);
148 xpc_connection_send_message(pThis->hXpcCon, hObj);
149 return VINF_SUCCESS;
150 }
151 else
152 return SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, uOperation, 0, pReqHdr);
153}
154#endif
155
156
157/**
158 * Map the ring buffer pointer into this process R3 address space.
159 *
160 * @returns VBox status code.
161 * @param pThis The internal network driver instance data.
162 */
163static int intnetR3IfMapBufferPointers(PINTNETIFCTXINT pThis)
164{
165 int rc = VINF_SUCCESS;
166
167 INTNETIFGETBUFFERPTRSREQ GetBufferPtrsReq;
168 GetBufferPtrsReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
169 GetBufferPtrsReq.Hdr.cbReq = sizeof(GetBufferPtrsReq);
170 GetBufferPtrsReq.pSession = pThis->pSupDrvSession;
171 GetBufferPtrsReq.hIf = pThis->hIf;
172 GetBufferPtrsReq.pRing3Buf = NULL;
173 GetBufferPtrsReq.pRing0Buf = NIL_RTR0PTR;
174
175#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
176 if (pThis->fIntNetR3Svc)
177 {
178#if defined(RT_OS_DARWIN)
179 xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0);
180 xpc_dictionary_set_uint64(hObj, "req-id", VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS);
181 xpc_dictionary_set_data(hObj, "req", &GetBufferPtrsReq, sizeof(GetBufferPtrsReq));
182 xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj);
183 rc = (int)xpc_dictionary_get_int64(hObjReply, "rc");
184 if (RT_SUCCESS(rc))
185 {
186 /* Get the shared memory object. */
187 xpc_object_t hObjShMem = xpc_dictionary_get_value(hObjReply, "buf-ptr");
188 size_t cbMem = xpc_shmem_map(hObjShMem, (void **)&pThis->pBuf);
189 if (!cbMem)
190 rc = VERR_NO_MEMORY;
191 else
192 pThis->cbBuf = cbMem;
193 }
194 xpc_release(hObjReply);
195#endif
196 }
197 else
198#endif
199 {
200 rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, 0 /*u64Arg*/, &GetBufferPtrsReq.Hdr);
201 if (RT_SUCCESS(rc))
202 {
203 AssertRelease(RT_VALID_PTR(GetBufferPtrsReq.pRing3Buf));
204 pThis->pBuf = GetBufferPtrsReq.pRing3Buf;
205 }
206 }
207
208 return rc;
209}
210
211
212static void intnetR3IfClose(PINTNETIFCTXINT pThis)
213{
214 if (pThis->hIf != INTNET_HANDLE_INVALID)
215 {
216 INTNETIFCLOSEREQ CloseReq;
217 CloseReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
218 CloseReq.Hdr.cbReq = sizeof(CloseReq);
219 CloseReq.pSession = pThis->pSupDrvSession;
220 CloseReq.hIf = pThis->hIf;
221
222 pThis->hIf = INTNET_HANDLE_INVALID;
223 int rc = intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_CLOSE, &CloseReq.Hdr);
224 AssertRC(rc);
225 }
226}
227
228
229DECLHIDDEN(int) IntNetR3IfCreate(PINTNETIFCTX phIfCtx, const char *pszNetwork)
230{
231 return IntNetR3IfCreateEx(phIfCtx, pszNetwork, kIntNetTrunkType_WhateverNone, "",
232 _128K /*cbSend*/, _256K /*cbRecv*/, 0 /*fFlags*/);
233}
234
235
236DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType,
237 const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags)
238{
239 AssertPtrReturn(phIfCtx, VERR_INVALID_POINTER);
240 AssertPtrReturn(pszNetwork, VERR_INVALID_POINTER);
241 AssertPtrReturn(pszTrunk, VERR_INVALID_POINTER);
242
243 PSUPDRVSESSION pSession = NIL_RTR0PTR;
244 int rc = SUPR3Init(&pSession);
245 if (RT_SUCCESS(rc))
246 {
247 PINTNETIFCTXINT pThis = (PINTNETIFCTXINT)RTMemAllocZ(sizeof(*pThis));
248 if (RT_LIKELY(pThis))
249 {
250 pThis->pSupDrvSession = pSession;
251#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
252 pThis->hEvtRecv = NIL_RTSEMEVENT;
253#endif
254
255 /* Driverless operation needs support for running the internal network switch using IPC. */
256 if (SUPR3IsDriverless())
257 {
258#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
259# if defined(RT_OS_DARWIN)
260 xpc_connection_t hXpcCon = xpc_connection_create(INTNET_R3_SVC_NAME, NULL);
261 xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) {
262 if (xpc_get_type(hObj) == XPC_TYPE_ERROR)
263 {
264 /** @todo Error handling - reconnecting. */
265 }
266 else
267 {
268 /* Out of band messages should only come when there is something to receive. */
269 RTSemEventSignal(pThis->hEvtRecv);
270 }
271 });
272
273 xpc_connection_resume(hXpcCon);
274 pThis->hXpcCon = hXpcCon;
275# endif
276 pThis->fIntNetR3Svc = true;
277 rc = RTSemEventCreate(&pThis->hEvtRecv);
278#else
279 rc = VERR_SUP_DRIVERLESS;
280#endif
281 }
282 else
283 {
284 /* Need to load VMMR0.r0 containing the network switching code. */
285 char szPathVMMR0[RTPATH_MAX];
286
287 rc = RTPathExecDir(szPathVMMR0, sizeof(szPathVMMR0));
288 if (RT_SUCCESS(rc))
289 {
290 rc = RTPathAppend(szPathVMMR0, sizeof(szPathVMMR0), "VMMR0.r0");
291 if (RT_SUCCESS(rc))
292 rc = SUPR3LoadVMM(szPathVMMR0, /* :pErrInfo */ NULL);
293 }
294 }
295
296 if (RT_SUCCESS(rc))
297 {
298 /* Open the interface. */
299 INTNETOPENREQ OpenReq;
300 RT_ZERO(OpenReq);
301
302 OpenReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
303 OpenReq.Hdr.cbReq = sizeof(OpenReq);
304 OpenReq.pSession = pThis->pSupDrvSession;
305 OpenReq.enmTrunkType = enmTrunkType;
306 OpenReq.fFlags = fFlags;
307 OpenReq.cbSend = cbSend;
308 OpenReq.cbRecv = cbRecv;
309 OpenReq.hIf = INTNET_HANDLE_INVALID;
310
311 rc = RTStrCopy(OpenReq.szNetwork, sizeof(OpenReq.szNetwork), pszNetwork);
312 if (RT_SUCCESS(rc))
313 rc = RTStrCopy(OpenReq.szTrunk, sizeof(OpenReq.szTrunk), pszTrunk);
314 if (RT_SUCCESS(rc))
315 {
316 rc = intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_OPEN, &OpenReq.Hdr);
317 if (RT_SUCCESS(rc))
318 {
319 pThis->hIf = OpenReq.hIf;
320
321 rc = intnetR3IfMapBufferPointers(pThis);
322 if (RT_SUCCESS(rc))
323 {
324 *phIfCtx = pThis;
325 return VINF_SUCCESS;
326 }
327 }
328
329 intnetR3IfClose(pThis);
330 }
331 }
332
333#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
334 if (pThis->fIntNetR3Svc)
335 {
336# if defined(RT_OS_DARWIN)
337 if (pThis->hXpcCon)
338 xpc_connection_cancel(pThis->hXpcCon);
339 pThis->hXpcCon = NULL;
340# endif
341
342 if (pThis->hEvtRecv != NIL_RTSEMEVENT)
343 RTSemEventDestroy(pThis->hEvtRecv);
344 }
345#endif
346
347 RTMemFree(pThis);
348 }
349
350 SUPR3Term();
351 }
352
353 return rc;
354}
355
356
357DECLHIDDEN(int) IntNetR3IfDestroy(INTNETIFCTX hIfCtx)
358{
359 PINTNETIFCTXINT pThis = hIfCtx;
360 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
361
362 intnetR3IfClose(pThis);
363
364#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
365 if (pThis->fIntNetR3Svc)
366 {
367# if defined(RT_OS_DARWIN)
368 /* Unmap the shared buffer. */
369 munmap(pThis->pBuf, pThis->cbBuf);
370 xpc_connection_cancel(pThis->hXpcCon);
371 pThis->hXpcCon = NULL;
372# endif
373 RTSemEventDestroy(pThis->hEvtRecv);
374 pThis->fIntNetR3Svc = false;
375 }
376#endif
377
378 RTMemFree(pThis);
379 return VINF_SUCCESS;
380}
381
382
383DECLHIDDEN(int) IntNetR3IfQueryBufferPtr(INTNETIFCTX hIfCtx, PINTNETBUF *ppIfBuf)
384{
385 PINTNETIFCTXINT pThis = hIfCtx;
386 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
387 AssertPtrReturn(ppIfBuf, VERR_INVALID_POINTER);
388
389 *ppIfBuf = pThis->pBuf;
390 return VINF_SUCCESS;
391}
392
393
394DECLHIDDEN(int) IntNetR3IfSetActive(INTNETIFCTX hIfCtx, bool fActive)
395{
396 PINTNETIFCTXINT pThis = hIfCtx;
397 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
398
399 INTNETIFSETACTIVEREQ Req;
400 Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
401 Req.Hdr.cbReq = sizeof(Req);
402 Req.pSession = pThis->pSupDrvSession;
403 Req.hIf = pThis->hIf;
404 Req.fActive = fActive;
405 return intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_SET_ACTIVE, &Req.Hdr);
406}
407
408
409DECLHIDDEN(int) IntNetR3IfSetPromiscuous(INTNETIFCTX hIfCtx, bool fPromiscuous)
410{
411 PINTNETIFCTXINT pThis = hIfCtx;
412 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
413
414 INTNETIFSETPROMISCUOUSMODEREQ Req;
415 Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
416 Req.Hdr.cbReq = sizeof(Req);
417 Req.pSession = pThis->pSupDrvSession;
418 Req.hIf = pThis->hIf;
419 Req.fPromiscuous = fPromiscuous;
420 return intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE, &Req.Hdr);
421}
422
423
424DECLHIDDEN(int) IntNetR3IfSend(INTNETIFCTX hIfCtx)
425{
426 PINTNETIFCTXINT pThis = hIfCtx;
427 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
428
429 INTNETIFSENDREQ Req;
430 Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
431 Req.Hdr.cbReq = sizeof(Req);
432 Req.pSession = pThis->pSupDrvSession;
433 Req.hIf = pThis->hIf;
434 return intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_SEND, &Req.Hdr);
435}
436
437
438DECLHIDDEN(int) IntNetR3IfWait(INTNETIFCTX hIfCtx, uint32_t cMillies)
439{
440 PINTNETIFCTXINT pThis = hIfCtx;
441 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
442
443 int rc = VINF_SUCCESS;
444 INTNETIFWAITREQ WaitReq;
445 WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
446 WaitReq.Hdr.cbReq = sizeof(WaitReq);
447 WaitReq.pSession = pThis->pSupDrvSession;
448 WaitReq.hIf = pThis->hIf;
449 WaitReq.cMillies = cMillies;
450#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3)
451 if (pThis->fIntNetR3Svc)
452 {
453 /* Send an asynchronous message. */
454 rc = intnetR3IfCallSvcAsync(pThis, VMMR0_DO_INTNET_IF_WAIT, &WaitReq.Hdr);
455 if (RT_SUCCESS(rc))
456 {
457 /* Wait on the receive semaphore. */
458 rc = RTSemEventWait(pThis->hEvtRecv, cMillies);
459 }
460 }
461 else
462#endif
463 rc = intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_WAIT, &WaitReq.Hdr);
464
465 return rc;
466}
467
468
469DECLHIDDEN(int) IntNetR3IfWaitAbort(INTNETIFCTX hIfCtx)
470{
471 PINTNETIFCTXINT pThis = hIfCtx;
472 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
473
474 INTNETIFABORTWAITREQ AbortWaitReq;
475 AbortWaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
476 AbortWaitReq.Hdr.cbReq = sizeof(AbortWaitReq);
477 AbortWaitReq.pSession = pThis->pSupDrvSession;
478 AbortWaitReq.hIf = pThis->hIf;
479 AbortWaitReq.fNoMoreWaits = true;
480 return intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_ABORT_WAIT, &AbortWaitReq.Hdr);
481}
482
483
484DECLHIDDEN(int) IntNetR3IfPumpPkts(INTNETIFCTX hIfCtx, PFNINPUT pfnInput, void *pvUser,
485 PFNINPUTGSO pfnInputGso, void *pvUserGso)
486{
487 PINTNETIFCTXINT pThis = hIfCtx;
488 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
489 AssertPtrReturn(pfnInput, VERR_INVALID_POINTER);
490
491 int rc;
492 for (;;)
493 {
494 rc = IntNetR3IfWait(hIfCtx, RT_INDEFINITE_WAIT);
495 if (RT_SUCCESS(rc) || rc == VERR_INTERRUPTED || rc == VERR_TIMEOUT)
496 {
497 PCINTNETHDR pHdr = IntNetRingGetNextFrameToRead(&pThis->pBuf->Recv);
498 while (pHdr)
499 {
500 const uint8_t u8Type = pHdr->u8Type;
501 void *pvSegFrame;
502 uint32_t cbSegFrame;
503
504 if (u8Type == INTNETHDR_TYPE_FRAME)
505 {
506 pvSegFrame = IntNetHdrGetFramePtr(pHdr, pThis->pBuf);
507 cbSegFrame = pHdr->cbFrame;
508
509 /* pass the frame to the user callback */
510 pfnInput(pvUser, pvSegFrame, cbSegFrame);
511 }
512 else if (u8Type == INTNETHDR_TYPE_GSO)
513 {
514 size_t cbGso = pHdr->cbFrame;
515 size_t cbFrame = cbGso - sizeof(PDMNETWORKGSO);
516
517 PCPDMNETWORKGSO pcGso = IntNetHdrGetGsoContext(pHdr, pThis->pBuf);
518 if (PDMNetGsoIsValid(pcGso, cbGso, cbFrame))
519 {
520 if (pfnInputGso != NULL)
521 {
522 /* pass the frame to the user GSO input callback if set */
523 pfnInputGso(pvUserGso, pcGso, (uint32_t)cbFrame);
524 }
525 else
526 {
527 const uint32_t cSegs = PDMNetGsoCalcSegmentCount(pcGso, cbFrame);
528 for (uint32_t i = 0; i < cSegs; ++i)
529 {
530 uint8_t abHdrScratch[256];
531 pvSegFrame = PDMNetGsoCarveSegmentQD(pcGso, (uint8_t *)(pcGso + 1), cbFrame,
532 abHdrScratch,
533 i, cSegs,
534 &cbSegFrame);
535
536 /* pass carved frames to the user input callback */
537 pfnInput(pvUser, pvSegFrame, (uint32_t)cbSegFrame);
538 }
539 }
540 }
541 }
542
543 /* advance to the next input frame */
544 IntNetRingSkipFrame(&pThis->pBuf->Recv);
545 pHdr = IntNetRingGetNextFrameToRead(&pThis->pBuf->Recv);
546 }
547 }
548 else
549 break;
550 }
551 return rc;
552}
553
554
555DECLHIDDEN(int) IntNetR3IfQueryOutputFrame(INTNETIFCTX hIfCtx, uint32_t cbFrame, PINTNETFRAME pFrame)
556{
557 PINTNETIFCTXINT pThis = hIfCtx;
558 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
559
560 return IntNetRingAllocateFrame(&pThis->pBuf->Send, cbFrame, &pFrame->pHdr, &pFrame->pvFrame);
561}
562
563
564DECLHIDDEN(int) IntNetR3IfOutputFrameCommit(INTNETIFCTX hIfCtx, PCINTNETFRAME pFrame)
565{
566 PINTNETIFCTXINT pThis = hIfCtx;
567 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
568
569 IntNetRingCommitFrame(&pThis->pBuf->Send, pFrame->pHdr);
570 return IntNetR3IfSend(hIfCtx);
571}
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