Hi,
Let me ask you for some help.
I thought NDIS driver writers had to ensure NET_BUFFER 4-byte alignment at the Ethernet-IP border. That is, the 14 byte Ethernet header, if in the same MDL than the IP header, had to begin at an alignment offset of 2, so that the IP header was 4-byte aligned.
But, after reading other threads in this forum, now I think the opposite: NET_BUFFERs passed to FilterSendNetBufferLists/FilterReceiveNetBufferLists can be unaligned.
What seems counterintuitive, because an appropriate allocation and use of buffers could prevent that for the sake of performance.
So, if I want to read an IPV4_HEADER, and I want to avoid copies as much as possible, I could write:
NdisAdvanceNetBufferDataStart(NetBuffer, sizeof(ETHERNET_HEADER), FALSE, NULL);
__declspec(align(4)) UINT8 Storage[sizeof(IPV4_HEADER)]; // Only used if the IPv4 header is not contiguous.
UNALIGNED PIPV4_HEADER IPHeader = (UNALIGNED PIPV4_HEADER)NdisGetDataBuffer(NetBuffer, sizeof(IPV4_HEADER), Storage, 1, 0);
Is it right that the “UNALIGNED” macro is mandatory in this case if I don’t want to increase the AlignMultiple value passed to NdisGetDataBuffer?
That would solve the reading part.
Now imagine the header is not contiguous (Sending Ethernet Frames only prohibits MAC header splitting) and I need to write directly to it. Then, I need access to individual bytes for writing.
I guess using NdisGetDataBuffer with BytesNeeded = 1 can be overkill.
I’m currently using this alternative code to get a pointer to a single byte to write to it:
PUINT8 Byte = (PUINT8)MmGetMdlVirtualAddress(NET_BUFFER_CURRENT_MDL(NetBuffer)) + NET_BUFFER_CURRENT_MDL_OFFSET(NetBuffer);
In fact, I used to read the ETHERNET_HEADER (which is documented to be always contiguous) also this way (casting to PETHERNET_HEADER instead of PUINT8). But I’ve stopped doing it (in favor of NdisGetDataBuffer), because I don’t know if the guarantee is forced by NDIS.
Anyway, I’ve read the documentation for MmGetMdlVirtualAddress and it says:
MmGetMdlVirtualAddress returns a virtual address that is not necessarily valid in the current thread context. Lower-level drivers should not attempt to use the returned virtual address to access memory, particularly user memory space.
My question is: even if that is true in the general case (particularly for user memory space), can I assume in FilterSendNetBufferLists/FilterReceiveNetBufferLists that NET_BUFFERs handed to me are already locked and mapped into kernel virtual memory, and that MmGetMdlVirtualAddress will return a valid virtual address I can directly use without any other measures?
If not, what would be the correct way of writing to a non-contiguous NET_BUFFER?
I know it is inadvisable to modify the original NET_BUFFER instead of modifying a clone, and I actually clone the NET_BUFFER_LIST, but first I need to advance each NET_BUFFER past the TCP header, then clone, and finally retreat the original buffer and the clone by different lengths (the clone will have some additional headers added by the driver). As I want to deal with IP and TCP options differing among the different NET_BUFFERs in a NET_BUFFER_LIST passed to FilterSendNetBufferLists, I’m currently modifying the original buffer (at the first byte after the “advance”) to keep a backup of the length I need to retreat (a complicated trick because I don’t want to allocate context space).
My doubt also comes from the documented fact that NdisGetDataBuffer can return NULL when NB’s buffer can’t be mapped into VA. If that is the case also for filter drivers, buffers could be “not mapped into VA” when passed to the filter driver, making MmGetMdlVirtualAddress useless.
Last question: can I definitely assume NdisRetreatNetBufferDataStart will always return NDIS_STATUS_SUCCESS (as to ignore the result) if retreating an offset previously advanced (at least) by a call to NdisAdvanceNetBufferDataStart? I’m trying to reduce the amount of conditions I check, because they are overwhelming.
Thank you very much.
Best regards.