(copied from https://community.osr.com/discussion/comment/302680/#Comment_302680)
I think I figured it out by reading NetAdapter's code. Here is how I made it work in my driver:
- When setting datapath capabilities, we use
NET_ADAPTER_RX_CAPABILITIES_INIT_DRIVER_MANAGED
macro. We also specifyEvtAdapterReturnRxBuffer
callback. - In
WskReceiveFromEvent
callback we iterate over WSK_DATAGRAM_INDICATION list and enqueue each item into cosumer pool (fromDMF_BufferQueue
)// each datagram indication is one UDP datagram for (PWSK_DATAGRAM_INDICATION next; dataIndication != NULL; dataIndication = next) { next = dataIndication->Next; // break list so that we can pass individual WSK_DATAGRAM_INDICATION to WskRelease dataIndication->Next = NULL; // fetch buffer from producer OVPN_RX_BUFFER* rxBuffer; LOG_IF_NOT_NT_SUCCESS(OvpnBufferQueueFetch(device->DataRxBufferQueue, &rxBuffer)); rxBuffer->DatagramIndication = dataIndication; // enqueue buffer to consumer, which is consumed by NetAdapter's RX Advance callback OvpnBufferQueueEnqueue(device->DataRxBufferQueue, rxBuffer); } // tell NetAdapter that we have something to consume OvpnAdapterNotifyRx(device->Adapter);
- In RX queue's Advance callback we iterate over queue's fragments. We dequeue datagram indication and set fragment properties based on datagram indication's buffer and MDL:
while (NetFragmentIteratorHasAny(&fi)) { OVPN_RX_BUFFER* buffer; // nothing has arrived and decrypted yet? if (!NT_SUCCESS(OvpnBufferQueueDequeue(bufferQueue, &buffer))) { break; } fragment = NetFragmentIteratorGetFragment(&fi); PWSK_DATAGRAM_INDICATION datagramIndication = buffer->DatagramIndication; // return buffer to producer queue OvpnBufferQueueReuse(bufferQueue, buffer); PMDL mdl = datagramIndication->Buffer.Mdl; // TODO: correctly adjust crypto/ovpn overhead fragment->ValidLength = datagramIndication->Buffer.Length - 8; fragment->Offset = datagramIndication->Buffer.Offset + 8; fragment->Capacity = MmGetMdlByteCount(mdl); NET_FRAGMENT_VIRTUAL_ADDRESS* virtualAddr = NetExtensionGetFragmentVirtualAddress(&queue->VirtualAddressExtension, NetFragmentIteratorGetIndex(&fi)); virtualAddr->VirtualAddress = (PUCHAR)(MmGetSystemAddressForMdlSafe(mdl, LowPagePriority)); // TODO: handle case when packet (DataIndication) contains multiple fragments NET_PACKET* packet = NetPacketIteratorGetPacket(&pi); packet->FragmentIndex = NetFragmentIteratorGetIndex(&fi); packet->FragmentCount = 1; packet->Layout = {}; // NetAdapter will call ReturnRxBuffer callback when it is done with buffers, there we return datagramIndication back to WSK returnCtx = NetExtensionGetFragmentReturnContext(&queue->ReturnContextExtension, NetFragmentIteratorGetIndex(&fi)); returnCtx->Handle = (NET_FRAGMENT_RETURN_CONTEXT_HANDLE)datagramIndication; NetFragmentIteratorAdvance(&fi); NetPacketIteratorAdvance(&pi); } NetFragmentIteratorSet(&fi); NetPacketIteratorSet(&pi);
- In NetAdapter's EvtAdapterReturnRxBuffer callback we call
WskRelease
on a datagram indication, which is passed asNET_FRAGMENT_RETURN_CONTEXT_HANDLE
by NetAdapter:_Use_decl_annotations_ void OvpnEvtAdapterReturnRxBuffer(NETADAPTER netAdapter, NET_FRAGMENT_RETURN_CONTEXT_HANDLE rxReturnContext) { POVPN_ADAPTER adapter = OvpnGetAdapterContext(netAdapter); POVPN_DEVICE device = OvpnGetDeviceContext(adapter->WdfDevice); PWSK_DATAGRAM_INDICATION dataIndication = (PWSK_DATAGRAM_INDICATION)rxReturnContext; LOG_IF_NOT_NT_SUCCESS(((WSK_PROVIDER_DATAGRAM_DISPATCH*)device->Socket.Socket->Dispatch)->WskRelease(device->Socket.Socket, dataIndication)); }
Not sure if this is the "best practices", but it works and doesn't crash on iperf3 tests.