Skip to content

Commit 4ce2cdb

Browse files
Michael AgunCopilot
andcommitted
Add bind classify variants that select the WFP filter by callout key
usersim's test_callout() selects a single WFP filter by (layer, sublayer) first-match. When multiple callouts are registered at the same layer and sublayer -- for example the legacy bind callout and the CGROUP_SOCK_ADDR bind callout, both at ALE_RESOURCE_ASSIGNMENT -- that first-match can dispatch to the wrong callout, so a test cannot reliably target a specific callout. Add usersim_fwp_bind_ipv4_by_callout() / usersim_fwp_bind_ipv6_by_callout(), which select the filter bound to a caller-supplied callout key via a new get_fwpm_filter_by_callout_under_lock() helper. test_callout() and test_bind_ipv4()/test_bind_ipv6() take an optional callout_key; when null, behavior is unchanged (layer+sublayer first-match), so existing callers are unaffected. This lets a consumer (e.g. the ebpf-for-windows netebpfext bind tests) exercise a specific bind callout explicitly even when multiple bind callouts share a WFP layer. The public _by_callout wrappers assert a non-null callout_key. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c2ba0624-5e1d-4385-9972-eec1b8c595f1
1 parent 116a3fb commit 4ce2cdb

3 files changed

Lines changed: 73 additions & 9 deletions

File tree

inc/usersim/fwp_test.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ usersim_fwp_bind_ipv4(_In_ fwp_classify_parameters_t* parameters);
3737
USERSIM_API FWP_ACTION_TYPE
3838
usersim_fwp_bind_ipv6(_In_ fwp_classify_parameters_t* parameters);
3939

40+
// Bind-hook classify variants that select the WFP filter bound to a specific callout key. Use these
41+
// to target a specific bind callout (e.g., the CGROUP_SOCK_ADDR bind callout) when multiple callouts
42+
// are registered at the ALE_RESOURCE_ASSIGNMENT layer, instead of relying on layer+sublayer
43+
// first-match ordering.
44+
USERSIM_API FWP_ACTION_TYPE
45+
usersim_fwp_bind_ipv4_by_callout(_In_ fwp_classify_parameters_t* parameters, _In_ const GUID* callout_key);
46+
47+
USERSIM_API FWP_ACTION_TYPE
48+
usersim_fwp_bind_ipv6_by_callout(_In_ fwp_classify_parameters_t* parameters, _In_ const GUID* callout_key);
49+
4050
USERSIM_API FWP_ACTION_TYPE
4151
usersim_fwp_cgroup_inet4_recv_accept(_In_ fwp_classify_parameters_t* parameters);
4252

src/fwp_um.cpp

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ fwp_engine_t::classify_test_packet(_In_ const GUID* layer_guid, NET_IFINDEX if_i
130130

131131
// This is used to test the bind hook.
132132
FWP_ACTION_TYPE
133-
fwp_engine_t::test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters)
133+
fwp_engine_t::test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters, _In_opt_ const GUID* callout_key)
134134
{
135135
FWPS_INCOMING_VALUE0 incoming_value[FWPS_FIELD_ALE_RESOURCE_ASSIGNMENT_V4_MAX] = {};
136136
incoming_value[FWPS_FIELD_ALE_RESOURCE_ASSIGNMENT_V4_IP_LOCAL_PORT].value.uint16 = parameters->destination_port;
@@ -148,12 +148,13 @@ fwp_engine_t::test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters)
148148
FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4,
149149
_default_sublayer,
150150
incoming_value,
151-
nullptr);
151+
nullptr,
152+
callout_key);
152153
}
153154

154155
// This is used to test the IPv6 bind hook.
155156
FWP_ACTION_TYPE
156-
fwp_engine_t::test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters)
157+
fwp_engine_t::test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters, _In_opt_ const GUID* callout_key)
157158
{
158159
FWPS_INCOMING_VALUE0 incoming_value[FWPS_FIELD_ALE_RESOURCE_ASSIGNMENT_V6_MAX] = {};
159160
incoming_value[FWPS_FIELD_ALE_RESOURCE_ASSIGNMENT_V6_IP_LOCAL_PORT].value.uint16 = parameters->destination_port;
@@ -171,15 +172,17 @@ fwp_engine_t::test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters)
171172
FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6,
172173
_default_sublayer,
173174
incoming_value,
174-
nullptr);
175+
nullptr,
176+
callout_key);
175177
}
176178

177179
_Requires_lock_not_held_(this->lock) FWP_ACTION_TYPE fwp_engine_t::test_callout(
178180
uint16_t layer_id,
179181
_In_ const GUID& layer_guid,
180182
_In_ const GUID& sublayer_guid,
181183
_In_ FWPS_INCOMING_VALUE0* incoming_value,
182-
_Out_opt_ uint64_t* flow_id)
184+
_Out_opt_ uint64_t* flow_id,
185+
_In_opt_ const GUID* callout_key)
183186
{
184187
FWPS_INCOMING_VALUES incoming_fixed_values = {.layerId = layer_id, .incomingValue = incoming_value};
185188
FWPS_INCOMING_METADATA_VALUES incoming_metadata_values = {};
@@ -188,7 +191,12 @@ _Requires_lock_not_held_(this->lock) FWP_ACTION_TYPE fwp_engine_t::test_callout(
188191

189192
{
190193
shared_lock_t l(lock);
191-
const FWPM_FILTER* fwpm_filter = get_fwpm_filter_with_context_under_lock(layer_guid, sublayer_guid);
194+
// When a specific callout key is requested, select the filter bound to that callout so the
195+
// intended callout is exercised even if multiple callouts are registered at this layer and
196+
// sublayer. Otherwise fall back to first-match by layer+sublayer.
197+
const FWPM_FILTER* fwpm_filter =
198+
callout_key ? get_fwpm_filter_by_callout_under_lock(layer_guid, sublayer_guid, *callout_key)
199+
: get_fwpm_filter_with_context_under_lock(layer_guid, sublayer_guid);
192200
if (!fwpm_filter) {
193201
return FWP_ACTION_CALLOUT_UNKNOWN;
194202
}
@@ -1069,6 +1077,30 @@ usersim_fwp_bind_ipv6(_In_ fwp_classify_parameters_t* parameters)
10691077
return fwp_engine_t::get()->test_bind_ipv6(parameters);
10701078
}
10711079

1080+
FWP_ACTION_TYPE
1081+
usersim_fwp_bind_ipv4_by_callout(_In_ fwp_classify_parameters_t* parameters, _In_ const GUID* callout_key)
1082+
{
1083+
CXPLAT_DEBUG_ASSERT(callout_key != nullptr);
1084+
if (callout_key == nullptr) {
1085+
// Guard release builds where the assert is compiled out: without a callout key the engine would
1086+
// silently fall back to layer+sublayer first-match and could exercise the wrong callout.
1087+
return FWP_ACTION_CALLOUT_UNKNOWN;
1088+
}
1089+
return fwp_engine_t::get()->test_bind_ipv4(parameters, callout_key);
1090+
}
1091+
1092+
FWP_ACTION_TYPE
1093+
usersim_fwp_bind_ipv6_by_callout(_In_ fwp_classify_parameters_t* parameters, _In_ const GUID* callout_key)
1094+
{
1095+
CXPLAT_DEBUG_ASSERT(callout_key != nullptr);
1096+
if (callout_key == nullptr) {
1097+
// Guard release builds where the assert is compiled out: without a callout key the engine would
1098+
// silently fall back to layer+sublayer first-match and could exercise the wrong callout.
1099+
return FWP_ACTION_CALLOUT_UNKNOWN;
1100+
}
1101+
return fwp_engine_t::get()->test_bind_ipv6(parameters, callout_key);
1102+
}
1103+
10721104
FWP_ACTION_TYPE
10731105
usersim_fwp_cgroup_inet4_recv_accept(_In_ fwp_classify_parameters_t* parameters)
10741106
{

src/fwp_um.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,10 @@ typedef class fwp_engine_t
218218
classify_test_packet(_In_ const GUID* layer_guid, NET_IFINDEX if_index);
219219

220220
FWP_ACTION_TYPE
221-
test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters);
221+
test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters, _In_opt_ const GUID* callout_key = nullptr);
222222

223223
FWP_ACTION_TYPE
224-
test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters);
224+
test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters, _In_opt_ const GUID* callout_key = nullptr);
225225

226226
FWP_ACTION_TYPE
227227
test_cgroup_inet4_recv_accept(_In_ fwp_classify_parameters_t* parameters);
@@ -268,7 +268,8 @@ typedef class fwp_engine_t
268268
_In_ const GUID& layer_guid,
269269
_In_ const GUID& sublayer_guid,
270270
_In_ FWPS_INCOMING_VALUE0* incoming_value,
271-
_Out_opt_ uint64_t* flow_handle);
271+
_Out_opt_ uint64_t* flow_handle,
272+
_In_opt_ const GUID* callout_key = nullptr);
272273

273274
_Requires_lock_not_held_(this->lock) void test_remove_flow_context(
274275
uint64_t flow_id,
@@ -298,6 +299,27 @@ typedef class fwp_engine_t
298299
return nullptr;
299300
}
300301

302+
// Select a filter by its bound callout key at the given layer and sublayer. This disambiguates the
303+
// case where multiple callouts (each with its own filter) are registered at the same WFP layer and
304+
// sublayer (e.g., the legacy bind callout and the CGROUP_SOCK_ADDR bind callout both at
305+
// ALE_RESOURCE_ASSIGNMENT). Selecting the filter by its callout key lets a test target a specific
306+
// callout explicitly instead of relying on layer+sublayer first-match ordering. The sublayer is
307+
// still matched (mirroring get_fwpm_filter_with_context_under_lock) so selection stays unambiguous
308+
// even if a callout ever owns filters on more than one sublayer.
309+
_Ret_maybenull_ const FWPM_FILTER*
310+
get_fwpm_filter_by_callout_under_lock(
311+
_In_ const GUID& layer_guid, _In_ const GUID& sublayer_guid, _In_ const GUID& callout_key)
312+
{
313+
for (auto& [first, filter] : fwpm_filters) {
314+
if (memcmp(&filter.layerKey, &layer_guid, sizeof(GUID)) == 0 &&
315+
memcmp(&filter.subLayerKey, &sublayer_guid, sizeof(GUID)) == 0 &&
316+
memcmp(&filter.action.calloutKey, &callout_key, sizeof(GUID)) == 0 && filter.rawContext != 0) {
317+
return &filter;
318+
}
319+
}
320+
return nullptr;
321+
}
322+
301323
_Ret_maybenull_ const GUID*
302324
get_callout_key_from_layer_guid_under_lock(_In_ const GUID* layer_guid)
303325
{

0 commit comments

Comments
 (0)