zapret2-fork/nfq2/winnative/batch.c
loop-uh dc41b27493
Some checks failed
build.yml / winws2 native: send the held batch before the receive loop exits (push) Failing after 0s
winws2 native: send the held batch before the receive loop exits
Unmodified packets were held until the next receive. On quit and when the
logical network disappears upstream leaves windivert_recv() before receiving,
so the last run (up to 128 packets) never reached the network. Stress on win10:
20 of 20 stops lost a full run, 2548 packets in total; 0 after the fix.

usleep(0), which starts windivert_recv_exit(), now sends the run and polls the
control events, as cygwin delivered signals there. This also makes the term
event work while the engine waits for a logical network (it used to hang).
WinDivertClose forgets the run of the handle without reading the packets.
tests/check_upstream.py fails the sync if upstream drops that usleep call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:16:34 +03:00

106 lines
4 KiB
C

// Batched reinjection of unmodified packets.
// winws2 receives packets from windivert in bulk but reinjects them one by one : one syscall per packet.
// Here consecutive unmodified packets of the received bulk are collected and sent by one WinDivertSendEx call.
// Order of packets on the wire is preserved : anything else that is sent (modified packet, fake from lua)
// flushes the collected run first. Next receive flushes it too.
// The run must not outlive the receive loop : on quit and when the logical network disappears upstream returns
// from windivert_recv() before the next receive, frees the receive buffer and closes the filter handle.
// Both exits start with usleep(0), so usleep flushes the run as well. WinDivertClose forgets the run of the handle
// and never touches the collected packets : on quit the receive buffer is already freed when the handle is closed.
// Upstream sources are not changed. WinDivertRecvEx/WinDivertSend/WinDivertClose/usleep are redirected here
// by macros in compat.h.
// Set environment variable WINWS2_NO_BATCH=1 to turn batching off.
#include "windivert/windivert.h"
#undef WinDivertRecvEx
#undef WinDivertSend
#undef WinDivertClose
#undef usleep
static const uint8_t *rcv_buf = NULL; // last receive buffer
static size_t rcv_size = 0;
static const WINDIVERT_ADDRESS *rcv_addr = NULL; // last receive address array
static unsigned int rcv_addr_count = 0;
static HANDLE run_handle = NULL; // collected run
static const uint8_t *run_pkt = NULL;
static size_t run_len = 0;
static const WINDIVERT_ADDRESS *run_addr = NULL;
static unsigned int run_count = 0;
static int batch_off = -1;
static void wn_batch_flush(void)
{
if (run_count)
{
UINT sent;
WinDivertSendEx(run_handle, run_pkt, (UINT)run_len, &sent, 0, run_addr, run_count * sizeof(WINDIVERT_ADDRESS), NULL);
run_count = 0;
run_len = 0;
}
}
BOOL wn_WinDivertRecvEx(HANDLE handle, VOID *pPacket, UINT packetLen, UINT *pRecvLen, UINT64 flags, WINDIVERT_ADDRESS *pAddr, UINT *pAddrLen, LPOVERLAPPED lpOverlapped)
{
// receive buffer is about to be overwritten
wn_batch_flush();
wn_control_poll();
if (batch_off < 0) batch_off = getenv("WINWS2_NO_BATCH") != NULL;
rcv_buf = (const uint8_t*)pPacket;
rcv_size = packetLen;
rcv_addr = pAddr;
rcv_addr_count = (pAddr && pAddrLen) ? *pAddrLen / sizeof(WINDIVERT_ADDRESS) : 0;
return WinDivertRecvEx(handle, pPacket, packetLen, pRecvLen, flags, pAddr, pAddrLen, lpOverlapped);
}
BOOL wn_WinDivertSend(HANDLE handle, const VOID *pPacket, UINT packetLen, UINT *pSendLen, const WINDIVERT_ADDRESS *pAddr)
{
const uint8_t *pkt = (const uint8_t*)pPacket;
// packet that lives inside the receive buffer and has its original address is a reinject of unmodified packet.
// both buffers stay valid until the next receive
if (!batch_off && rcv_buf && pkt >= rcv_buf && pkt + packetLen <= rcv_buf + rcv_size &&
rcv_addr && pAddr >= rcv_addr && pAddr < rcv_addr + rcv_addr_count)
{
if (run_count && (handle != run_handle || pkt != run_pkt + run_len || pAddr != run_addr + run_count || run_count >= WINDIVERT_BATCH_MAX))
wn_batch_flush();
if (!run_count)
{
run_handle = handle;
run_pkt = pkt;
run_addr = pAddr;
}
run_len += packetLen;
run_count++;
if (pSendLen) *pSendLen = packetLen;
return TRUE;
}
wn_batch_flush();
return WinDivertSend(handle, pPacket, packetLen, pSendLen, pAddr);
}
BOOL wn_WinDivertClose(HANDLE handle)
{
if (run_handle == handle)
{
run_handle = NULL;
run_count = 0;
run_len = 0;
}
// the receive buffer may be freed after the close. next receive sets it again
rcv_buf = NULL;
rcv_addr = NULL;
rcv_addr_count = 0;
return WinDivertClose(handle);
}
// upstream calls usleep(0) to "make signals working" before it checks the quit flag and the logical network.
// do what cygwin did there : send what is held and deliver the pending control events.
// the waiting loop "logical network is not present" sleeps by usleep too and gets the stop request this way
int wn_usleep(useconds_t us)
{
wn_batch_flush();
int r = usleep(us);
wn_control_poll();
return r;
}