NDIS 6.0 introduced a new way of representing network packet data. The new data structures, a combination of Net Buffer Lists (NBLs), Net Buffers (NBs) and Memory Descriptor Lists (MDLs) have significant advantages over the old NDIS 5.X way of describing packets i.e. NDIS_PACKET and NDIS_BUFFER structures. This document describes the functionality provided by NBLs, NBs, MDLs and their usage in NDIS 6.X drivers on Windows Vista and later versions of Windows.
A Net Buffer, represented by the NET_BUFFER structure, describes a single network packet. A Net Buffer List, represented by the NET_BUFFER_LIST structure, describes a group of packets that have common out of band (OOB) data. An MDL describes a single virtually contiguous data buffer. The buffer described by a MDL can comprise of pages that are physically fragmented. Each physical page is described by an entry in the Page Frame Number (PFN) array that is stored as a part of the MDL. In NDIS 5.X, packet transmission and reception functions were presented with an array of pointers to NDIS_PACKET structures such that they could perform multi-packet sends and receives. In NDIS 6.0, multi-packet sends and receives are facilitated by providing drivers with a single pointer that points to a chain of NBLs.
Figure 1 : NBL, NB, MDL structures and network packet data
Figure #1 shows the relationship between NBL, NB, MDL structures and network packet data. In this figure there is a single NBL and 2 NBs. Each NB describes a separate network packet. NB#1 describes a packet that is stored in buffers that are virtually discontiguous and hence the NB#1 points to a chain of 2 MDLs i.e. MDL#1 and MDL#2 each one of which describes a single virtually contiguous buffer i.e. Buffer #1 and Buffer #2 respectively. NB#2 on the other hand describes a packet that is stored in a virtually contiguous buffer hence it points to a single MDL i.e. MDL #3. The labels on the arrows depict the fields in the data structures that point to the target.
NDIS 6.0 Send and Receive NBL handlers don't return a status value to convey the result of NBL processing as these functions could potentially deal with a chain of NBLs each one of which could encounter a different error while being processed. Instead, functions that process NBLs set the result of processing the individual NBL in the NET_BUFFER_LIST.Status field which can be accessed using the macro NET_BUFFER_LIST_STATUS().
Even in Windows 7 quite a few network drivers, especially form third party ISVs and IHVs, continue to use the old NDIS 5.X interfaces. So NDIS6 in Windows 7 continues to support the co-existence of NDIS 6.X and NDIS 5.X modules on the same system. NDIS will internally translate NET_BUFFER_LISTs to and from NDIS_PACKETs to pass packet data between NDIS 6.X and NDIS 5.X drivers.
The data buffer described by a NB can be split into unused data space and used data space. The unused data space (also known as backfill space), if any, is located before the start of the used data space (i.e. space that contains the valid data) described by the NB. As the NB is forwarded from one driver to another in the networking stack the position of the split can be changed.
The NB structure fields DataOffset, CurrentMdl and CurrentMdlOffset determine the location of the split. DataOffset is the number of bytes from the start of the buffer described by the NB where the used data space begins.
CurrentMDL is a convenience provided by NDIS whereby a driver can get to the first MDL in the NB that describes the valid data. CurrentMdlOffset is the offset of the valid data from the start of the buffer described by CurrentMdl. NdisAdjustNetBufferCurrentMdl() sets the CurrentMdlOffset and CurrentMdl fields of the NB based on the current value of DataOffset.
Figure 2 : NET_BUFFER used and unused data space
Figure #2 displays the unused data space and the used data space in the buffer described by the NB. The unused data space is not virtually contiguous and hence described by two separate MDLs. The used data space (the shaded area) is at an offset of NET_BUFFER.DataOffset from the start of the buffer described by the NB. When a network driver attempts to access the valid data in the buffer it can start at the MDL pointed to by NET_BUFFER.CurrentMdl and access the data at an offset of NET_BUFFER.CurrentMdlOffset from the start of the data buffer described by that MDL. The valid data in the NB straddles 2 buffers i.e. Buffer #2 and Buffer #3, this is determined by NET_BUFFER.DataLength. The remaining space in Buffer#3 is unused.
NdisRetreatNetBufferDataStart() increases the used data space it the net buffer by decrementing NET_BUFFER.DataOffset thus giving the calling driver access to packet header prior the current header. NdisRetreatNetBufferDataStart() can also allocate extra unused data space if desired by the caller. NdisAdvanceNetBufferDataStart() decreases the used data space in the net buffer by incrementing NET_BUFFER.DataOffset. To retreat the data offset in all the net buffers that are linked to a NBL use the function NdisRetreatNetBufferListDataStart(). Both these functions are used to provide drivers, at a particular layer in the network stack, access to the appropriate header in the packet.
Figure 3 : NET_BUFFER data offset
Figure #3 shows the state of a NBL before and after the NB data start position has been retreated. The upper part of the figure shows the original NBL that has some backfill space in the first buffer. The lower part of the figure shows the NBL after the data start has been retreated. As a part of the retreat operation a new MDL and new data buffer have been allocated and pre-pended to the existing MDLs because the backfill space (NET_BUFFER.DataOffset) in the original NB was smaller than the backfill space (DataBackFill) and retraction offset (DataOffsetDelta) requested by the driver via the call to the function NdisRetreatNetBufferDataStart().
Unlike most windows kernel data structures that are allocated from system-wide executive pools, NBLs and NBs are allocated from pools created specifically for that purpose to prevent fragmentation of the system-wide executive pools. Drivers typically create their own pools for allocating NBLs and NBs in their MiniportInitializeEx() or ProtocolBindAdapterEx() routines.
NdisAllocateNetBufferListPool() and NdisFreeNetBufferListPool() allocates and frees a pool from which NBLs and optionally NBs, MDLs and data buffers can be allocated. The value of the field NET_BUFFER_LIST_POOL_PARAMETERS.fAllocateNetBuffer specified during the NBL pool allocation determines if both NBLs and NBs or only NBLs are allocated from that pool. In addition, MDLs and data buffers can also be allocated from the same NBL pool if specified at creation time through the NET_BUFFER_LIST_POOL_PARAMETERS.DataSize parameter. NDIS maintains a list of NBL pools in the global variable ndis!ndisGlobalNetBufferListPoolList and a list of all NB pools in ndis!ndisGlobalNetBufferPoolList.
NBLs, NB, MDL and data buffers can be allocated using NdisAllocateNetBufferList() and NdisAllocateNetBufferAndNetBufferList() and freed back to the NBL pool using NdisFreeNetBufferList().
When allocating NBLs, if the caller does not specify a valid pool handle to allocate from, the NBL is allocated from the default NBL pool whose handle stored in the NDIS global ndis!ndisNetBufferListPool. When allocating NBs through NdisAllocateNetBuffer() if the caller did not specify a valid pool handle to allocate from, the NB is allocated from the default NB pool whose handle stored in the NDIS global ndis!ndisNetBufferPool.
The following table shows the result of calling NdisAllocateNetBufferList() on a pool that was created with different values of NET_BUFFER_LIST_POOL_PARAMETERS.fAllocateNetBuffer and NET_BUFFER_LIST_POOL_PARAMETERS.DataSize passed to NdisAllocateNetBufferListPool().
NBL and NB created
NBL, NB, MBL and data buffer created
Only NBL Created
The following table shows the result of calling NdisAllocateNetBufferAndNetBufferList() on a pool that was created with different values of NET_BUFFER_LIST_POOL_PARAMETERS.fAllocateNetBuffer and NET_BUFFER_LIST_POOL_PARAMETERS.DataSize passed to NdisAllocateNetBufferListPool().
NBL and NB created
NBL, NB, MBL and data buffer created
Contexts are dynamically allocated out of band (OOB) storage areas where drivers can store proprietary data. Contexts are associated with NBLs and not individual NBs. A single NBL can have a chain of context area associated with it.
The size of this context data buffer is specified in the field NET_BUFFER_LIST_POOL_PARAMETERS.ContextSize during a call to NdisAllocateNetBufferListPool().When a NBL is allocated from the pool NDIS will automatically allocate an initial context data buffer and associate it with the NBL.
As the NBL traverses through various drivers in the networking stack, each component can call NdisAllocateNetBufferListContext() to sub-allocate a part of the context data buffer and assign it to the caller. Context data area for a particular context allocation request is always contiguous. If the amount of context space requested by the driver cannot be accommodated in the current context data buffer then a new context data buffer, big enough to accommodate the request, is allocated by NDIS and chained to the existing context data buffers. The ContextSize and ContextBackFill parameters to the function NdisAllocateNetBufferListContext() must be a multiple of pointer size (4 bytes on x86 and 8 bytes on X64). Context areas have to be freed using NdisFreeNetBufferListContext() in the reverse order of their allocation.
Once a context area is allocated by a driver it can obtain a pointer to its own context area by calling NET_BUFFER_LIST_CONTEXT_DATA_START().
Figure 4 : NBL Contexts
Figure#4 shows the context areas for a NBL during packet reception. The context buffer (Context #1) was created and associated with the NBL right at the time the NBL was created by the NDIS miniport driver. The miniport driver allocated its own context area (Driver #1 Context Area) and then indicated the NBL to the NDIS intermediate driver which in turn allocated its own context area (Driver#2 Context Area) and indicated the NBL to the NDIS protocol driver. The context area size that was requested by the protocol driver was too big to be accommodated in the existing context buffer (Context #1). So NDIS allocated a new context buffer (Context#2) big enough to hold the protocol driver requested context size and a context back fill area and assigned the context area (Driver #3 Context Area) to the protocol driver.
Child NBLs can be derived by the cloning, fragmenting and reassembling parent NBLs. Once drivers create child NBLs they should retain the parent NBL and only forward the child NBL to other drivers. Driver creating child NBLs should increment the parent NBL's ChildRefCount and set the child NBLs ParentNetBufferList to point to the NBL the child is derived from. Creating a child NBL involves replicating the NBL, NB and MDL structures that describe the packet data buffer in the parent NBL. The data buffer itself, the OOB data and any context associated with the parent NBL is not replicated.
NdisAllocateCloneNetBufferList() and NdisFreeCloneNetBufferList() are functions that allocate and free cloned NBLs. The driver has to perform these steps manually if required. Drivers can choose (via the flag NDIS_CLONE_FLAGS_USE_ORIGINAL_MDLS) if the cloned NBL will use the MDL structures in the original NBL or allocate new MDLs with the cloned NBLs and NBs.
Figure 5 : NBL Cloning
Figure#5 shows the original parent NBL and the child NBL cloned from the parent. The NBL, NB, and MDL data structure have been replicated when the NBL was cloned but data buffers were not. Also the child NBL did not inherit either the NBL context or the OOB data that was associated with the parent.
NdisAllocateFragmentNetBufferList() and NdisFreeFragmentNetBufferList() are functions that allocate and free fragment NBLs. Fragment NBLs are used to split large data (described by a single NB) into smaller chunks (packets) each described by a separate NB in the same NBL. Each NB can have its own backfill space allowing headers to be inserted within the large data chunk. The backfill space in the parent NB is lost.
NdisAllocateReassembledNetBufferList() and NdisFreeReassembledNetBufferList() allocate and free reassembled NBLs. Reassembled NBLs are used to combine data from multiple NBs into a single NB. The backfill space in each one of parent NBs is lost.
Unlike cloning where the packet meta-data i.e. the NBL, NB and MDL structures are replicated, NB copying involves replicating all or part of the data contents as well from the source NB to the destination NB. The function NdisCopyFromNetBufferToNetBuffer() performs the copying operation. Since this function works on the NB rather than the NBL the caller has to iterate over all the NBs in the NBL to copy the NBL in its entirety. The destination NB, MDL and data buffer can be allocated by NdisAllocateNetBufferMdlAndData().
Starting with the latest version of the debugger 6.12.0.033, most of the commands in the NDIS kernel debugger extension are actually functional and display useful information. The extension DLL NDISKD.dll can be found in WINEXT in the debugger's installation directory. !ndiskd.help displays a list of commands. The following table lists the commands in NDISKD.dll that are related to NBLs.
Show information about a NET_BUFFER_LIST.
Show information about a NET_BUFFER.
Show information about a NET_BUFFER_LIST pool.
Show information about a NET_BUFFER pool.
Show all NET_BUFFER_LISTs that are in transit. Does not work with NDIS public symbols.
Show information about all NET_BUFFER_LIST pools in the system. Does not work with NDIS public symbols.
Show information about all NET_BUFFER pools in the system. Does not work with NDIS public symbols.
Check out our training courses on Windows internals, drivers, debugging, malware, rootkits and other security topics.