Files
yttrium-virtio-gpu/NetKVM/Common/ParaNdis_TX.cpp
T
Sergey Bykov 0534862e89 NetKVM: Fix possible PF on ParaNdis6_CancelSendNetBufferLists path
Current implementation of CParaNdisTX::BuildCancelList may trigger page fault
and a bugcheck if m_SendQueue is already empty.

Trap:
2: kd> .trap ffffcc81`1b5d2520
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=0000000000000000 rbx=0000000000000000 rcx=00000000000019a4
rdx=00000000000019a5 rsi=0000000000000000 rdi=0000000000000000
rip=fffff80eae479edf rsp=ffffcc811b5d26b0 rbp=ffffa4873a248ee8
 r8=ffffa487392bae80  r9=0000000000000002 r10=ffffa487391f0c30
r11=0000000000000001 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz na po nc
netkvm!CNBL::MatchCancelID [inlined in
netkvm!CParaNdisTX::BuildCancelList+0x3f]:
fffff80e`ae479edf 488b4320        mov     rax,qword ptr [rbx+20h]
ds:00000000`00000020=????????????????
2: kd> r cr2
Last set context:
cr2=0000000000000020

Callstack:
2: kd> kc
  *** Stack trace for last set context - .thread/.cxr resets it
 # Call Site
00 netkvm!CNBL::MatchCancelID
01 netkvm!CParaNdisTX::BuildCancelList
02 netkvm!CParaNdisTX::CancelNBLs
03 netkvm!ParaNdis6_CancelSendNetBufferLists
04 NDIS!ndisMCancelSendNetBufferListsOnMiniport
05 nt!KeExpandKernelStackAndCalloutInternal
06 NDIS!ndisExpandStack
07 NDIS!ndisFilterCancelSendNetBufferLists
08 klim6
09 NDIS!ndisFInvokePause
0a NDIS!ndisPauseFilterInner
0b NDIS!ndisPauseFilter
0c NDIS!Ndis::BindEngine::Iterate
0d NDIS!Ndis::BindEngine::UpdateBindings
0e NDIS!Ndis::BindEngine::DispatchPendingWork
0f NDIS!Ndis::BindEngine::ApplyBindChanges
10 NDIS!ndisPnPRemoveDevice
11 NDIS!ndisPnPRemoveDeviceEx
12 NDIS!ndisPnPIrpRemoveDevice
13 NDIS!ndisPnPDispatch
14 nt!IopSynchronousCall
15 nt!IopRemoveDevice
16 nt!PnpRemoveLockedDeviceNode
17 nt!PnpDeleteLockedDeviceNode
18 nt!PnpDeleteLockedDeviceNodes
19 nt!PnpProcessQueryRemoveAndEject
1a nt!PnpProcessTargetDeviceEvent
1b nt!PnpDeviceEventWorker
1c nt!ExpWorkerThread
1d nt!PspSystemThreadStartup
1e nt!KiStartSystemThread

Queue state:
2: kd> dt netkvm!CLockFreeDynamicQueue<CNBL> 0xffffa4873a248ee8
   +0x000 m_Queue          : CLockFreeQueue<CNBL>
   +0x030 m_QueueFullList  :
CNdisList<CNBL,CRawAccess,CNonCountingObject>
   +0x048 m_QueueFullListLock : CNdisSpinLock
   +0x058 m_QueueFullListIsEmpty : 0n1
   +0x05c m_ElementCount   : 0n0
   +0x060 m_Size           : 0n8192

The fix is trivial: check if CNBL is empty before trying to access its fields.

Signed-off-by: Sergey Bykov <[email protected]>
2021-08-29 23:45:13 +03:00

1061 lines
30 KiB
C++

#include "ndis56common.h"
#include "kdebugprint.h"
#include "ParaNdis_DebugHistory.h"
#include "Trace.h"
#ifdef NETKVM_WPP_ENABLED
#include "ParaNdis_TX.tmh"
#endif
CNBL::CNBL(PNET_BUFFER_LIST NBL, PPARANDIS_ADAPTER Context, CParaNdisTX &ParentTXPath, CAllocationHelper<CNBL> *NBLAllocator, CAllocationHelper<CNB> *NBAllocator)
: m_NBL(NBL)
, m_Context(Context)
, m_ParentTXPath(&ParentTXPath)
, CNdisAllocatableViaHelper<CNBL>(NBLAllocator)
, m_NBAllocator(NBAllocator)
{
m_NBL->Scratch = this;
m_LsoInfo.Value = NET_BUFFER_LIST_INFO(m_NBL, TcpLargeSendNetBufferListInfo);
m_CsoInfo.Value = NET_BUFFER_LIST_INFO(m_NBL, TcpIpChecksumNetBufferListInfo);
#if PARANDIS_SUPPORT_USO
m_UsoInfo.Value = NET_BUFFER_LIST_INFO(m_NBL, UdpSegmentationOffloadInfo);
#endif
ParaNdis_DebugNBLIn(NBL, m_LogIndex);
}
CNBL::~CNBL()
{
CDpcIrqlRaiser OnDpc;
m_Buffers.ForEachDetached([this](CNB *NB)
{ CNB::Destroy(NB); });
if(m_NBL)
{
auto NBL = DetachInternalObject();
NETKVM_ASSERT(NET_BUFFER_LIST_NEXT_NBL(NBL) == nullptr);
if (CallCompletionForNBL(m_Context, NBL))
{
m_ParentTXPath->CompleteOutstandingNBLChain(NBL);
} else
{
m_ParentTXPath->CompleteOutstandingInternalNBL(NBL);
}
}
}
bool CNBL::ParsePriority()
{
NDIS_NET_BUFFER_LIST_8021Q_INFO priorityInfo;
priorityInfo.Value = m_Context->ulPriorityVlanSetting ?
NET_BUFFER_LIST_INFO(m_NBL, Ieee8021QNetBufferListInfo) : nullptr;
if (!priorityInfo.TagHeader.VlanId)
{
priorityInfo.TagHeader.VlanId = m_Context->VlanId;
}
if (priorityInfo.TagHeader.CanonicalFormatId || !IsValidVlanId(m_Context, priorityInfo.TagHeader.VlanId))
{
DPrintf(0, "[%s] Discarded invalid priority tag %p\n", __FUNCTION__, priorityInfo.Value);
return false;
}
else if (priorityInfo.Value)
{
// ignore priority, if configured
if (!IsPrioritySupported(m_Context))
priorityInfo.TagHeader.UserPriority = 0;
// ignore VlanId, if specified
if (!IsVlanSupported(m_Context))
priorityInfo.TagHeader.VlanId = 0;
if (priorityInfo.Value)
{
m_TCI = static_cast<UINT16>(priorityInfo.TagHeader.UserPriority << 13 | priorityInfo.TagHeader.VlanId);
DPrintf(1, "[%s] Populated priority tag %p\n", __FUNCTION__, priorityInfo.Value);
}
}
return true;
}
void CNBL::RegisterNB(CNB *NB)
{
m_Buffers.PushBack(NB);
m_BuffersNumber++;
}
void CNBL::RegisterMappedNB(CNB *NB)
{
UNREFERENCED_PARAMETER(NB);
if (m_BuffersNumber == (ULONG)m_BuffersMapped.AddRef())
{
m_ParentTXPath->NBLMappingDone(this);
}
}
bool CNBL::ParseBuffers()
{
m_MaxDataLength = 0;
CAllocationHelper<CNB> *pNBAllocator = this;
for (auto NB = NET_BUFFER_LIST_FIRST_NB(m_NBL); NB != nullptr; NB = NET_BUFFER_NEXT_NB(NB))
{
CNB *NBHolder = new (pNBAllocator) CNB(NB, this, m_Context, pNBAllocator);
pNBAllocator = m_NBAllocator;
if(!NBHolder || !NBHolder->IsValid())
{
return false;
}
RegisterNB(NBHolder);
m_MaxDataLength = max(m_MaxDataLength, NBHolder->GetDataLength());
}
if(m_MaxDataLength == 0)
{
DPrintf(0, "[%s] - Empty NBL (%p) dropped\n", __FUNCTION__, m_NBL);
return false;
}
return true;
}
bool CNBL::NeedsLSO()
{
return m_MaxDataLength > m_Context->MaxPacketSize.nMaxFullSizeOS;
}
bool CNBL::FitsLSO()
{
return (m_MaxDataLength <= PARANDIS_MAX_LSO_SIZE + LsoTcpHeaderOffset() + MAX_TCP_HEADER_SIZE);
}
bool CNBL::ParseLSO()
{
NETKVM_ASSERT(IsLSO());
if (m_LsoInfo.LsoV1Transmit.Type != NDIS_TCP_LARGE_SEND_OFFLOAD_V1_TYPE &&
m_LsoInfo.LsoV2Transmit.Type != NDIS_TCP_LARGE_SEND_OFFLOAD_V2_TYPE)
{
return false;
}
if (NeedsLSO() &&
(!m_LsoInfo.LsoV2Transmit.MSS ||
!m_LsoInfo.LsoV2Transmit.TcpHeaderOffset))
{
return false;
}
if (!FitsLSO())
{
return false;
}
if (!LsoTcpHeaderOffset() != !MSS())
{
return false;
}
if ((!m_Context->Offload.flags.fTxLso || !m_Context->bOffloadv4Enabled) &&
m_LsoInfo.LsoV2Transmit.IPVersion == NDIS_TCP_LARGE_SEND_OFFLOAD_IPv4)
{
return false;
}
if (m_LsoInfo.LsoV2Transmit.Type == NDIS_TCP_LARGE_SEND_OFFLOAD_V2_TYPE &&
m_LsoInfo.LsoV2Transmit.IPVersion == NDIS_TCP_LARGE_SEND_OFFLOAD_IPv6 &&
(!m_Context->Offload.flags.fTxLsov6 || !m_Context->bOffloadv6Enabled))
{
return false;
}
return true;
}
#if PARANDIS_SUPPORT_USO
bool CNBL::ParseUSO()
{
if (m_MaxDataLength > PARANDIS_MAX_LSO_SIZE + LsoTcpHeaderOffset() + sizeof(UDPHeader))
return false;
if (!m_UsoInfo.Transmit.MSS || !m_UsoInfo.Transmit.UdpHeaderOffset)
return false;
if (m_UsoInfo.Transmit.IPVersion == NDIS_UDP_SEGMENTATION_OFFLOAD_IPV4 && !m_Context->Offload.flags.fUsov4)
return false;
if (m_UsoInfo.Transmit.IPVersion == NDIS_UDP_SEGMENTATION_OFFLOAD_IPV6 && !m_Context->Offload.flags.fUsov6)
return false;
return true;
}
#else
bool CNBL::ParseUSO()
{
return false;
}
#endif
template <typename TClassPred, typename TOffloadPred, typename TSupportedPred>
bool CNBL::ParseCSO(TClassPred IsClass, TOffloadPred IsOffload,
TSupportedPred IsSupported, LPSTR OffloadName)
{
NETKVM_ASSERT(IsClass());
UNREFERENCED_PARAMETER(IsClass);
if (IsOffload())
{
if(!IsSupported())
{
DPrintf(0, "[%s] %s request when it is not supported\n", __FUNCTION__, OffloadName);
#if FAIL_UNEXPECTED
// ignore unexpected CS requests while this passes WHQL
return false;
#endif
}
}
return true;
}
bool CNBL::ParseOffloads()
{
if (IsLSO())
{
if(!ParseLSO())
{
return false;
}
}
else if (IsUSO())
{
if (!ParseUSO())
{
return false;
}
}
else if (IsIP4CSO())
{
if(!ParseCSO([this] () -> bool { return IsIP4CSO(); },
[this] () -> bool { return m_CsoInfo.Transmit.TcpChecksum; },
[this] () -> bool { return m_Context->Offload.flags.fTxTCPChecksum && m_Context->bOffloadv4Enabled; },
"TCP4 CSO"))
{
return false;
}
else if(!ParseCSO([this] () -> bool { return IsIP4CSO(); },
[this] () -> bool { return m_CsoInfo.Transmit.UdpChecksum; },
[this] () -> bool { return m_Context->Offload.flags.fTxUDPChecksum && m_Context->bOffloadv4Enabled; },
"UDP4 CSO"))
{
return false;
}
if(!ParseCSO([this] () -> bool { return IsIP4CSO(); },
[this] () -> bool { return m_CsoInfo.Transmit.IpHeaderChecksum; },
[this] () -> bool { return m_Context->Offload.flags.fTxIPChecksum && m_Context->bOffloadv4Enabled; },
"IP4 CSO"))
{
return false;
}
}
else if (IsIP6CSO())
{
if(!ParseCSO([this] () -> bool { return IsIP6CSO(); },
[this] () -> bool { return m_CsoInfo.Transmit.TcpChecksum; },
[this] () -> bool { return m_Context->Offload.flags.fTxTCPv6Checksum && m_Context->bOffloadv6Enabled; },
"TCP6 CSO"))
{
return false;
}
else if(!ParseCSO([this] () -> bool { return IsIP6CSO(); },
[this] () -> bool { return m_CsoInfo.Transmit.UdpChecksum; },
[this] () -> bool { return m_Context->Offload.flags.fTxUDPv6Checksum && m_Context->bOffloadv6Enabled; },
"UDP6 CSO"))
{
return false;
}
}
return true;
}
void CNBL::StartMapping()
{
CDpcIrqlRaiser OnDpc;
AddRef();
m_Buffers.ForEach([this](CNB *NB)
{
if (!NB->ScheduleBuildSGListForTx())
{
m_HaveFailedMappings = true;
NB->MappingDone(nullptr);
}
});
Release();
}
void CNBL::OnLastReferenceGone()
{
Destroy(this);
}
CParaNdisTX::~CParaNdisTX()
{
TPassiveSpinLocker LockedContext(m_Lock);
CNBL* NBL = nullptr;
NBL = m_SendQueue.Dequeue();
while (NBL)
{
NBL->~CNBL();
NBL = m_SendQueue.Dequeue();
}
DPrintf(1, "Pools state %d-> NB: %d, NBL: %d\n", m_queueIndex, m_nbPool.GetCount(), m_nblPool.GetCount());
if (m_StateMachineRegistered)
{
m_Context->m_StateMachine.UnregisterFlow(m_StateMachine);
m_StateMachineRegistered = false;
}
}
bool CParaNdisTX::Create(PPARANDIS_ADAPTER Context, UINT DeviceQueueIndex)
{
m_Context = Context;
m_queueIndex = (u16)DeviceQueueIndex;
Context->m_StateMachine.RegisterFlow(m_StateMachine);
m_StateMachineRegistered = true;
m_nbPool.Create(Context->MiniportHandle);
m_nblPool.Create(Context->MiniportHandle);
CreatePath();
return m_VirtQueue.Create(DeviceQueueIndex,
&m_Context->IODevice,
m_Context->MiniportHandle,
m_Context->maxFreeTxDescriptors,
m_Context->nVirtioHeaderSize,
m_Context) &&
m_SendQueue.Create(Context, m_SendQueue.IsPowerOfTwo(m_Context->maxFreeTxDescriptors) ?
8 * m_Context->maxFreeTxDescriptors : PARANDIS_TX_LOCK_FREE_QUEUE_DEFAULT_SIZE);
}
void CParaNdisTX::CompleteOutstandingNBLChain(PNET_BUFFER_LIST NBL, ULONG Flags)
{
ULONG NBLNum = ParaNdis_CountNBLs(NBL);
ParaNdis_CompleteNBLChain(m_Context->MiniportHandle, NBL, Flags);
m_StateMachine.UnregisterOutstandingItems(NBLNum);
}
void CParaNdisTX::CompleteOutstandingInternalNBL(PNET_BUFFER_LIST NBL, BOOLEAN UnregisterOutstanding /*= TRUE*/)
{
ULONG NBLNum = ParaNdis_CountNBLs(NBL);
CGuestAnnouncePackets::NblCompletionCallback(NBL);
if (UnregisterOutstanding)
{
m_StateMachine.UnregisterOutstandingItems(NBLNum);
}
}
void CParaNdisTX::Send(PNET_BUFFER_LIST NBL)
{
PNET_BUFFER_LIST nextNBL = nullptr;
NDIS_STATUS RejectionStatus = NDIS_STATUS_FAILURE;
BOOLEAN CallCompletion = CallCompletionForNBL(m_Context, NBL);
if (!m_StateMachine.RegisterOutstandingItems(ParaNdis_CountNBLs(NBL), &RejectionStatus))
{
if (CallCompletion)
{
ParaNdis_CompleteNBLChainWithStatus(m_Context->MiniportHandle, NBL, RejectionStatus);
}
else
{
CompleteOutstandingInternalNBL(NBL, FALSE);
}
return;
}
for(auto currNBL = NBL; currNBL != nullptr; currNBL = nextNBL)
{
nextNBL = NET_BUFFER_LIST_NEXT_NBL(currNBL);
NET_BUFFER_LIST_NEXT_NBL(currNBL) = nullptr;
auto NBLHolder = new (&m_nblPool) CNBL(currNBL, m_Context, *this, &m_nblPool, &m_nbPool);
if (NBLHolder == nullptr)
{
currNBL->Status = NDIS_STATUS_RESOURCES;
if (CallCompletion)
{
CompleteOutstandingNBLChain(currNBL);
}
else
{
CompleteOutstandingInternalNBL(NBL);
}
DPrintf(0, "ERROR: Failed to allocate CNBL instance\n");
continue;
}
if(NBLHolder->Prepare() &&
ParaNdis_IsTxRxPossible(m_Context))
{
NBLHolder->StartMapping();
}
else
{
NBLHolder->SetStatus(ParaNdis_ExactSendFailureStatus(m_Context));
NBLHolder->Release();
}
}
}
void CParaNdisTX::NBLMappingDone(CNBL *NBLHolder)
{
NETKVM_ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
if (NBLHolder->MappingSucceeded() && m_VirtQueue.Alive())
{
m_SendQueue.Enqueue(NBLHolder);
if (m_DpcWaiting == 0)
{
DoPendingTasks(NBLHolder);
}
}
else
{
NBLHolder->SetStatus(NDIS_STATUS_FAILURE);
NBLHolder->Release();
}
}
CNB *CNBL::PopMappedNB()
{
m_MappedBuffersDetached.AddRef();
return m_Buffers.Pop();
}
void CNBL::PushMappedNB(CNB *NB)
{
m_MappedBuffersDetached.Release();
m_Buffers.Push(NB);
}
void CNBL::NBComplete()
{
m_BuffersDone.AddRef();
m_MappedBuffersDetached.Release();
}
bool CNBL::IsSendDone()
{
return (LONG)m_BuffersDone == (LONG)m_BuffersNumber && !HaveDetachedBuffers();
}
PNET_BUFFER_LIST CNBL::DetachInternalObject()
{
m_Buffers.ForEach([this](CNB *NB)
{
NB->ReleaseResources();
});
// do it for both LsoV1 and LsoV2
if (IsLSO())
{
m_LsoInfo.LsoV1TransmitComplete.TcpPayload = m_TransferSize;
}
//Flush changes made in LSO structures
NET_BUFFER_LIST_INFO(m_NBL, TcpLargeSendNetBufferListInfo) = m_LsoInfo.Value;
auto Res = m_NBL;
ParaNdis_DebugNBLOut(m_LogIndex, Res);
m_NBL = nullptr;
return Res;
}
PNET_BUFFER_LIST CParaNdisTX::ProcessWaitingList(CRawCNBLList& completed)
{
PNET_BUFFER_LIST CompletedNBLs = nullptr;
// locked part under waiting list lock
{
TDPCSpinLocker LockedContext(m_WaitingListLock);
completed.ForEachDetachedIf([](CNBL* NBL) { return !NBL->IsSendDone(); },
[&](CNBL* NBL)
{
m_WaitingList.PushBack(NBL);
});
m_WaitingList.ForEachDetachedIf([](CNBL* NBL) { return NBL->IsSendDone(); },
[&](CNBL* NBL)
{
completed.PushBack(NBL);
});
}
// end of locked part under waiting list lock
completed.ForEachDetached([&](CNBL* NBL)
{
NBL->SetStatus(NDIS_STATUS_SUCCESS);
auto RawNBL = NBL->DetachInternalObject();
NBL->Release();
if (CallCompletionForNBL(m_Context, RawNBL))
{
NET_BUFFER_LIST_NEXT_NBL(RawNBL) = CompletedNBLs;
CompletedNBLs = RawNBL;
}
else
{
CompleteOutstandingInternalNBL(RawNBL);
}
});
return CompletedNBLs;
}
void CParaNdisTX::Notify(SMNotifications message)
{
CRawCNBList nbToFree;
CRawCNBLList completedNBLs;
__super::Notify(message);
if (message != SupriseRemoved)
{
// probably similar processing we'll do in case of reset
return;
}
CDpcIrqlRaiser OnDpc;
// pause follows after we return all the NBLs
DoWithTXLock([&]()
{
//
m_VirtQueue.ProcessTXCompletions(nbToFree, true);
while (HaveMappedNBLs())
{
CNBL *nbl = PopMappedNBL();
nbl->SetStatus(NDIS_STATUS_SEND_ABORTED);
completedNBLs.Push(nbl);
while (nbl->HaveMappedBuffers())
{
CNB *nb = nbl->PopMappedNB();
CNB::Destroy(nb);
nbl->NBComplete();
}
}
});
PostProcessPendingTask(nbToFree, completedNBLs);
}
PNET_BUFFER_LIST CParaNdisTX::BuildCancelList(PVOID CancelId)
{
PNET_BUFFER_LIST CanceledNBLs = nullptr;
TPassiveSpinLocker LockedContext(m_Lock);
CNBL* NBL = nullptr;
NBL = PopMappedNBL();
while (NBL)
{
if (NBL->MatchCancelID(CancelId) && !NBL->HaveDetachedBuffers())
{
NBL->SetStatus(NDIS_STATUS_SEND_ABORTED);
auto RawNBL = NBL->DetachInternalObject();
NBL->Release();
NET_BUFFER_LIST_NEXT_NBL(RawNBL) = CanceledNBLs;
CanceledNBLs = RawNBL;
}
NBL = PopMappedNBL();
}
return CanceledNBLs;
}
void CParaNdisTX::CancelNBLs(PVOID CancelId)
{
auto CanceledNBLs = BuildCancelList(CancelId);
if (CanceledNBLs != nullptr)
{
CompleteOutstandingNBLChain(CanceledNBLs);
}
}
//called with TX lock held
bool CParaNdisTX::RestartQueue()
{
auto res = ParaNdis_SynchronizeWithInterrupt(m_Context,
m_messageIndex,
RestartQueueSynchronously,
this) ? true : false;
return res;
}
//called with TX lock held
//returns queue restart status
bool CParaNdisTX::SendMapped(bool IsInterrupt, CRawCNBLList& toWaitingList)
{
bool SentOutSomeBuffers = false;
bool bRestartStatus = false;
bool HaveBuffers = true;
if(ParaNdis_IsTxRxPossible(m_Context))
{
while (HaveBuffers && HaveMappedNBLs())
{
auto NBLHolder = PeekMappedNBL();
if (NBLHolder->HaveMappedBuffers())
{
auto NBHolder = NBLHolder->PopMappedNB();
auto result = m_VirtQueue.SubmitPacket(*NBHolder);
switch (result)
{
case SUBMIT_NO_PLACE_IN_QUEUE:
NBLHolder->PushMappedNB(NBHolder);
HaveBuffers = false;
// break the loop, allow to kick and free some buffers
break;
case SUBMIT_FAILURE:
__fallthrough;
case SUBMIT_SUCCESS:
__fallthrough;
case SUBMIT_PACKET_TOO_LARGE:
// if this NBL finished?
if (!NBLHolder->HaveMappedBuffers())
{
/* We use PeekMappedNBL method to get the current NBL
* that should be processed from the queue, when we finish
* sending all it's NBs, we should pop it from the queue.
*/
PopMappedNBL();
toWaitingList.Push(NBLHolder);
}
else
{
// no, keep it in the queue
}
if (result == SUBMIT_SUCCESS)
{
SentOutSomeBuffers = true;
}
else
{
CNB::Destroy(NBHolder);
NBLHolder->NBComplete();
}
break;
default:
NETKVM_ASSERT(false);
break;
}
}
else
{
NETKVM_ASSERT(false);
}
}
}
if (IsInterrupt)
{
bRestartStatus = RestartQueue();
}
if (SentOutSomeBuffers || !HaveBuffers)
{
m_VirtQueue.Kick();
}
return bRestartStatus;
}
void CParaNdisTX::PostProcessPendingTask(
CRawCNBList& nbToFree,
CRawCNBLList& completedNBLs)
{
PNET_BUFFER_LIST pNBLReturnNow = nullptr;
if (!nbToFree.IsEmpty() || !completedNBLs.IsEmpty())
{
nbToFree.ForEachDetached([](CNB *NB)
{
CNBL *NBL = NB->GetParentNBL();
CNB::Destroy(NB);
NBL->NBComplete();
}
);
pNBLReturnNow = ProcessWaitingList(completedNBLs);
if (pNBLReturnNow)
{
CompleteOutstandingNBLChain(pNBLReturnNow, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL);
}
}
}
bool CParaNdisTX::DoPendingTasks(CNBL *nblHolder)
{
bool bRestartQueueStatus = false;
bool bFromDpc = nblHolder == nullptr;
CRawCNBList nbToFree;
CRawCNBLList completedNBLs;
if (bFromDpc)
{
m_DpcWaiting.AddRef();
}
DoWithTXLock([&]()
{
m_VirtQueue.ProcessTXCompletions(nbToFree);
if (bFromDpc)
{
m_DpcWaiting.Release();
}
if (bFromDpc || 0 == (LONG)m_DpcWaiting)
{
bRestartQueueStatus = SendMapped(bFromDpc, completedNBLs);
if (bRestartQueueStatus)
{
// we can enter here only when we called from DPC
// if we can't enable interrupts on queue right now,
// we can retrieve completed packets and try again
m_VirtQueue.ProcessTXCompletions(nbToFree);
bRestartQueueStatus = SendMapped(true, completedNBLs);
}
} else
{
// the call initiated by Send(), we can give up
// and let pending DPC do the job instead of wait
}
});
PostProcessPendingTask(nbToFree, completedNBLs);
return bRestartQueueStatus;
}
void CNB::MappingDone(PSCATTER_GATHER_LIST SGL)
{
m_SGL = SGL;
m_ParentNBL->RegisterMappedNB(this);
}
CNB::~CNB()
{
NETKVM_ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
if(m_SGL != nullptr)
{
NdisMFreeNetBufferSGList(m_Context->DmaHandle, m_SGL, m_NB);
}
}
void CNB::ReleaseResources()
{
if (m_SGL != nullptr)
{
NdisMFreeNetBufferSGList(m_Context->DmaHandle, m_SGL, m_NB);
m_SGL = nullptr;
}
}
bool CNB::ScheduleBuildSGListForTx()
{
NETKVM_ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
return NdisMAllocateNetBufferSGList(m_Context->DmaHandle, m_NB, this,
NDIS_SG_LIST_WRITE_TO_DEVICE, nullptr, 0) == NDIS_STATUS_SUCCESS;
}
void CNB::PopulateIPLength(IPHeader *IpHeader, USHORT IpLength) const
{
if ((IpHeader->v4.ip_verlen & 0xF0) == 0x40)
{
if (!IpHeader->v4.ip_length)
{
IpHeader->v4.ip_length = swap_short(IpLength);
}
}
else if ((IpHeader->v6.ip6_ver_tc & 0xF0) == 0x60)
{
if (!IpHeader->v6.ip6_payload_len)
{
IpHeader->v6.ip6_payload_len = swap_short(IpLength - IPV6_HEADER_MIN_SIZE);
}
}
else
{
DPrintf(0, "[%s] ERROR: Bad version of IP header!\n", __FUNCTION__);
}
}
void CNB::SetupLSO(virtio_net_hdr *VirtioHeader, PVOID IpHeader, ULONG EthPayloadLength) const
{
PopulateIPLength(reinterpret_cast<IPHeader*>(IpHeader), static_cast<USHORT>(EthPayloadLength));
tTcpIpPacketParsingResult packetReview;
packetReview = ParaNdis_CheckSumVerifyFlat(reinterpret_cast<IPv4Header*>(IpHeader), EthPayloadLength,
pcrIpChecksum | pcrFixIPChecksum | pcrTcpChecksum | pcrFixPHChecksum,
FALSE,
__FUNCTION__);
if (packetReview.xxpCheckSum == ppresPCSOK || packetReview.fixedXxpCS)
{
auto IpHeaderOffset = m_Context->Offload.ipHeaderOffset;
auto VHeader = static_cast<virtio_net_hdr*>(VirtioHeader);
auto PriorityHdrLen = (m_ParentNBL->TCI() != 0) ? ETH_PRIORITY_HEADER_SIZE : 0;
VHeader->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
VHeader->gso_type = packetReview.ipStatus == ppresIPV4 ? VIRTIO_NET_HDR_GSO_TCPV4 : VIRTIO_NET_HDR_GSO_TCPV6;
VHeader->hdr_len = (USHORT)(packetReview.XxpIpHeaderSize + IpHeaderOffset + PriorityHdrLen);
VHeader->gso_size = (USHORT)m_ParentNBL->MSS();
VHeader->csum_start = (USHORT)(m_ParentNBL->TCPHeaderOffset() + PriorityHdrLen);
VHeader->csum_offset = TCP_CHECKSUM_OFFSET;
}
}
void CNB::SetupUSO(virtio_net_hdr *VirtioHeader, PVOID IpHeader, ULONG EthPayloadLength) const
{
PopulateIPLength(reinterpret_cast<IPHeader*>(IpHeader), static_cast<USHORT>(EthPayloadLength));
tTcpIpPacketParsingResult packetReview;
packetReview = ParaNdis_CheckSumVerifyFlat(reinterpret_cast<IPv4Header*>(IpHeader), EthPayloadLength,
pcrIpChecksum | pcrFixIPChecksum | pcrUdpChecksum | pcrFixPHChecksum,
FALSE,
__FUNCTION__);
if (packetReview.xxpCheckSum == ppresPCSOK || packetReview.fixedXxpCS)
{
auto IpHeaderOffset = m_Context->Offload.ipHeaderOffset;
auto VHeader = static_cast<virtio_net_hdr*>(VirtioHeader);
auto PriorityHdrLen = (m_ParentNBL->TCI() != 0) ? ETH_PRIORITY_HEADER_SIZE : 0;
VHeader->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
VHeader->gso_type = VIRTIO_NET_HDR_GSO_UDP_L4;
VHeader->hdr_len = (USHORT)(packetReview.XxpIpHeaderSize + IpHeaderOffset + PriorityHdrLen);
VHeader->gso_size = (USHORT)m_ParentNBL->UsoMSS();
VHeader->csum_start = (USHORT)(m_ParentNBL->UsoHeaderOffset() + PriorityHdrLen);
VHeader->csum_offset = UDP_CHECKSUM_OFFSET;
DPrintf(0, "[%s] mss %d, hdr %d, total %d!\n", __FUNCTION__, VHeader->gso_size, VHeader->hdr_len, GetDataLength());
}
}
USHORT CNB::QueryL4HeaderOffset(PVOID PacketData, ULONG IpHeaderOffset) const
{
USHORT Res;
auto ppr = ParaNdis_ReviewIPPacket(RtlOffsetToPointer(PacketData, IpHeaderOffset),
GetDataLength(), FALSE, __FUNCTION__);
if (ppr.ipStatus != ppresNotIP)
{
Res = static_cast<USHORT>(IpHeaderOffset + ppr.ipHeaderSize);
}
else
{
DPrintf(0, "[%s] ERROR: NOT an IP packet - expected troubles!\n", __FUNCTION__);
Res = 0;
}
return Res;
}
void CNB::SetupCSO(virtio_net_hdr *VirtioHeader, ULONG L4HeaderOffset) const
{
u16 PriorityHdrLen = m_ParentNBL->TCI() ? ETH_PRIORITY_HEADER_SIZE : 0;
VirtioHeader->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
VirtioHeader->csum_start = static_cast<u16>(L4HeaderOffset) + PriorityHdrLen;
VirtioHeader->csum_offset = m_ParentNBL->IsTcpCSO() ? TCP_CHECKSUM_OFFSET : UDP_CHECKSUM_OFFSET;
}
void CNB::DoIPHdrCSO(PVOID IpHeader, ULONG EthPayloadLength) const
{
ParaNdis_CheckSumVerifyFlat(IpHeader,
EthPayloadLength,
pcrIpChecksum | pcrFixIPChecksum, FALSE,
__FUNCTION__);
}
bool CNB::FillDescriptorSGList(CTXDescriptor &Descriptor, ULONG ParsedHeadersLength) const
{
return Descriptor.SetupHeaders(ParsedHeadersLength) &&
MapDataToVirtioSGL(Descriptor, ParsedHeadersLength + NET_BUFFER_DATA_OFFSET(m_NB));
}
bool CNB::MapDataToVirtioSGL(CTXDescriptor &Descriptor, ULONG Offset) const
{
for (ULONG i = 0; i < m_SGL->NumberOfElements; i++)
{
if (Offset < m_SGL->Elements[i].Length)
{
PHYSICAL_ADDRESS PA;
PA.QuadPart = m_SGL->Elements[i].Address.QuadPart + Offset;
if (!Descriptor.AddDataChunk(PA, m_SGL->Elements[i].Length - Offset))
{
return false;
}
Offset = 0;
}
else
{
Offset -= m_SGL->Elements[i].Length;
}
}
return true;
}
bool CNB::CopyHeaders(PVOID Destination, ULONG MaxSize, ULONG &HeadersLength, ULONG &L4HeaderOffset) const
{
HeadersLength = 0;
L4HeaderOffset = 0;
if (m_ParentNBL->IsLSO() || m_ParentNBL->IsTcpCSO())
{
L4HeaderOffset = m_ParentNBL->TCPHeaderOffset();
HeadersLength = L4HeaderOffset + sizeof(TCPHeader);
Copy(Destination, HeadersLength);
}
else if (m_ParentNBL->IsUSO())
{
L4HeaderOffset = m_ParentNBL->UsoHeaderOffset();
HeadersLength = L4HeaderOffset + sizeof(UDPHeader);
HeadersLength = Copy(Destination, HeadersLength);
}
else if (m_ParentNBL->IsUdpCSO())
{
HeadersLength = Copy(Destination, MaxSize);
L4HeaderOffset = QueryL4HeaderOffset(Destination, m_Context->Offload.ipHeaderOffset);
}
else if (m_ParentNBL->IsIPHdrCSO())
{
HeadersLength = Copy(Destination, MaxSize);
L4HeaderOffset = QueryL4HeaderOffset(Destination, m_Context->Offload.ipHeaderOffset);
}
else
{
HeadersLength = ETH_HEADER_SIZE;
Copy(Destination, HeadersLength);
}
return (HeadersLength <= MaxSize);
}
void CNB::BuildPriorityHeader(PETH_HEADER EthHeader, PVLAN_HEADER VlanHeader) const
{
VlanHeader->TCI = RtlUshortByteSwap(m_ParentNBL->TCI());
if (VlanHeader->TCI != 0)
{
VlanHeader->EthType = EthHeader->EthType;
EthHeader->EthType = RtlUshortByteSwap(PRIO_HEADER_ETH_TYPE);
}
}
void CNB::PrepareOffloads(virtio_net_hdr *VirtioHeader, PVOID IpHeader, ULONG EthPayloadLength, ULONG L4HeaderOffset) const
{
*VirtioHeader = {};
if (m_ParentNBL->IsLSO())
{
SetupLSO(VirtioHeader, IpHeader, EthPayloadLength);
}
else if (m_ParentNBL->IsUSO())
{
SetupUSO(VirtioHeader, IpHeader, EthPayloadLength);
}
else if (m_ParentNBL->IsTcpCSO() || m_ParentNBL->IsUdpCSO())
{
SetupCSO(VirtioHeader, L4HeaderOffset);
}
if (m_ParentNBL->IsIPHdrCSO())
{
DoIPHdrCSO(IpHeader, EthPayloadLength);
}
}
bool CNB::BindToDescriptor(CTXDescriptor &Descriptor)
{
if (m_SGL == nullptr)
{
return false;
}
Descriptor.SetNB(this);
auto &HeadersArea = Descriptor.HeadersAreaAccessor();
auto EthHeaders = HeadersArea.EthHeadersAreaVA();
ULONG HeadersLength;
ULONG L4HeaderOffset;
if (!CopyHeaders(EthHeaders, HeadersArea.MaxEthHeadersSize(), HeadersLength, L4HeaderOffset))
{
return false;
}
BuildPriorityHeader(HeadersArea.EthHeader(), HeadersArea.VlanHeader());
PrepareOffloads(HeadersArea.VirtioHeader(),
HeadersArea.IPHeaders(),
GetDataLength() - m_Context->Offload.ipHeaderOffset,
L4HeaderOffset);
return FillDescriptorSGList(Descriptor, HeadersLength);
}
ULONG CNB::Copy(PVOID Dst, ULONG Length) const
{
ULONG CurrOffset = NET_BUFFER_CURRENT_MDL_OFFSET(m_NB);
ULONG Copied = 0;
Length = min(Length, NET_BUFFER_DATA_LENGTH(m_NB));
for (PMDL CurrMDL = NET_BUFFER_CURRENT_MDL(m_NB);
CurrMDL != nullptr && Copied < Length;
CurrMDL = CurrMDL->Next)
{
ULONG CurrLen;
PVOID CurrAddr;
#if NDIS_SUPPORT_NDIS620
NdisQueryMdl(CurrMDL, &CurrAddr, &CurrLen, MM_PAGE_PRIORITY(LowPagePriority | MdlMappingNoExecute));
#else
NdisQueryMdl(CurrMDL, &CurrAddr, &CurrLen, MM_PAGE_PRIORITY(LowPagePriority));
#endif
if (CurrAddr == nullptr)
{
break;
}
CurrLen = min(CurrLen - CurrOffset, Length - Copied);
NdisMoveMemory(RtlOffsetToPointer(Dst, Copied),
RtlOffsetToPointer(CurrAddr, CurrOffset),
CurrLen);
Copied += CurrLen;
CurrOffset = 0;
}
return Copied;
}