STATUS_FWP_TXN_IN_PROGRESS (0XC022000E) Fix – No Call Inside Transaction
This error means you tried a firewall API call that's banned inside an open transaction. The fix is wrapping it outside or using a different API.
Yeah, this error's a pain. You're coding against the Windows Filtering Platform, you open a transaction with FwpmTransactionBegin, do some work, then bam – 0XC022000E. The call is not allowed from within an explicit transaction. Let's fix it.
The Real Fix
- Move the offending call outside the transaction. The most common culprit is
FwpsCalloutRegisterorFwpsCalloutUnregisterById. These can't run inside an explicit transaction. So do this beforeFwpmTransactionBeginor afterFwpmTransactionCommit/FwpmTransactionAbort. - Use a different API that allows it. For example, instead of
FwpsCalloutRegisterinside a transaction, register the callout beforehand. Then inside the transaction, just update the callout's filter conditions viaFwpmFilterAdd. - If you must have both inside a transaction, split the work. Do the callout registration outside, then open a transaction for filter changes only. This is the cleanest pattern I've seen.
The reason step 3 works is: the callout registration is a one-time thing per driver load. It doesn't need atomicity with filter updates. The transaction is only for atomic filter changes. So you're just aligning with how WFP expects things.
Why This Happens
What's actually happening here is the WFP kernel module checks the current thread's transaction context. When you call FwpmTransactionBegin, it sets a flag on the thread. Then when you call a function like FwpsCalloutRegister, it sees that flag and returns 0XC022000E. The design decision behind this is that callout registration is a global operation – it alters the system's callout list, which other threads might read concurrently. Putting it inside a transaction could cause deadlocks or inconsistent states if the transaction aborts halfway.
Variations of the Same Issue
You might see this error in other places too:
- FwpmLayerCreate – Creating a new layer is also banned inside transactions. Same reason: it's a global change.
- FwpmProviderContextAdd – Adding a provider context works inside transactions (it's supported), but if you try to add one with a callout ID that doesn't exist yet, you get this error too. Because the callout check fails.
- FwpmFilterAdd with a callout ID that is not registered – This doesn't give 0XC022000E directly, but if you call
FwpmFilterAddinside a transaction and the callout is missing, it throws a different error. But the root cause is the same: you're mixing callout lifecycle with transaction.
How to Prevent It
Prevention is about knowing which WFP functions are transaction-safe. Here's a short rule of thumb:
| Safe inside transaction | Not safe inside transaction |
|---|---|
| FwpmFilterAdd | FwpsCalloutRegister |
| FwpmFilterDeleteById | FwpsCalloutUnregisterById |
| FwpmProviderContextAdd | FwpmLayerCreate |
| FwpmSublayerAdd | FwpmSessionCreate |
Also, always check the Microsoft documentation for each function. The Supported in explicit transaction field will tell you. I've learned this the hard way after spending 3 hours debugging a driver that crashed on Windows 10 22H2. The error only showed up when the system was under load, because the transaction commit happened before the callout registration was done.
One More Thing
If you're using user-mode WFP (FwpmEngine functions), the rules are similar but not identical. FwpmEngineOpen is never allowed inside a transaction. FwpmFilterAdd is fine. So the same pattern applies: register callouts first, then open transactions for filter work.
Short version: don't mix callout registration with filter transactions. Do them separately. That's the whole fix.
Hope this saves you the headache I had. Now go close that bug ticket.
Was this solution helpful?