Hello,
I debug an interesting problem which is reproducible with tap-windows6 driver on Windows Server 2019/2022 when using iperf3. The problem doesn’t reproduce with iPerf2 or on Windows 10.
I have an OpenVPN tunnel between Windows and Linux and run iperf3 -c 10.8.0.1 -R -V -t 1
on Windows machine. This doesn’t really work - after a while iPerf completes with very poor performance like tens on Kb. For the contrast, without “-R” flag (means that Windows machine sends) or on Windows 10 I got over 100Mbit/s. Without VPN I got 3.6Gbit/s, but poor performance of the tap-windows6 driver is outside of scope of this problem.
Here is a very simplified data flow:
iperf <-> tun interface (tap-windows6 driver) <-> openvpn (encrypt/decrypt) <-> network <-> other peer
I took TCP dump on both ends and found out that while on transport interface all TCP packets arrive from Linux to Windows as expected, on VPN (tun) interface at some point certain packet arrives with noticeable delay, like one second. I added additional logging to OpenVPN and turns out that delay happens during writing to tun interface. OpenVPN uses overlapped IO - it passes OVERLAPPED structure to WriteFile and then does WSAWaitForMultipleEvents, including event from abovementioned structure. No further writes to tun happen unless WSAWaitForMultipleEvents reports that previous write has been completed.
WriteFile inside OpenVPN calls IRP_MJ_WRITE handler in tap-windows6 driver. This call indicates received data to OS. IRP_MJ_WRITE handler does this (yes I know that this is far from optimal and we've developed another driver which improves performance dramatically, but this is another topic):
- allocates NBL
- copies packet content to NBL
- marks IRP as pending
- calls NdisMIndicateReceiveNetBufferLists
- returns STATUS_PENDING
After that NDIS calls ReturnNetBufferListsHandler callback (usually this happens between steps 4 and 5), where we free NBL and complete IRP from previous step.
The mysterious thing is that in iperf3 test scenario, 8th (and following) packet of length 1442 takes about second to digest. Here is a driver log:
packet 6, so far so good
Thu Apr 7 15:22:42.639 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.640 2022 (UTC + 3:00): Enter IRP_MJ_WRITE, 1442 bytes
Thu Apr 7 15:22:42.640 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.640 2022 (UTC + 3:00): NdisMIndicateReceiveNetBufferLists()
Thu Apr 7 15:22:42.640 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.640 2022 (UTC + 3:00): Enter ReturnNetBufferListsHandler
Thu Apr 7 15:22:42.640 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.641 2022 (UTC + 3:00): IoCompleteRequest, bytecount 1456, status 0
Thu Apr 7 15:22:42.641 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.641 2022 (UTC + 3:00): Exit ReturnNetBufferListsHandler
Thu Apr 7 15:22:42.641 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.641 2022 (UTC + 3:00): Exit IRP_MJ_WRITE, status=259
Thu Apr 7 15:22:42.641 2022 (UTC + 3:00):
packet 7, still good
Thu Apr 7 15:22:42.641 2022 (UTC + 3:00): Enter IRP_MJ_WRITE, 1442 bytes
Thu Apr 7 15:22:42.641 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.642 2022 (UTC + 3:00): NdisMIndicateReceiveNetBufferLists()
Thu Apr 7 15:22:42.642 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.642 2022 (UTC + 3:00): Enter ReturnNetBufferListsHandler
Thu Apr 7 15:22:42.642 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.642 2022 (UTC + 3:00): IoCompleteRequest, bytecount 1456, status 0
Thu Apr 7 15:22:42.642 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.642 2022 (UTC + 3:00): Exit ReturnNetBufferListsHandler
Thu Apr 7 15:22:42.642 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.643 2022 (UTC + 3:00): Exit IRP_MJ_WRITE, status=259
Thu Apr 7 15:22:42.643 2022 (UTC + 3:00):
packet 8, here we have a problem
Thu Apr 7 15:22:42.643 2022 (UTC + 3:00): Enter IRP_MJ_WRITE, 1442 bytes
Thu Apr 7 15:22:42.643 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.648 2022 (UTC + 3:00): Packet len 1442 #8, expect delay!
Thu Apr 7 15:22:42.648 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.649 2022 (UTC + 3:00): NdisMIndicateReceiveNetBufferLists()
Thu Apr 7 15:22:42.649 2022 (UTC + 3:00):
Thu Apr 7 15:22:42.649 2022 (UTC + 3:00): Exit IRP_MJ_WRITE, status=259
Thu Apr 7 15:22:42.649 2022 (UTC + 3:00):
!!DELAY!!
Thu Apr 7 15:22:44.111 2022 (UTC + 3:00): Enter ReturnNetBufferListsHandler
Thu Apr 7 15:22:44.111 2022 (UTC + 3:00):
Thu Apr 7 15:22:44.112 2022 (UTC + 3:00): IoCompleteRequest, bytecount 1456, status 0
Thu Apr 7 15:22:44.112 2022 (UTC + 3:00):
Thu Apr 7 15:22:44.113 2022 (UTC + 3:00): Exit ReturnNetBufferListsHandler
Thu Apr 7 15:22:44.113 2022 (UTC + 3:00):
packet 9, again delay
Thu Apr 7 15:22:44.113 2022 (UTC + 3:00): Enter IRP_MJ_WRITE, 1442 bytes
Thu Apr 7 15:22:44.113 2022 (UTC + 3:00):
Thu Apr 7 15:22:44.114 2022 (UTC + 3:00): NdisMIndicateReceiveNetBufferLists()
Thu Apr 7 15:22:44.114 2022 (UTC + 3:00):
Thu Apr 7 15:22:44.115 2022 (UTC + 3:00): Exit IRP_MJ_WRITE, status=259
Thu Apr 7 15:22:44.115 2022 (UTC + 3:00):
!!HEY NDIS, WHY IS TAKES SO LONG TO DIGEST THIS TCP PACKET?!!
Thu Apr 7 15:22:45.115 2022 (UTC + 3:00): Enter ReturnNetBufferListsHandler
Thu Apr 7 15:22:45.115 2022 (UTC + 3:00):
Thu Apr 7 15:22:45.117 2022 (UTC + 3:00): IoCompleteRequest, bytecount 1456, status 0
Thu Apr 7 15:22:45.117 2022 (UTC + 3:00):
Thu Apr 7 15:22:45.117 2022 (UTC + 3:00): Exit ReturnNetBufferListsHandler
Thu Apr 7 15:22:45.117 2022 (UTC + 3:00):
I can work around the problem by specifying NDIS_RECEIVE_FLAGS_RESOURCES flag when calling NdisMIndicateReceiveNetBufferLists - this allows to free NBLs immediately, complete IRP and return STATUS_SUCCESS - I checked that this works. However this doesn’t answer the question why certain packet indication takes about second and only on Windows Server 2019/2022 - there is no such problem on Windows 10. Also this particular driver (tap-windows6) is retiring and I would prefer not to make somewhat major change, especially without understanding the root cause.
Any ideas how I can debug this problem further, figure out what causes this delay and get rid of it?