CLOSE
Updated on 13 Aug, 202524 mins read 13 views

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:

PartAPI PrefixPurposeWhere It Lives
Runtime definitionFWPS_CALLOUTLinks GUID to your function pointers (classifyFn, notifyFn, flowDeleteFn).Kernel memory (WFP runtime)
Policy definitionFWPM_CALLOUTDescribes the callout object to the Filtering Engine so filters can reference it.Filtering Engine policy store

Callbacks in a Callout

  1. classifyFn – Required
    1. Runs when your callout is triggered by a filter. This is where you inspect or modify traffic, allow/block, inject packets, etc.
  2. notifyFn – Optional
    1. Called when a filter using your callout is added or removed. Lets you allocate or free per-filter resources.
  3. flowDeleteFn – Optional
    1. 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

ThingFWPS_CALLOUTFWPM_CALLOUT
What it isThe 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 inKernel memory, inside WFP’s runtime.Filtering Engine’s policy store.
When you use itWhen registering your actual functions in the driver (FwpsCalloutRegister).When telling the Filtering Engine “I have a callout with this GUID” (FwpmCalloutAdd).
Who uses itWFP runtime, to actually run your code.Filters, to point to your callout by GUID.
AnalogyThe 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 *