-
Notifications
You must be signed in to change notification settings - Fork 268
Allow sockops BPF programs to query the WFP flow_id #4763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ typedef struct _net_ebpf_bpf_sock_ops | |
| EBPF_CONTEXT_HEADER; | ||
| bpf_sock_ops_t context; | ||
| uint64_t process_id; | ||
| uint64_t flow_id; ///< WFP flow ID associated with this connection. | ||
| } net_ebpf_sock_ops_t; | ||
|
|
||
| /** | ||
|
|
@@ -86,6 +87,27 @@ _ebpf_sock_ops_get_current_pid_tgid( | |
| return (sock_ops_ctx->process_id << 32 | (uint32_t)(uintptr_t)PsGetCurrentThreadId()); | ||
| } | ||
|
|
||
| // | ||
| // SOCK_OPS Program-type specific helper function implementation. | ||
| // | ||
| static uint64_t | ||
| _ebpf_sock_ops_get_flow_id( | ||
| uint64_t dummy_param1, | ||
| uint64_t dummy_param2, | ||
| uint64_t dummy_param3, | ||
| uint64_t dummy_param4, | ||
| uint64_t dummy_param5, | ||
| _In_ const bpf_sock_ops_t* ctx) | ||
| { | ||
| UNREFERENCED_PARAMETER(dummy_param1); | ||
| UNREFERENCED_PARAMETER(dummy_param2); | ||
| UNREFERENCED_PARAMETER(dummy_param3); | ||
| UNREFERENCED_PARAMETER(dummy_param4); | ||
| UNREFERENCED_PARAMETER(dummy_param5); | ||
| net_ebpf_sock_ops_t* sock_ops_ctx = CONTAINING_RECORD(ctx, net_ebpf_sock_ops_t, context); | ||
| return sock_ops_ctx->flow_id; | ||
| } | ||
|
|
||
| // | ||
| // SOCK_OPS Program Information NPI Provider. | ||
| // | ||
|
|
@@ -97,6 +119,13 @@ static ebpf_helper_function_addresses_t _ebpf_sock_ops_global_helper_function_ad | |
| EBPF_COUNT_OF(_ebpf_sock_ops_global_helper_functions), | ||
| (uint64_t*)_ebpf_sock_ops_global_helper_functions}; | ||
|
|
||
| static const void* _ebpf_sock_ops_program_type_specific_helper_functions[] = {(void*)_ebpf_sock_ops_get_flow_id}; | ||
|
|
||
| static ebpf_helper_function_addresses_t _ebpf_sock_ops_program_type_specific_helper_function_address_table = { | ||
| EBPF_HELPER_FUNCTION_ADDRESSES_HEADER, | ||
| EBPF_COUNT_OF(_ebpf_sock_ops_program_type_specific_helper_functions), | ||
| (uint64_t*)_ebpf_sock_ops_program_type_specific_helper_functions}; | ||
|
|
||
| static ebpf_result_t | ||
| _ebpf_sock_ops_context_create( | ||
| _In_reads_bytes_opt_(data_size_in) const uint8_t* data_in, | ||
|
|
@@ -116,6 +145,8 @@ _ebpf_sock_ops_context_destroy( | |
| static ebpf_program_data_t _ebpf_sock_ops_program_data = { | ||
| .header = EBPF_PROGRAM_DATA_HEADER, | ||
| .program_info = &_ebpf_sock_ops_program_info, | ||
| .program_type_specific_helper_function_addresses = | ||
| &_ebpf_sock_ops_program_type_specific_helper_function_address_table, | ||
| .global_helper_function_addresses = &_ebpf_sock_ops_global_helper_function_address_table, | ||
| .context_create = &_ebpf_sock_ops_context_create, | ||
| .context_destroy = &_ebpf_sock_ops_context_destroy, | ||
|
|
@@ -515,6 +546,9 @@ net_ebpf_extension_sock_ops_flow_established_classify( | |
| local_flow_context->parameters.layer_id = incoming_fixed_values->layerId; | ||
| local_flow_context->parameters.callout_id = net_ebpf_extension_get_callout_id_for_hook(hook_id); | ||
|
|
||
| // Store the flow_id in the sock_ops context for the helper function. | ||
| local_flow_context->context.flow_id = incoming_metadata_values->flowHandle; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we be particularly paranoid and follow WFP runtime checks? local_flow_context->context.flow_id = 0; |
||
|
|
||
| status = FwpsFlowAssociateContext( | ||
| local_flow_context->parameters.flow_id, | ||
| local_flow_context->parameters.layer_id, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -579,8 +579,8 @@ _ebpf_bind_context_create( | |
| ebpf_result_t retval; | ||
| *context = nullptr; | ||
| bind_md_t* bind_context = nullptr; | ||
| bind_context_header_t* bind_context_header = | ||
| reinterpret_cast<bind_context_header_t*>(ebpf_allocate_with_tag(sizeof(bind_context_header_t), EBPF_POOL_TAG_DEFAULT)); | ||
| bind_context_header_t* bind_context_header = reinterpret_cast<bind_context_header_t*>( | ||
| ebpf_allocate_with_tag(sizeof(bind_context_header_t), EBPF_POOL_TAG_DEFAULT)); | ||
| if (bind_context_header == nullptr) { | ||
| retval = EBPF_NO_MEMORY; | ||
| goto Done; | ||
|
|
@@ -714,8 +714,8 @@ _ebpf_sock_addr_context_create( | |
| *context = nullptr; | ||
|
|
||
| bpf_sock_addr_t* sock_addr_context = nullptr; | ||
| sock_addr_context_header_t* sock_addr_context_header = | ||
| reinterpret_cast<sock_addr_context_header_t*>(ebpf_allocate_with_tag(sizeof(sock_addr_context_header_t), EBPF_POOL_TAG_DEFAULT)); | ||
| sock_addr_context_header_t* sock_addr_context_header = reinterpret_cast<sock_addr_context_header_t*>( | ||
| ebpf_allocate_with_tag(sizeof(sock_addr_context_header_t), EBPF_POOL_TAG_DEFAULT)); | ||
| if (sock_addr_context_header == nullptr) { | ||
| retval = EBPF_NO_MEMORY; | ||
| goto Done; | ||
|
|
@@ -818,13 +818,27 @@ _ebpf_sock_ops_get_current_pid_tgid( | |
| return 0; | ||
| } | ||
|
|
||
| static uint64_t | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this needed in addition to the one in netebpfext/net_ebpf_ext_sock_ops.c ?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is for the mock layer that allows BPF programs to be run in unit_tests.exe, entirely in user mode with out netebpfext.sys. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, thanks |
||
| _ebpf_sock_ops_get_flow_id(_In_ const bpf_sock_ops_t* ctx) | ||
| { | ||
| UNREFERENCED_PARAMETER(ctx); | ||
| return 12345; // Mock flow ID for testing. | ||
| } | ||
|
|
||
| static const void* _ebpf_sock_ops_global_helper_functions[] = {(void*)_ebpf_sock_ops_get_current_pid_tgid}; | ||
|
|
||
| static ebpf_helper_function_addresses_t _ebpf_sock_ops_global_helper_function_address_table = { | ||
| EBPF_HELPER_FUNCTION_ADDRESSES_HEADER, | ||
| EBPF_COUNT_OF(_ebpf_sock_ops_global_helper_functions), | ||
| (uint64_t*)_ebpf_sock_ops_global_helper_functions}; | ||
|
|
||
| static const void* _ebpf_sock_ops_program_type_specific_helper_functions[] = {(void*)_ebpf_sock_ops_get_flow_id}; | ||
|
|
||
| static ebpf_helper_function_addresses_t _ebpf_sock_ops_program_type_specific_helper_function_address_table = { | ||
| EBPF_HELPER_FUNCTION_ADDRESSES_HEADER, | ||
| EBPF_COUNT_OF(_ebpf_sock_ops_program_type_specific_helper_functions), | ||
| (uint64_t*)_ebpf_sock_ops_program_type_specific_helper_functions}; | ||
|
|
||
| static ebpf_result_t | ||
| _ebpf_sock_ops_context_create( | ||
| _In_reads_bytes_opt_(data_size_in) const uint8_t* data_in, | ||
|
|
@@ -839,8 +853,8 @@ _ebpf_sock_ops_context_create( | |
| *context = nullptr; | ||
|
|
||
| bpf_sock_ops_t* sock_ops_context = nullptr; | ||
| sock_ops_context_header_t* sock_ops_context_header = | ||
| reinterpret_cast<sock_ops_context_header_t*>(ebpf_allocate_with_tag(sizeof(sock_ops_context_header_t), EBPF_POOL_TAG_DEFAULT)); | ||
| sock_ops_context_header_t* sock_ops_context_header = reinterpret_cast<sock_ops_context_header_t*>( | ||
| ebpf_allocate_with_tag(sizeof(sock_ops_context_header_t), EBPF_POOL_TAG_DEFAULT)); | ||
| if (sock_ops_context_header == nullptr) { | ||
| retval = EBPF_NO_MEMORY; | ||
| goto Done; | ||
|
|
@@ -898,6 +912,8 @@ _ebpf_sock_ops_context_destroy( | |
| static ebpf_program_data_t _ebpf_sock_ops_program_data = { | ||
| .header = EBPF_PROGRAM_DATA_HEADER, | ||
| .program_info = &_ebpf_sock_ops_program_info, | ||
| .program_type_specific_helper_function_addresses = | ||
| &_ebpf_sock_ops_program_type_specific_helper_function_address_table, | ||
| .global_helper_function_addresses = &_ebpf_sock_ops_global_helper_function_address_table, | ||
| .context_create = &_ebpf_sock_ops_context_create, | ||
| .context_destroy = &_ebpf_sock_ops_context_destroy, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| // Copyright (c) eBPF for Windows contributors | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| // Whenever this sample program changes, bpf2c_tests will fail unless the | ||
| // expected files in tests\bpf2c_tests\expected are updated. The following | ||
| // script can be used to regenerate the expected files: | ||
| // generate_expected_bpf2c_output.ps1 | ||
| // | ||
| // Usage: | ||
| // .\scripts\generate_expected_bpf2c_output.ps1 <build_output_path> | ||
| // Example: | ||
| // .\scripts\generate_expected_bpf2c_output.ps1 .\x64\Debug\ | ||
|
|
||
| #include "bpf_helpers.h" | ||
| #include "ebpf_nethooks.h" | ||
| #include "net/ip.h" | ||
| #include "socket_tests_common.h" | ||
|
|
||
| struct | ||
| { | ||
| __uint(type, BPF_MAP_TYPE_HASH); | ||
| __type(key, connection_tuple_t); | ||
| __type(value, uint64_t); | ||
| __uint(max_entries, 10); | ||
| } flow_id_map SEC(".maps"); | ||
|
|
||
| struct | ||
| { | ||
| __uint(type, BPF_MAP_TYPE_RINGBUF); | ||
| __uint(max_entries, 256 * 1024); | ||
| } flow_id_audit_map SEC(".maps"); | ||
|
|
||
| typedef struct _flow_id_audit_entry | ||
| { | ||
| connection_tuple_t tuple; | ||
| uint64_t flow_id; | ||
| uint64_t process_id; | ||
| uint32_t operation; | ||
| bool outbound; | ||
| bool connected; | ||
| } flow_id_audit_entry_t; | ||
|
|
||
| inline int | ||
| update_flow_id_audit_map(flow_id_audit_entry_t* audit_entry) | ||
| { | ||
| return bpf_ringbuf_output(&flow_id_audit_map, audit_entry, sizeof(*audit_entry), 0); | ||
| } | ||
|
|
||
| inline int | ||
| handle_v4_flow_id(bpf_sock_ops_t* ctx, bool outbound, bool connected) | ||
| { | ||
| int result = 0; | ||
| flow_id_audit_entry_t audit_entry = {0}; | ||
|
|
||
| // Get the WFP flow ID using the new helper function. | ||
| uint64_t flow_id = bpf_sock_ops_get_flow_id(ctx); | ||
|
|
||
| audit_entry.tuple.local_ip.ipv4 = ctx->local_ip4; | ||
| audit_entry.tuple.local_port = ctx->local_port; | ||
| audit_entry.tuple.remote_ip.ipv4 = ctx->remote_ip4; | ||
| audit_entry.tuple.remote_port = ctx->remote_port; | ||
| audit_entry.tuple.protocol = ctx->protocol; | ||
| audit_entry.tuple.interface_luid = ctx->interface_luid; | ||
| audit_entry.process_id = bpf_get_current_pid_tgid(); | ||
| // Ignore the thread Id. | ||
| audit_entry.process_id >>= 32; | ||
| audit_entry.outbound = outbound; | ||
| audit_entry.connected = connected; | ||
| audit_entry.operation = ctx->op; | ||
| audit_entry.flow_id = flow_id; | ||
|
|
||
| // Store the flow ID in our map for later verification. | ||
| bpf_map_update_elem(&flow_id_map, &audit_entry.tuple, &flow_id, BPF_ANY); | ||
|
|
||
| return update_flow_id_audit_map(&audit_entry); | ||
| } | ||
|
|
||
| inline int | ||
| handle_v6_flow_id(bpf_sock_ops_t* ctx, bool outbound, bool connected) | ||
| { | ||
| int result = 0; | ||
| flow_id_audit_entry_t audit_entry = {0}; | ||
|
|
||
| // Get the WFP flow ID using the new helper function. | ||
| uint64_t flow_id = bpf_sock_ops_get_flow_id(ctx); | ||
|
|
||
| // Copy IPv6 addresses. | ||
| __builtin_memcpy(&audit_entry.tuple.local_ip.ipv6, &ctx->local_ip6, sizeof(audit_entry.tuple.local_ip.ipv6)); | ||
| __builtin_memcpy(&audit_entry.tuple.remote_ip.ipv6, &ctx->remote_ip6, sizeof(audit_entry.tuple.remote_ip.ipv6)); | ||
|
|
||
| audit_entry.tuple.local_port = ctx->local_port; | ||
| audit_entry.tuple.remote_port = ctx->remote_port; | ||
| audit_entry.tuple.protocol = ctx->protocol; | ||
| audit_entry.tuple.interface_luid = ctx->interface_luid; | ||
| audit_entry.process_id = bpf_get_current_pid_tgid(); | ||
| // Ignore the thread Id. | ||
| audit_entry.process_id >>= 32; | ||
| audit_entry.outbound = outbound; | ||
| audit_entry.connected = connected; | ||
| audit_entry.operation = ctx->op; | ||
| audit_entry.flow_id = flow_id; | ||
|
|
||
| // Store the flow ID in our map for later verification | ||
| bpf_map_update_elem(&flow_id_map, &audit_entry.tuple, &flow_id, BPF_ANY); | ||
|
|
||
| return update_flow_id_audit_map(&audit_entry); | ||
| } | ||
|
|
||
| SEC("sockops") | ||
| int | ||
| flow_id_monitor(bpf_sock_ops_t* ctx) | ||
| { | ||
| switch (ctx->op) { | ||
| case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: | ||
| if (ctx->family == AF_INET) { | ||
| return handle_v4_flow_id(ctx, false, true); | ||
| } else if (ctx->family == AF_INET6) { | ||
| return handle_v6_flow_id(ctx, false, true); | ||
| } | ||
| break; | ||
| case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: | ||
| if (ctx->family == AF_INET) { | ||
| return handle_v4_flow_id(ctx, true, true); | ||
| } else if (ctx->family == AF_INET6) { | ||
| return handle_v6_flow_id(ctx, true, true); | ||
| } | ||
| break; | ||
| case BPF_SOCK_OPS_CONNECTION_DELETED_CB: | ||
| if (ctx->family == AF_INET) { | ||
| return handle_v4_flow_id(ctx, false, false); | ||
| } else if (ctx->family == AF_INET6) { | ||
| return handle_v6_flow_id(ctx, false, false); | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| char _license[] SEC("license") = "MIT"; |
Uh oh!
There was an error while loading. Please reload this page.