What a Callout Is?
In WFP, a callout
is a custom extension point in the network stack that you implement in a kernel-mode driver. It lets you inject your own logic into Windows' packet/stream/connection processing pipeline.
Think of WFP is a set of layers through which network data flows. A filter can say:
When you see matching traffic at this layer, instead of just allowing or blocking it, run this extra bit of code.
That “extra bit of code” = your callout.
Two Parts of a Callout
You define a callout in two domains:
Part | API Prefix | Purpose | Where It Lives |
---|---|---|---|
Runtime definition | FWPS_CALLOUT | Links GUID to your function pointers (classifyFn , notifyFn , flowDeleteFn ). | Kernel memory (WFP runtime) |
Policy definition | FWPM_CALLOUT | Describes the callout object to the Filtering Engine so filters can reference it. | Filtering Engine policy store |
Callbacks in a Callout
classifyFn
– Required- Runs when your callout is triggered by a filter. This is where you inspect or modify traffic, allow/block, inject packets, etc.
notifyFn
– Optional- Called when a filter using your callout is added or removed. Lets you allocate or free per-filter resources.
flowDeleteFn
– Optional- Called when a flow your callout is handling ends. Used for cleanup.
Registering a Callout
Kernel runtime registration (FWPS_CALLOUT
):
This is the structure of it.
typedef struct FWPS_CALLOUT_
{
GUID calloutKey; // Unique ID (must match FWPM_CALLOUT)
UINT32 flags; // Usually 0
FWPS_CALLOUT_CLASSIFY_FN0 classifyFn; // REQUIRED - your classify function
FWPS_CALLOUT_NOTIFY_FN0 notifyFn; // OPTIONAL - called when filter added/removed
FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN0 flowDeleteFn; // OPTIONAL - called when flow deleted
} FWPS_CALLOUT;
Register it:
FWPS_CALLOUT runtimeCallout = {0};
runtimeCallout.calloutKey = MY_CALLOUT_GUID;
runtimeCallout.classifyFn = MyClassifyFn;
runtimeCallout.notifyFn = MyNotifyFn;
runtimeCallout.flowDeleteFn = MyFlowDeleteFn;
UINT32 calloutId = 0;
status = FwpsCalloutRegister(
deviceObject,
&runtimeCallout,
&calloutId
);
Filtering Engine Registration (FWPM_CALLOUT
):
Policy callout registration structure.
typedef struct FWPM_CALLOUT_
{
GUID calloutKey; // Unique ID (must match FWPS_CALLOUT)
FWPM_DISPLAY_DATA0 displayData; // Name + description
UINT32 flags; // Usually 0
GUID applicableLayer; // Layer GUID (e.g., FWPM_LAYER_STREAM_V4)
GUID providerKey; // OPTIONAL - provider GUID if used
UINT64 providerDataSize; // OPTIONAL - extra provider data size
UINT8* providerData; // OPTIONAL - extra provider data
} FWPM_CALLOUT;
Add it to the Filtering Engine:
FWPM_CALLOUT mgmtCallout = {0};
mgmtCallout.calloutKey = MY_CALLOUT_GUID;
mgmtCallout.displayData.name = L"My Custom Callout";
mgmtCallout.applicableLayer = FWPM_LAYER_STREAM_V4;
status = FwpmCalloutAdd(
engineHandle,
&mgmtCallout,
NULL,
NULL
);
calloutKey
in both must match – it's GUID that uniquely identifies our callout.
Lifecycle
Driver loads
↓
FwpmEngineOpen // open session with Filtering Engine
↓
FwpsCalloutRegister // tell WFP runtime about your code
FwpmCalloutAdd // add policy object so filters can point to it
FwpmFilterAdd // create filter using your callout
↓
Traffic matches filter → classifyFn runs
↓
Driver unloads
↓
FwpmCalloutDelete + FwpsCalloutUnregister
FwpmEngineClose
Difference Between FWPS_CALLOUT
and FWPM_CALLOUT
Thing | FWPS_CALLOUT | FWPM_CALLOUT |
---|---|---|
What it is | The code you write — your classify, notify, and cleanup functions. | The label in the Filtering Engine’s database so filters can find and use your code. |
Lives in | Kernel memory, inside WFP’s runtime. | Filtering Engine’s policy store. |
When you use it | When registering your actual functions in the driver (FwpsCalloutRegister ). | When telling the Filtering Engine “I have a callout with this GUID” (FwpmCalloutAdd ). |
Who uses it | WFP runtime, to actually run your code. | Filters, to point to your callout by GUID. |
Analogy | The chef in the kitchen. | The menu entry that says “Dish #17 is made by Chef Bob.” |
Easy Analogy:
- FWPM_CALLOUT = The menu entry in a restaurant:
- customer (filters) pick it when they want that dish.
- FWPS_CALLOUT = The chef in the kitchen:
- actually cooks the dish when an order comes in.
- The GUID = The dish number linking the menu to the chef.
In short:
- FWPS_CALLOUT: is your actual code functions.
- FWPM_CALLOUT: is the database entry that lets filters find your code.
Step-by-Step Example
Step 1 – Define the Callout GUID
// Must be identical for FWPS_CALLOUT and FWPM_CALLOUT
// {12345678-90AB-CDEF-1234-567890ABCDEF} example GUID
DEFINE_GUID(
MY_CALLOUT_GUID,
0x12345678, 0x90ab, 0xcdef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef
);
Step 2 – FWPS_CALLOUT (Runtime registration)
This tells WFP what functions to call when your callout is triggered.
FWPS_CALLOUT s_callout = {0};
s_callout.calloutKey = MY_CALLOUT_GUID; // Must match FWPM_CALLOUT
s_callout.flags = 0; // Usually 0
s_callout.classifyFn = MyClassifyFn; // REQUIRED
s_callout.notifyFn = MyNotifyFn; // OPTIONAL
s_callout.flowDeleteFn = MyFlowDeleteFn; // OPTIONAL
UINT32 runtimeCalloutId = 0;
status = FwpsCalloutRegister(
deviceObject, // From DriverEntry
&s_callout,
&runtimeCalloutId
);
Step 3 – FWPM_CALLOUT (Policy registration)
This adds a callout object to the Filtering Engine's database so filters can reference it.
FWPM_CALLOUT m_callout = {0};
m_callout.calloutKey = MY_CALLOUT_GUID; // Same GUID
m_callout.displayData.name = L"My Custom Callout"; // Human-readable name
m_callout.displayData.description = L"Processes TCP stream data";
m_callout.applicableLayer = FWPM_LAYER_STREAM_V4; // Layer this callout works on
m_callout.flags = 0; // Usually 0
status = FwpmCalloutAdd(
engineHandle, // Obtained from FwpmEngineOpen in kernel mode
&m_callout,
NULL, // Security descriptor (optional)
NULL // Optional callout ID return
);
Step 4 – Add a Filter that Uses the Callout
FWPM_FILTER filter = {0};
filter.displayData.name = L"My TCP Stream Filter";
filter.layerKey = FWPM_LAYER_STREAM_V4;
filter.action.type = FWP_ACTION_CALLOUT_TERMINATING; // or INSPECTION
filter.action.calloutKey = MY_CALLOUT_GUID; // Link to callout
filter.filterCondition = NULL; // No conditions = all traffic
filter.numFilterConditions = 0;
status = FwpmFilterAdd(engineHandle, &filter, NULL, NULL);
Flow Summary
Packet hits FWPM_LAYER_STREAM_V4
↓
Matching filter found → action = calloutKey(MY_CALLOUT_GUID)
↓
Filtering Engine finds FWPM_CALLOUT for MY_CALLOUT_GUID
↓
Maps to FWPS_CALLOUT runtime object
↓
Calls your MyClassifyFn in kernel mode
Leave a comment
Your email address will not be published. Required fields are marked *