diff --git a/simplewall.vcxproj b/simplewall.vcxproj index bb8abb6d..544e514b 100644 --- a/simplewall.vcxproj +++ b/simplewall.vcxproj @@ -393,6 +393,7 @@ + @@ -422,6 +423,7 @@ + diff --git a/simplewall.vcxproj.filters b/simplewall.vcxproj.filters index 332a5192..38893209 100644 --- a/simplewall.vcxproj.filters +++ b/simplewall.vcxproj.filters @@ -75,6 +75,9 @@ Source Files + + Source Files + @@ -158,6 +161,9 @@ Header Files + + Header Files + diff --git a/src/controls.c b/src/controls.c index cbcc3486..7f683b19 100644 --- a/src/controls.c +++ b/src/controls.c @@ -15,6 +15,7 @@ VOID _app_getapptooltipstring ( PR_STRING path = NULL; PITEM_APP ptr_app; PR_STRING string, value; + LONG traffic_error; ptr_app = _app_getappitem (app_hash); @@ -53,6 +54,19 @@ VOID _app_getapptooltipstring ( _r_obj_appendstringbuilder (buffer, SZ_CRLF); } + // UDP accounting status + if (ptr_network && ptr_network->protocol == IPPROTO_UDP && (ptr_network->is_stats_initialized || _InterlockedCompareExchange (&ptr_network->traffic_error, 0, 0))) + { + traffic_error = _InterlockedCompareExchange (&ptr_network->traffic_error, 0, 0); + + _r_obj_appendstringbuilder (buffer, _r_locale_getstring (traffic_error == ERROR_NOT_READY ? IDS_UDP_PENDING : (traffic_error ? IDS_UDP_UNAVAILABLE : IDS_UDP_ACCOUNTING))); + + if (traffic_error && traffic_error != ERROR_NOT_READY) + _r_obj_appendstringbuilderformat (buffer, L" (%lu)", (ULONG)traffic_error); + + _r_obj_appendstringbuilder (buffer, SZ_CRLF); + } + // file information _r_obj_initializestringbuilder (&sb, 0); @@ -552,7 +566,7 @@ LONG _app_getstateicon ( default: { - return IDI_INACTIVE; + return IDI_INACTIVE; // never match! } } } @@ -658,25 +672,14 @@ VOID _app_settrayicon ( _In_ ENUM_INSTALL_TYPE install_type ) { - HICON current_handle, new_handle; - LONG icon_id, icon_size; + HICON hicon; + LONG icon_size; icon_size = _r_dc_getsystemmetrics (SM_CXSMICON, _r_dc_gettaskbardpi ()); - icon_id = _app_getstateicon (install_type); - current_handle = (HICON)_InterlockedCompareExchangePointer ((volatile PVOID_PTR)&config.htray_icon, NULL, config.htray_icon); + hicon = _r_sys_loadsharedicon (_r_sys_getimagebase (), MAKEINTRESOURCE (_app_getstateicon (install_type)), icon_size); - if (current_handle) - DestroyIcon (current_handle); - - _r_sys_loadicon (&new_handle, _r_sys_getimagebase (), MAKEINTRESOURCE (icon_id), icon_size); - - _r_tray_setinfo (hwnd, &GUID_TrayIcon, new_handle, _r_app_getname ()); - - current_handle = (HICON)_InterlockedCompareExchangePointer ((volatile PVOID_PTR)&config.htray_icon, new_handle, NULL); - - if (current_handle) - DestroyIcon (current_handle); + _r_tray_setinfo (hwnd, &GUID_TrayIcon, hicon, _r_app_getname ()); } VOID _app_imagelist_init ( diff --git a/src/global.h b/src/global.h index fe776b2e..0a0ba3b3 100644 --- a/src/global.h +++ b/src/global.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "app.h" #include "rapp.h" @@ -114,5 +116,6 @@ DECLSPEC_SELECTANY const LONG64 timer_array[] = #include "search.h" #include "security.h" #include "timer.h" +#include "udpstats.h" #include "uwp.h" #include "wfp.h" diff --git a/src/main.c b/src/main.c index ca9938f4..3181166e 100644 --- a/src/main.c +++ b/src/main.c @@ -266,6 +266,24 @@ VOID _app_config_apply ( break; } + case IDM_CONNECTIONS_ENABLE: + { + new_val = !_r_config_getboolean (L"IsNetworkMonitorEnabled", TRUE, NULL); + break; + } + + case IDM_CONNECTIONS_SHOWAITCONNECTIONS: + { + new_val = !_r_config_getboolean (L"IsNetworkShowWaitConnections", TRUE, NULL); + break; + } + + case IDM_CONNECTIONS_MEASUREUDPTRAFFIC: + { + new_val = !_r_config_getboolean (L"IsUdpTrafficEnabled", FALSE, NULL); + break; + } + default: { return; @@ -411,6 +429,33 @@ VOID _app_config_apply ( break; } + case IDM_CONNECTIONS_ENABLE: + { + _r_config_setboolean (L"IsNetworkMonitorEnabled", new_val, NULL); + + _r_menu_checkitem (hmenu, IDM_CONNECTIONS_ENABLE, 0, MF_BYCOMMAND, new_val); + + break; + } + + case IDM_CONNECTIONS_SHOWAITCONNECTIONS: + { + _r_config_setboolean (L"IsNetworkShowWaitConnections", new_val, NULL); + + _r_menu_checkitem (hmenu, IDM_CONNECTIONS_SHOWAITCONNECTIONS, 0, MF_BYCOMMAND, new_val); + + break; + } + + case IDM_CONNECTIONS_MEASUREUDPTRAFFIC: + { + _r_config_setboolean (L"IsUdpTrafficEnabled", new_val, NULL); + + _r_menu_checkitem (hmenu, IDM_CONNECTIONS_MEASUREUDPTRAFFIC, 0, MF_BYCOMMAND, new_val); + + break; + } + case IDC_USESTEALTHMODE_CHK: { _r_config_setboolean (L"UseStealthMode", new_val, NULL); @@ -527,6 +572,7 @@ VOID _app_config_apply ( case IDM_PROFILETYPE_PLAIN: case IDM_PROFILETYPE_COMPRESSED: case IDM_PROFILETYPE_ENCRYPTED: + case IDM_CONNECTIONS_MEASUREUDPTRAFFIC: case IDC_USENETWORKRESOLUTION_CHK: case IDM_USENETWORKRESOLUTION_CHK: case IDC_USECERTIFICATES_CHK: @@ -2109,6 +2155,7 @@ INT_PTR CALLBACK DlgProc ( case WM_DESTROY: { + _app_network_stop (); _app_loginit (FALSE); if (_r_queuedlock_islocked (&lock_apply)) @@ -3066,6 +3113,9 @@ INT_PTR CALLBACK DlgProc ( case IDM_PROFILETYPE_PLAIN: case IDM_PROFILETYPE_COMPRESSED: case IDM_PROFILETYPE_ENCRYPTED: + case IDM_CONNECTIONS_ENABLE: + case IDM_CONNECTIONS_SHOWAITCONNECTIONS: + case IDM_CONNECTIONS_MEASUREUDPTRAFFIC: case IDM_USENETWORKRESOLUTION_CHK: case IDM_USECERTIFICATES_CHK: case IDM_KEEPUNUSED_CHK: diff --git a/src/main.h b/src/main.h index 85b0e3ce..c2d5a93d 100644 --- a/src/main.h +++ b/src/main.h @@ -209,7 +209,6 @@ typedef struct _STATIC_DATA PSID builtin_admins_sid; volatile HWND hnotification; - volatile HICON htray_icon; volatile HANDLE hnetevent; volatile HANDLE hlogfile; volatile LONG log_id; @@ -269,7 +268,6 @@ typedef struct _ITEM_LOG volatile PR_STRING local_host_str; LONG64 timestamp; - UINT64 filter_id; ULONG app_hash; @@ -429,12 +427,11 @@ typedef struct _ITEM_NETWORK UINT16 remote_port; UINT16 local_port; - UINT8 protocol; - BOOLEAN is_connection; - BOOLEAN is_stats_enabled; BOOLEAN is_stats_initialized; + BOOLEAN is_stats_enabled; + BOOLEAN is_connection; volatile LONG64 download_speed; volatile LONG64 upload_speed; @@ -444,6 +441,9 @@ typedef struct _ITEM_NETWORK ULONG64 last_bytes_in; ULONG64 last_bytes_out; ULONG64 last_stats_tick; + ULONG64 udp_created; + + volatile LONG traffic_error; } ITEM_NETWORK, *PITEM_NETWORK; typedef struct _ITEM_STATUS @@ -550,3 +550,70 @@ typedef struct _ITEM_TAB_CONTEXT INT listview_id; INT locale_id; } ITEM_TAB_CONTEXT, *PITEM_TAB_CONTEXT; + +typedef struct _UDP_ENDPOINT +{ + BYTE address[0x10]; + ULONG64 created; // UDP owner table bind timestamp, in FILETIME units + ULONG scope_id; + ULONG pid; + ADDRESS_FAMILY af; + USHORT port; // host byte order +} UDP_ENDPOINT, *PUDP_ENDPOINT; + +typedef struct UDP_ENTRY +{ + struct UDP_ENTRY *next; + UDP_ENDPOINT endpoint; + ULONG64 since; + ULONG64 received; + ULONG64 sent; + ULONG error_code; + ULONG epoch; + BOOLEAN is_observed; +} UDP_ENTRY, *PUDP_ENTRY; + +typedef struct UDP_AFD_SOCKET +{ + struct UDP_AFD_SOCKET *next; + UDP_ENDPOINT endpoint; + ULONG64 handle; + BOOLEAN is_bound; + BOOLEAN is_unsupported; +} UDP_AFD_SOCKET, *PUDP_AFD_SOCKET; + +typedef struct _UDP_TRACE_PROPERTIES +{ + EVENT_TRACE_PROPERTIES properties; + WCHAR name[0x40]; +} UDP_TRACE_PROPERTIES, *PUDP_TRACE_PROPERTIES; + +#define UDP_SESSION_SEMAPHORE L"Global\\simplewall-UDP-owner" +#define UDP_SESSION_NAME L"simplewall-UDP" +#define UDP_MAX_ENDPOINTS 0x2000 +#define UDP_BUCKETS 0x100 + +typedef struct _SW_UDP_STATS +{ + RTL_SRWLOCK control_lock; + RTL_SRWLOCK lock; + UDP_ENTRY *buckets[UDP_BUCKETS]; + UDP_AFD_SOCKET *sockets[UDP_BUCKETS]; // owned by the ETW consumer thread + HANDLE hthread; + HANDLE hguard; + TRACEHANDLE hsession; + TRACEHANDLE hconsumer; + ULONG socket_count; + ULONG count; + ULONG epoch; + volatile LONG error_code; + volatile LONG stopping; + BOOLEAN is_owns_guard; +} SW_UDP_STATS, *PSW_UDP_STATS; + +typedef struct _UDP_SNAPSHOT +{ + ULONG64 received; + ULONG64 sent; + ULONG error_code; // nonzero means totals must not be presented as complete +} UDP_SNAPSHOT, *PUDP_SNAPSHOT; diff --git a/src/messages.c b/src/messages.c index 74236044..6f030a32 100644 --- a/src/messages.c +++ b/src/messages.c @@ -78,6 +78,10 @@ VOID _app_message_initialize ( _r_menu_checkitem (hmenu, IDM_PROFILETYPE_PLAIN, IDM_PROFILETYPE_ENCRYPTED, MF_BYCOMMAND, IDM_PROFILETYPE_PLAIN + _r_calc_clamp (_r_config_getlong (L"ProfileType", 0, NULL), 0, 2)); + _r_menu_checkitem (hmenu, IDM_CONNECTIONS_ENABLE, 0, MF_BYCOMMAND, _r_config_getboolean (L"IsNetworkMonitorEnabled", TRUE, NULL)); + _r_menu_checkitem (hmenu, IDM_CONNECTIONS_SHOWAITCONNECTIONS, 0, MF_BYCOMMAND, _r_config_getboolean (L"IsNetworkShowWaitConnections", TRUE, NULL)); + _r_menu_checkitem (hmenu, IDM_CONNECTIONS_MEASUREUDPTRAFFIC, 0, MF_BYCOMMAND, _r_config_getboolean (L"IsUdpTrafficEnabled", FALSE, NULL)); + is_enabled = _r_config_getboolean (L"IsHashesEnabled", FALSE, NULL); _r_menu_checkitem (hmenu, IDM_USECERTIFICATES_CHK, 0, MF_BYCOMMAND, _r_config_getboolean (L"IsCertificatesEnabled", TRUE, NULL)); @@ -187,6 +191,10 @@ VOID _app_message_localize ( _r_menu_setitemtext (hmenu, IDM_PROFILETYPE_COMPRESSED, FALSE, _r_locale_getstring (IDS_PROFILE_TYPE_COMPRESSED)); _r_menu_setitemtext (hmenu, IDM_PROFILETYPE_ENCRYPTED, FALSE, _r_locale_getstring (IDS_PROFILE_TYPE_ENCRYPTED)); + _r_menu_setitemtext (hmenu, IDM_CONNECTIONS_ENABLE, FALSE, _r_locale_getstring (IDS_CONNECTIONS_ENABLE)); + _r_menu_setitemtext (hmenu, IDM_CONNECTIONS_SHOWAITCONNECTIONS, FALSE, _r_locale_getstring (IDS_CONNECTIONS_SHOWAITCONNECTIONS)); + _r_menu_setitemtext (hmenu, IDM_CONNECTIONS_MEASUREUDPTRAFFIC, FALSE, _r_locale_getstring (IDS_CONNECTIONS_MEASUREUDPTRAFFIC)); + _r_menu_setitemtext (hmenu, IDM_USENETWORKRESOLUTION_CHK, FALSE, _r_locale_getstring (IDS_USENETWORKRESOLUTION_CHK)); _r_menu_setitemtext (hmenu, IDM_USECERTIFICATES_CHK, FALSE, _r_locale_getstring (IDS_USECERTIFICATES_CHK)); _r_menu_setitemtext (hmenu, IDM_KEEPUNUSED_CHK, FALSE, _r_locale_getstring (IDS_KEEPUNUSED_CHK)); @@ -199,6 +207,7 @@ VOID _app_message_localize ( { _r_menu_setitemtext (hsubmenu, 5, TRUE, _r_locale_getstring (IDS_TRAY_RULES)); _r_menu_setitemtext (hsubmenu, 6, TRUE, _r_locale_getstring (IDS_PROFILE_TYPE)); + _r_menu_setitemtext (hsubmenu, 7, TRUE, _r_locale_getstring (IDS_TAB_NETWORK)); } recommended_string = _r_locale_getstring (IDS_RECOMMENDED); @@ -1405,6 +1414,12 @@ VOID _app_displayinfonetwork_callback ( case 10: case 11: { + if (ptr_network->protocol == IPPROTO_UDP && _InterlockedCompareExchange (&ptr_network->traffic_error, 0, 0)) + { + _r_str_copy (lpnmlv->item.pszText, lpnmlv->item.cchTextMax, L"\x2014"); + break; + } + if (!ptr_network->is_stats_initialized) break; diff --git a/src/network.c b/src/network.c index 78cb783c..d21b005e 100644 --- a/src/network.c +++ b/src/network.c @@ -113,7 +113,54 @@ static VOID _app_network_update_tcp6_stats ( _app_network_update_stats_values (ptr_network, data_rod.DataBytesIn, data_rod.DataBytesOut); } -_Ret_maybenull_ +static VOID _app_network_update_udp_stats ( + _Inout_ PITEM_NETWORK ptr_network, + _In_ PITEM_NETWORK_CONTEXT context, + _In_ ULONG pid, + _In_ ULONG64 created, + _In_ ULONG scope_id +) +{ + UDP_ENDPOINT endpoint = {0}; + UDP_SNAPSHOT snapshot; + + if (!context->udp_stats) + return; + + endpoint.pid = pid; + endpoint.af = ptr_network->af; + endpoint.port = ptr_network->local_port; + endpoint.created = created; + endpoint.scope_id = scope_id; + + RtlCopyMemory (endpoint.address, &ptr_network->local_addr, endpoint.af == AF_INET ? 4 : 16); // set correct sizes + + udp_stats_read (context->udp_stats, &endpoint, &snapshot); + + _InterlockedExchange (&ptr_network->traffic_error, snapshot.error_code); + + if (snapshot.error_code) + { + ptr_network->is_stats_initialized = FALSE; + + return; + } + + if (ptr_network->udp_created != created) + { + ptr_network->udp_created = created; + ptr_network->is_stats_initialized = FALSE; + + _InterlockedExchange64 (&ptr_network->download_speed, 0); + _InterlockedExchange64 (&ptr_network->upload_speed, 0); + } + + _app_network_update_stats_values (ptr_network, snapshot.received, snapshot.sent); + + _InterlockedExchange64 (&ptr_network->download_total, snapshot.received); + _InterlockedExchange64 (&ptr_network->upload_total, snapshot.sent); +} + PITEM_NETWORK_CONTEXT _app_network_getcontext () { static R_INITONCE init_once = PR_INITONCE_INIT; @@ -132,23 +179,41 @@ PITEM_NETWORK_CONTEXT _app_network_getcontext () return network_context; } +VOID _app_network_stop () +{ + // the original network worker lives until process exit. keep the stopped context valid for that worker; stop/join only our independent ETW consumer. + udp_stats_stop (_app_network_getcontext ()->udp_stats); +} + VOID _app_network_initialize ( _In_ HWND hwnd ) { PITEM_NETWORK_CONTEXT network_context; R_ENVIRONMENT environment; + ULONG status; if (!_r_config_getboolean (L"IsNetworkMonitorEnabled", TRUE, NULL)) return; network_context = _app_network_getcontext (); - if (!network_context) + if (!network_context) // USELESS! return; network_context->hwnd = hwnd; + // opt-in: sockets created before capture cannot be mapped by AFD events + if (_r_config_getboolean (L"IsUdpTrafficEnabled", FALSE, NULL)) + { + network_context->udp_stats = udp_stats_create (); + + status = udp_stats_start (network_context->udp_stats); + + if (status != STATUS_SUCCESS && status != STATUS_ALREADY_INITIALIZED) + _r_log (LOG_LEVEL_WARNING, NULL, L"udp_stats_start", NULL, status); + } + _r_queuedlock_acquireexclusive (&network_context->lock_network); _r_obj_clearhashtable (network_context->network_ptr); _r_queuedlock_releaseexclusive (&network_context->lock_network); @@ -176,6 +241,16 @@ VOID _app_network_uninitialize ( _r_queuedlock_releaseexclusive (&context->lock_checker); } +FORCEINLINE BOOLEAN _app_network_iswaitingconnection ( + _In_ ULONG pid +) +{ + if (pid == PROC_WAITING_PID && !_r_config_getboolean (L"IsNetworkShowWaitConnections", TRUE, NULL)) + return TRUE; + + return FALSE; +} + VOID _app_network_generatetable ( _Inout_ PITEM_NETWORK_CONTEXT network_context ) @@ -184,12 +259,16 @@ VOID _app_network_generatetable ( PMIB_UDP6TABLE_OWNER_MODULE udp6_table; PMIB_TCPTABLE_OWNER_MODULE tcp4_table; PMIB_UDPTABLE_OWNER_MODULE udp4_table; - PITEM_NETWORK ptr_network; IN_ADDR local_addr, remote_addr; + PITEM_NETWORK ptr_network; PVOID buffer; ULONG allocated_size, network_hash, required_size = 0; + BOOL udp4_complete = FALSE, udp6_complete = FALSE; ULONG status; + udp_stats_poll (network_context->udp_stats); + udp_stats_begin_refresh (network_context->udp_stats); + _r_queuedlock_acquireexclusive (&network_context->lock_checker); _r_obj_clearhashtable (network_context->checker_ptr); _r_queuedlock_releaseexclusive (&network_context->lock_checker); @@ -209,6 +288,9 @@ VOID _app_network_generatetable ( { for (ULONG i = 0; i < tcp4_table->dwNumEntries; i++) { + if (_app_network_iswaitingconnection (tcp4_table->table[i].dwOwningPid)) + continue; + RtlZeroMemory (&remote_addr, sizeof (IN_ADDR)); RtlZeroMemory (&local_addr, sizeof (IN_ADDR)); @@ -246,6 +328,7 @@ VOID _app_network_generatetable ( if (!_app_network_getpath (ptr_network, tcp4_table->table[i].dwOwningPid, tcp4_table->table[i].OwningModuleInfo)) { _r_obj_dereference (ptr_network); + continue; } @@ -299,6 +382,9 @@ VOID _app_network_generatetable ( { for (ULONG i = 0; i < tcp6_table->dwNumEntries; i++) { + if (_app_network_iswaitingconnection (tcp6_table->table[i].dwOwningPid)) + continue; + network_hash = _app_network_gethash ( AF_INET6, tcp6_table->table[i].dwOwningPid, @@ -330,6 +416,7 @@ VOID _app_network_generatetable ( if (!_app_network_getpath (ptr_network, tcp6_table->table[i].dwOwningPid, tcp6_table->table[i].OwningModuleInfo)) { _r_obj_dereference (ptr_network); + continue; } @@ -381,6 +468,8 @@ VOID _app_network_generatetable ( if (status == NO_ERROR) { + udp4_complete = TRUE; + for (ULONG i = 0; i < udp4_table->dwNumEntries; i++) { RtlZeroMemory (&local_addr, sizeof (IN_ADDR)); @@ -389,20 +478,30 @@ VOID _app_network_generatetable ( network_hash = _app_network_gethash (AF_INET, udp4_table->table[i].dwOwningPid, NULL, 0, &local_addr, udp4_table->table[i].dwLocalPort, IPPROTO_UDP, 0); - if (_app_network_isitemfound (network_hash)) + ptr_network = _app_network_getitem (network_hash); + + if (ptr_network) { + _app_network_update_udp_stats (ptr_network, network_context, udp4_table->table[i].dwOwningPid, udp4_table->table[i].liCreateTimestamp.QuadPart, 0); + _r_queuedlock_acquireexclusive (&network_context->lock_checker); _r_obj_addhashtablepointer (network_context->checker_ptr, network_hash, NULL); _r_queuedlock_releaseexclusive (&network_context->lock_checker); + _r_obj_dereference (ptr_network); + continue; } + if (_app_network_iswaitingconnection (udp4_table->table[i].dwOwningPid)) + continue; + ptr_network = (PITEM_NETWORK)_r_obj_allocate (sizeof (ITEM_NETWORK), &_app_dereferencenetwork); if (!_app_network_getpath (ptr_network, udp4_table->table[i].dwOwningPid, udp4_table->table[i].OwningModuleInfo)) { _r_obj_dereference (ptr_network); + continue; } @@ -413,6 +512,8 @@ VOID _app_network_generatetable ( ptr_network->local_addr.S_un.S_addr = udp4_table->table[i].dwLocalAddr; ptr_network->local_port = _r_byteswap_ushort ((USHORT)udp4_table->table[i].dwLocalPort); + _app_network_update_udp_stats (ptr_network, network_context, udp4_table->table[i].dwOwningPid, udp4_table->table[i].liCreateTimestamp.QuadPart, 0); + if (_app_network_isvalidconnection (ptr_network->af, &ptr_network->local_addr)) ptr_network->is_connection = TRUE; @@ -444,24 +545,36 @@ VOID _app_network_generatetable ( if (status == NO_ERROR) { + udp6_complete = TRUE; + for (ULONG i = 0; i < udp6_table->dwNumEntries; i++) { network_hash = _app_network_gethash (AF_INET6, udp6_table->table[i].dwOwningPid, NULL, 0, udp6_table->table[i].ucLocalAddr, udp6_table->table[i].dwLocalPort, IPPROTO_UDP, 0); - if (_app_network_isitemfound (network_hash)) + ptr_network = _app_network_getitem (network_hash); + + if (ptr_network) { + _app_network_update_udp_stats (ptr_network, network_context, udp6_table->table[i].dwOwningPid, udp6_table->table[i].liCreateTimestamp.QuadPart, udp6_table->table[i].dwLocalScopeId); + _r_queuedlock_acquireexclusive (&network_context->lock_checker); _r_obj_addhashtablepointer (network_context->checker_ptr, network_hash, NULL); _r_queuedlock_releaseexclusive (&network_context->lock_checker); + _r_obj_dereference (ptr_network); + continue; } + if (_app_network_iswaitingconnection (udp6_table->table[i].dwOwningPid)) + continue; + ptr_network = (PITEM_NETWORK)_r_obj_allocate (sizeof (ITEM_NETWORK), &_app_dereferencenetwork); if (!_app_network_getpath (ptr_network, udp6_table->table[i].dwOwningPid, udp6_table->table[i].OwningModuleInfo)) { _r_obj_dereference (ptr_network); + continue; } @@ -472,6 +585,8 @@ VOID _app_network_generatetable ( RtlCopyMemory (ptr_network->local_addr6.u.Byte, udp6_table->table[i].ucLocalAddr, FWP_V6_ADDR_SIZE); ptr_network->local_port = _r_byteswap_ushort ((USHORT)udp6_table->table[i].dwLocalPort); + _app_network_update_udp_stats (ptr_network, network_context, udp6_table->table[i].dwOwningPid, udp6_table->table[i].liCreateTimestamp.QuadPart, udp6_table->table[i].dwLocalScopeId); + if (_app_network_isvalidconnection (ptr_network->af, &ptr_network->local_addr6)) ptr_network->is_connection = TRUE; @@ -486,6 +601,8 @@ VOID _app_network_generatetable ( } } + udp_stats_end_refresh (network_context->udp_stats, udp4_complete, udp6_complete); + if (buffer) _r_mem_free (buffer); } @@ -772,7 +889,7 @@ VOID _app_network_printlistviewtable ( _app_listview_addnetworkitem (network_context->hwnd, network_hash); - if (ptr_network->path && ptr_network->app_hash) + if (ptr_network->app_hash && ptr_network->path) _app_getfileinformation (ptr_network->path, ptr_network->app_hash, ptr_network->type, IDC_NETWORK); // resolve network address diff --git a/src/network.h b/src/network.h index 84f09a72..0fbf807c 100644 --- a/src/network.h +++ b/src/network.h @@ -3,17 +3,25 @@ #pragma once +#include "udpstats.h" + typedef struct _ITEM_NETWORK_CONTEXT { - HWND hwnd; - R_QUEUED_LOCK lock_network; R_QUEUED_LOCK lock_checker; PR_HASHTABLE network_ptr; PR_HASHTABLE checker_ptr; + + SW_UDP_STATS *udp_stats; + + HWND hwnd; } ITEM_NETWORK_CONTEXT, *PITEM_NETWORK_CONTEXT; +PITEM_NETWORK_CONTEXT _app_network_getcontext (); + +VOID _app_network_stop (); + VOID _app_network_initialize ( _In_ HWND hwnd ); diff --git a/src/profile.c b/src/profile.c index a4e01566..6cf10f52 100644 --- a/src/profile.c +++ b/src/profile.c @@ -797,20 +797,22 @@ VOID _app_freeapplication ( { PITEM_RULE ptr_rule; - _r_queuedlock_acquireshared (&lock_rules); - - for (ULONG_PTR i = 0; i < _r_obj_getlistsize (rules_list); i++) + if (hwnd) { - ptr_rule = (PITEM_RULE)_r_obj_getlistitem (rules_list, i); + _r_queuedlock_acquireshared (&lock_rules); - if (!ptr_rule || ptr_rule->type != DATA_RULE_USER) - continue; + for (ULONG_PTR i = 0; i < _r_obj_getlistsize (rules_list); i++) + { + ptr_rule = (PITEM_RULE)_r_obj_getlistitem (rules_list, i); + + if (!ptr_rule || ptr_rule->type != DATA_RULE_USER) + continue; - if (hwnd) _app_ruleremoveapp (hwnd, i, ptr_rule, app_hash); - } + } - _r_queuedlock_releaseshared (&lock_rules); + _r_queuedlock_releaseshared (&lock_rules); + } _r_obj_removehashtableitem (apps_table, app_hash); } @@ -982,7 +984,7 @@ VOID _app_ruleremoveapp ( if (!ptr_rule->apps || !_r_obj_removehashtableitem (ptr_rule->apps, app_hash)) return; - if (ptr_rule->is_enabled && _r_obj_isempty (ptr_rule->apps)) + if (ptr_rule->is_enabled && _r_obj_isempty2 (ptr_rule->apps)) { ptr_rule->is_enabled = FALSE; ptr_rule->is_haveerrors = FALSE; diff --git a/src/udpstats.c b/src/udpstats.c new file mode 100644 index 00000000..8d095779 --- /dev/null +++ b/src/udpstats.c @@ -0,0 +1,794 @@ +// simplewall +// Copyright (c) 2026 Henry++ +// Copyright (c) 2026 Tarkiin +// UDP events contain metadata only; no packet payload is captured or stored +#include "global.h" + +FORCEINLINE static ULONG udp_bucket ( + _In_ ULONG pid, + _In_ USHORT port +) +{ + return (pid ^ port) % UDP_BUCKETS; +} + +static VOID udp_initialize_properties ( + _Out_ PUDP_TRACE_PROPERTIES buffer +) +{ + RtlZeroMemory (buffer, sizeof (UDP_TRACE_PROPERTIES)); + + buffer->properties.Wnode.BufferSize = sizeof (UDP_TRACE_PROPERTIES); + buffer->properties.Wnode.Guid = udp_session; + buffer->properties.Wnode.ClientContext = 1; // QPC; ProcessTrace converts to FILETIME + buffer->properties.Wnode.Flags = WNODE_FLAG_TRACED_GUID; + buffer->properties.BufferSize = 64; + //buffer->properties.MinimumBuffers = 0; // already 0 + buffer->properties.MaximumBuffers = 128; + buffer->properties.FlushTimer = 1; + buffer->properties.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + buffer->properties.LoggerNameOffset = FIELD_OFFSET (UDP_TRACE_PROPERTIES, name); +} + +// select a bound socket, not a remote flow. UDP listeners may use wildcard addresses and IPv6 sockets may receive IPv4. never credit two rows for one event. +static INT udp_match ( + _In_ const PUDP_ENDPOINT endpoint, + _In_ ADDRESS_FAMILY af, + _In_ const PBYTE address +) +{ + ULONG_PTR length; + const BYTE mapped[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF}; + const BYTE zero[16] = {0}; + + if (endpoint->af == af) + { + length = (af == AF_INET) ? 4 : 16; // set correct sizes + + if (RtlEqualMemory (endpoint->address, address, length)) + return 3; + + if (RtlEqualMemory (endpoint->address, zero, length)) + return 2; + } + else if (af == AF_INET && endpoint->af == AF_INET6) + { + if (RtlEqualMemory (endpoint->address, mapped, 12) && !RtlEqualMemory (endpoint->address + 12, address, 4)) // set correct sizes + return 2; + + if (RtlEqualMemory (endpoint->address, zero, 16)) // set correct sizes + return 1; + } + + return 0; +} + +static VOID udp_account ( + _In_ PSW_UDP_STATS stats, + _In_ ULONG pid, + _In_ ULONG size, + _In_ USHORT port, + _In_ ADDRESS_FAMILY af, + _In_ const PBYTE address, + _In_ ULONG scope_id, + _In_ BOOLEAN is_receive, + _In_ ULONG64 timestamp, + _In_ ULONG error_code +) +{ + UDP_ENTRY *best = NULL, *entry; + INT candidate, score = 0; + BOOLEAN is_ambiguous = FALSE; + + AcquireSRWLockExclusive (&stats->lock); + + for (entry = stats->buckets[udp_bucket (pid, port)]; entry; entry = entry->next) + { + if (entry->endpoint.pid != pid || entry->endpoint.port != port) + continue; + + if (af == AF_INET6 && scope_id != MAXDWORD && entry->endpoint.scope_id != scope_id) + continue; + + candidate = udp_match (&entry->endpoint, af, address); + + if (candidate > score) + { + best = entry; + score = candidate; + is_ambiguous = FALSE; + } + else if (candidate && candidate == score) + { + is_ambiguous = TRUE; + } + } + + if (best && !is_ambiguous && timestamp >= best->since) + { + if (error_code != ERROR_SUCCESS) + best->error_code = error_code; + + best->is_observed = TRUE; + + if (is_receive) + { + best->received += size; + } + else + { + best->sent += size; + } + } + else if (is_ambiguous) + { + // never silently attribute an event to one of several equally good rows + for (entry = stats->buckets[udp_bucket (pid, port)]; entry; entry = entry->next) + { + if (entry->endpoint.pid == pid && entry->endpoint.port == port && udp_match (&entry->endpoint, af, address) == score) + entry->error_code = ERROR_INVALID_DATA; + } + } + + ReleaseSRWLockExclusive (&stats->lock); +} + +static ULONG udp_u32 ( + _In_ const PBYTE data +) +{ + ULONG value; + + RtlCopyMemory (&value, data, sizeof (ULONG)); + + return value; +} + +static VOID udp_afd_event ( + _In_ PSW_UDP_STATS stats, + _In_ PEVENT_RECORD event +) +{ + UDP_AFD_SOCKET **link, *socket; + ULONG64 handle = 0; + ULONG pointer_size = (event->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER) ? sizeof (ULONG) : sizeof (ULONG64), prefix = 8 + 2 * pointer_size, length, size_offset; + USHORT af, port, id = event->EventHeader.EventDescriptor.Id; + const PBYTE data = (const PBYTE)(event->UserData); + BOOLEAN is_receive = id == 1004 || id == 1006 || id == 1009 || id == 1012 || id == 1015; + BOOLEAN is_transfer = is_receive || id == 1003 || id == 1005 || id == 1007 || id == 1011 || id == 1013; + + // RIO uses a different completion path. mark just that socket unavailable. + if (id == 4006) + { + if (event->EventHeader.EventDescriptor.Version != 0 || !data || event->UserDataLength < pointer_size) + goto unsupported; + + RtlCopyMemory (&handle, data, pointer_size); + + for (socket = stats->sockets[(handle >> 4) % UDP_BUCKETS]; socket; socket = socket->next) + { + if (socket->handle == handle) + { + socket->is_unsupported = TRUE; + + if (socket->is_bound) + { + udp_account ( + stats, + socket->endpoint.pid, + 0, + socket->endpoint.port, + socket->endpoint.af, + socket->endpoint.address, + socket->endpoint.scope_id, + FALSE, + event->EventHeader.TimeStamp.QuadPart, + ERROR_NOT_SUPPORTED + ); + } + + break; + } + } + + return; + } + + if (id != 1000 && id != 1001 && id != 1002 && id != 1030 && !is_transfer) + return; + + if (event->EventHeader.EventDescriptor.Version != 0 || !data || event->UserDataLength < prefix) + goto unsupported; + + RtlCopyMemory (&handle, data + 8 + pointer_size, pointer_size); + + link = &stats->sockets[(handle >> 4) % UDP_BUCKETS]; + + while (*link && (*link)->handle != handle) + { + link = &(*link)->next; + } + + socket = *link; + + if ((id == 1001 || id == 1002) && udp_u32 (data) == 0) + { + if (socket) + { + *link = socket->next; + + stats->socket_count -= 1; + + _r_mem_free (socket); + } + + return; + } + + if (id == 1000 && udp_u32 (data) == 0) + { + if (event->UserDataLength < prefix + 12 + pointer_size + 4) + goto unsupported; + + // kernel endpoint addresses are reused, including by TCP sockets + if (socket) + { + *link = socket->next; + + stats->socket_count -= 1; + + _r_mem_free (socket); + + socket = NULL; + } + + if (udp_u32 (data + prefix + 4) != SOCK_DGRAM || udp_u32 (data + prefix + 8) != IPPROTO_UDP) + return; + + if (!socket) + { + if (stats->socket_count >= UDP_MAX_ENDPOINTS) + { + _InterlockedExchange (&stats->error_code, ERROR_NOT_ENOUGH_MEMORY); + + return; + } + + socket = (UDP_AFD_SOCKET *)_r_mem_allocatesafe (sizeof (UDP_AFD_SOCKET)); + + if (!socket) + { + _InterlockedExchange (&stats->error_code, ERROR_NOT_ENOUGH_MEMORY); + + return; + } + + socket->next = *link; + + *link = socket; + + stats->socket_count += 1; + } + + socket->handle = handle; + socket->is_bound = FALSE; + + RtlZeroMemory (&socket->endpoint, sizeof (UDP_ENDPOINT)); + + socket->endpoint.pid = udp_u32 (data + prefix + 12); + + return; + } + + if (!socket || udp_u32 (data) != 1) + return; + + if (id == 1030) + { + if (event->UserDataLength < prefix + 8) + goto unsupported; + + if (udp_u32 (data + prefix) != ERROR_SUCCESS) + return; + + length = udp_u32 (data + prefix + 4); + + if (length > event->UserDataLength - prefix - 8 || length < 4) + goto unsupported; + + RtlCopyMemory (&af, data + prefix + 8, 2); + RtlCopyMemory (&port, data + prefix + 10, 2); + + if ((af != AF_INET && af != AF_INET6) || length < (af == AF_INET ? 16U : 28U)) + goto unsupported; + + socket->endpoint.af = af; + socket->endpoint.port = (USHORT)((port >> 8) | (port << 8)); + + RtlCopyMemory (socket->endpoint.address, data + prefix + (af == AF_INET ? 12 : 16), af == AF_INET ? 4 : 16); + + socket->endpoint.scope_id = (af == AF_INET6) ? udp_u32 (data + prefix + 32) : 0; + socket->is_bound = socket->endpoint.port != 0; + + if (socket->is_bound && socket->is_unsupported) + { + udp_account ( + stats, + socket->endpoint.pid, + 0, + socket->endpoint.port, + af, + socket->endpoint.address, + socket->endpoint.scope_id, + FALSE, + event->EventHeader.TimeStamp.QuadPart, + ERROR_NOT_SUPPORTED + ); + } + + return; + } + + if (is_transfer && socket->is_bound) + { + size_offset = prefix + 4 + pointer_size; + + if (event->UserDataLength < size_offset + 8) + goto unsupported; + + if (udp_u32 (data + size_offset + 4) != ERROR_SUCCESS) + return; + + udp_account ( + stats, + socket->endpoint.pid, + udp_u32 (data + size_offset), + socket->endpoint.port, + socket->endpoint.af, + socket->endpoint.address, + socket->endpoint.scope_id, + is_receive, + event->EventHeader.TimeStamp.QuadPart, + socket->is_unsupported ? ERROR_NOT_SUPPORTED : ERROR_SUCCESS + ); + } + + return; + +unsupported: + + _InterlockedExchange (&stats->error_code, ERROR_NOT_SUPPORTED); +} + +static VOID WINAPI udp_event ( + _In_ PEVENT_RECORD event +) +{ + PSW_UDP_STATS stats = event->UserContext; +#if defined(UDP_STATS_DIAGNOSTIC) + UDP_STATS_DIAGNOSTIC (event); +#endif // UDP_STATS_DIAGNOSTIC + + if (_InterlockedCompareExchange (&stats->stopping, 0, 0)) + return; + + if (IsEqualGUID (&event->EventHeader.ProviderId, &afd_provider)) + udp_afd_event (stats, event); +} + +static ULONG WINAPI udp_buffer ( + _In_ PEVENT_TRACE_LOGFILEW buffer +) +{ + PSW_UDP_STATS stats = (PSW_UDP_STATS)buffer->Context; + + if (buffer->EventsLost) + _InterlockedExchange (&stats->error_code, ERROR_DATA_NOT_ACCEPTED); + + return !_InterlockedCompareExchange (&stats->stopping, 0, 0); +} + +static VOID NTAPI udp_thread ( + _In_ PVOID context +) +{ + PSW_UDP_STATS stats = (PSW_UDP_STATS)context; + ULONG result; + + result = ProcessTrace (&stats->hconsumer, 1, NULL, NULL); + + if (!_InterlockedCompareExchange (&stats->stopping, 0, 0)) + _InterlockedExchange (&stats->error_code, result ? result : ERROR_OPERATION_ABORTED); +} + +_Ret_maybenull_ +PSW_UDP_STATS udp_stats_create () +{ + PSW_UDP_STATS stats; + + stats = (PSW_UDP_STATS)_r_mem_allocatesafe (sizeof (SW_UDP_STATS)); + + if (stats) + { + stats->hconsumer = INVALID_PROCESSTRACE_HANDLE; + stats->error_code = ERROR_NOT_READY; + } + + return stats; +} + +NTSTATUS udp_stats_start ( + _Inout_opt_ PSW_UDP_STATS stats +) +{ + EVENT_TRACE_LOGFILEW logfile = {0}; + UDP_TRACE_PROPERTIES properties; + OBJECT_ATTRIBUTES oa = {0}; + R_ENVIRONMENT environment; + UNICODE_STRING us; + WCHAR buffer[0x80]; + TRACEHANDLE hsession = 0; + NTSTATUS status; + + if (!stats) + return STATUS_MEMORY_NOT_ALLOCATED; + + if (stats->hguard || _InterlockedCompareExchange (&stats->stopping, 0, 0)) + return STATUS_ALREADY_INITIALIZED; + + // a global semaphore distinguishes an orphan left by a crash from an active simplewall instance. never stop another tracing application's session. + _r_str_printf (buffer, RTL_NUMBER_OF (buffer), L"\\Sessions\\%u\\BaseNamedObjects\\%s", NtCurrentPeb ()->SessionId, UDP_SESSION_SEMAPHORE); + + _r_obj_initializeunicodestring (&us, buffer); + + InitializeObjectAttributes (&oa, &us, OBJ_CASE_INSENSITIVE | OBJ_OPENIF, NULL, NULL); + + status = NtCreateSemaphore (&stats->hguard, SEMAPHORE_ALL_ACCESS, &oa, 1, 1); + + if (NT_SUCCESS (status) || status == STATUS_OBJECT_NAME_COLLISION) + { + status = _r_sys_waitforsingleobject (stats->hguard, 0); + + stats->is_owns_guard = (status == STATUS_WAIT_0); + + status = stats->is_owns_guard ? STATUS_SUCCESS : STATUS_ALREADY_INITIALIZED; + } + else + { + stats->hguard = NULL; + + goto fail; + } + + udp_initialize_properties (&properties); + + status = StartTraceW (&hsession, UDP_SESSION_NAME, &properties.properties); + + if (status == ERROR_ALREADY_EXISTS) + { + udp_initialize_properties (&properties); + + status = ControlTraceW (0, UDP_SESSION_NAME, &properties.properties, EVENT_TRACE_CONTROL_QUERY); + + if (status == ERROR_SUCCESS && IsEqualGUID (&properties.properties.Wnode.Guid, &udp_session)) + { + status = ControlTraceW (0, UDP_SESSION_NAME, &properties.properties, EVENT_TRACE_CONTROL_STOP); + + if (status == ERROR_SUCCESS) + { + udp_initialize_properties (&properties); + + status = StartTraceW (&hsession, UDP_SESSION_NAME, &properties.properties); + } + } + else if (status == ERROR_SUCCESS) + { + status = STATUS_ALREADY_INITIALIZED; + } + } + + if (status != STATUS_SUCCESS) + goto fail; + + stats->hsession = hsession; + + // DATAGRAM plus RIO lifecycle metadata; no verbose packet-buffer events + status = EnableTraceEx2 (hsession, &afd_provider, EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, 0x41, 0, 0, NULL); + + if (status != STATUS_SUCCESS) + { + status = _r_sys_doserrortontstatus (status); + + goto fail; + } + + logfile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; + logfile.LoggerName = UDP_SESSION_NAME; + logfile.EventRecordCallback = &udp_event; + logfile.BufferCallback = &udp_buffer; + logfile.Context = stats; + + stats->hconsumer = OpenTraceW (&logfile); + + if (stats->hconsumer == INVALID_PROCESSTRACE_HANDLE) + { + status = _r_sys_doserrortontstatus (NtLastError ()); + + goto fail; + } + + _InterlockedExchange (&stats->error_code, STATUS_SUCCESS); + + _r_sys_setenvironment (&environment, THREAD_PRIORITY_ABOVE_NORMAL, IoPriorityHigh, MEMORY_PRIORITY_NORMAL); + + status = _r_sys_createthread (&stats->hthread, NtCurrentProcess (), &udp_thread, stats, &environment, L"UdpMonitoring"); + + if (!NT_SUCCESS (status)) + goto fail; + + NtResumeThread (stats->hthread, NULL); + + return STATUS_SUCCESS; + +fail: + + udp_stats_stop (stats); + + _InterlockedExchange (&stats->error_code, status); + + return status; +} + +VOID udp_stats_stop ( + _In_opt_ PSW_UDP_STATS stats +) +{ + UDP_TRACE_PROPERTIES properties; + + if (!stats) + return; + + AcquireSRWLockExclusive (&stats->control_lock); + + _InterlockedExchange (&stats->error_code, ERROR_OPERATION_ABORTED); + _InterlockedExchange (&stats->stopping, TRUE); + + if (stats->hsession) + { + udp_initialize_properties (&properties); + + ControlTraceW (stats->hsession, NULL, &properties.properties, EVENT_TRACE_CONTROL_STOP); + + stats->hsession = 0; + } + + if (stats->hconsumer != INVALID_PROCESSTRACE_HANDLE) + CloseTrace (stats->hconsumer); + + if (stats->hthread) + { + _r_sys_waitforsingleobject (stats->hthread, INFINITE); + + NtClose (stats->hthread); + + stats->hthread = NULL; + } + + stats->hconsumer = INVALID_PROCESSTRACE_HANDLE; + + if (stats->hguard) + { + if (stats->is_owns_guard) + NtReleaseSemaphore (stats->hguard, 1, NULL); + + NtClose (stats->hguard); + stats->hguard = NULL; + + stats->is_owns_guard = FALSE; + } + + ReleaseSRWLockExclusive (&stats->control_lock); +} + +VOID udp_stats_destroy ( + _In_opt_ PSW_UDP_STATS stats +) +{ + UDP_AFD_SOCKET *next, *socket; + UDP_ENTRY *entry, *next_entry; + + if (!stats) + return; + + udp_stats_stop (stats); + + for (ULONG i = 0; i < UDP_BUCKETS; i++) + { + socket = stats->sockets[i]; + + while (socket) + { + next = socket->next; + + _r_mem_free (socket); + + socket = next; + } + + entry = stats->buckets[i]; + + while (entry) + { + next_entry = entry->next; + + _r_mem_free (entry); + + entry = next_entry; + } + } + + _r_mem_free (stats); +} + +ULONG udp_stats_poll ( + _In_opt_ PSW_UDP_STATS stats +) +{ + UDP_TRACE_PROPERTIES properties; + ULONG status; + + if (!stats) + return ERROR_NOT_READY; + + AcquireSRWLockExclusive (&stats->control_lock); + + status = _InterlockedCompareExchange (&stats->error_code, 0, 0); + + if (status == ERROR_SUCCESS && stats->hsession) + { + udp_initialize_properties (&properties); + + status = ControlTraceW (stats->hsession, NULL, &properties.properties, EVENT_TRACE_CONTROL_QUERY); + + if (status == ERROR_SUCCESS && (properties.properties.EventsLost || properties.properties.RealTimeBuffersLost || properties.properties.LogBuffersLost)) + status = ERROR_DATA_NOT_ACCEPTED; + + if (status != ERROR_SUCCESS) + _InterlockedExchange (&stats->error_code, status); + } + + ReleaseSRWLockExclusive (&stats->control_lock); + + return status; +} + +VOID udp_stats_begin_refresh ( + _Inout_opt_ PSW_UDP_STATS stats +) +{ + if (!stats) + return; + + AcquireSRWLockExclusive (&stats->lock); + stats->epoch += 1; + ReleaseSRWLockExclusive (&stats->lock); +} + +VOID udp_stats_read ( + _Inout_ PSW_UDP_STATS stats, + _In_ PUDP_ENDPOINT endpoint, + _Out_ PUDP_SNAPSHOT snapshot +) +{ + LARGE_INTEGER li; + UDP_ENTRY *entry; + ULONG bucket; + + RtlZeroMemory (snapshot, sizeof (UDP_SNAPSHOT)); + + snapshot->error_code = stats ? _InterlockedCompareExchange (&stats->error_code, 0, 0) : ERROR_NOT_READY; + + if (snapshot->error_code != ERROR_SUCCESS) + return; + + bucket = udp_bucket (endpoint->pid, endpoint->port); + + AcquireSRWLockExclusive (&stats->lock); + + for (entry = stats->buckets[bucket]; entry; entry = entry->next) + { + if (entry->endpoint.pid == endpoint->pid && entry->endpoint.port == endpoint->port && entry->endpoint.af == endpoint->af && + entry->endpoint.scope_id == endpoint->scope_id && RtlEqualMemory (entry->endpoint.address, endpoint->address, endpoint->af == AF_INET ? 4 : 16)) + { + break; + } + } + + if (!entry) + { + if (stats->count < UDP_MAX_ENDPOINTS) + entry = (PUDP_ENTRY)_r_mem_allocatesafe (sizeof (UDP_ENTRY)); + + if (!entry) + { + snapshot->error_code = ERROR_NOT_ENOUGH_MEMORY; + + ReleaseSRWLockExclusive (&stats->lock); + + return; + } + + entry->next = stats->buckets[bucket]; + stats->buckets[bucket] = entry; + stats->count += 1; + } + + if (!entry->since || entry->endpoint.created != endpoint->created) + { + entry->endpoint = *endpoint; + entry->since = _r_sys_gettimestamp (&li)->QuadPart; + + if (endpoint->created > entry->since) + entry->since = endpoint->created; + + entry->received = entry->sent = 0; + entry->error_code = ERROR_SUCCESS; + entry->is_observed = FALSE; + } + + snapshot->error_code = entry->error_code ? entry->error_code : (entry->is_observed ? ERROR_SUCCESS : ERROR_NOT_READY); + snapshot->received = entry->received; + snapshot->sent = entry->sent; + entry->epoch = stats->epoch; + + ReleaseSRWLockExclusive (&stats->lock); +} + +VOID udp_stats_end_refresh ( + _In_opt_ SW_UDP_STATS *stats, + _In_ BOOLEAN ipv4_complete, + _In_ BOOLEAN ipv6_complete +) +{ + UDP_ENTRY *entry, *next, *previous; + + if (!stats) + return; + + AcquireSRWLockExclusive (&stats->lock); + + for (ULONG i = 0; i < UDP_BUCKETS; i++) + { + entry = stats->buckets[i]; + previous = NULL; + + while (entry) + { + next = entry->next; + + if (entry->epoch != stats->epoch && (entry->endpoint.af == AF_INET ? ipv4_complete : ipv6_complete)) + { + if (previous) + { + previous->next = next; + } + else + { + stats->buckets[i] = next; + } + + _r_mem_free (entry); + + stats->count -= 1; + } + else + { + previous = entry; + } + + entry = next; + } + } + + ReleaseSRWLockExclusive (&stats->lock); +} diff --git a/src/udpstats.h b/src/udpstats.h new file mode 100644 index 00000000..f4424728 --- /dev/null +++ b/src/udpstats.h @@ -0,0 +1,45 @@ +// simplewall +// Copyright (c) 2026 Henry++ +// Copyright (c) 2026 Tarkiin +// UDP events contain metadata only; no packet payload is captured or stored +#pragma once + +#include "global.h" + +static const GUID afd_provider = {0xE53C6823, 0x7BB8, 0x44BB, {0x90, 0xDC, 0x3F, 0x86, 0x09, 0x0D, 0x48, 0xA6}}; +static const GUID udp_session = {0x4BFA58D6, 0x5D5D, 0x46F2, {0x93, 0x93, 0xC9, 0xE0, 0x5E, 0x27, 0xB4, 0x71}}; + +_Ret_maybenull_ +PSW_UDP_STATS udp_stats_create (); + +NTSTATUS udp_stats_start ( + _Inout_opt_ PSW_UDP_STATS stats +); + +VOID udp_stats_stop ( + _In_opt_ PSW_UDP_STATS stats +); + +VOID udp_stats_destroy ( + _In_opt_ PSW_UDP_STATS stats +); + +ULONG udp_stats_poll ( + _In_opt_ PSW_UDP_STATS stats +); + +VOID udp_stats_begin_refresh ( + _Inout_opt_ PSW_UDP_STATS stats +); + +VOID udp_stats_read ( + _Inout_ PSW_UDP_STATS stats, + _In_ PUDP_ENDPOINT endpoint, + _Out_ PUDP_SNAPSHOT snapshot +); + +VOID udp_stats_end_refresh ( + _In_opt_ SW_UDP_STATS *stats, + _In_ BOOLEAN ipv4_complete, + _In_ BOOLEAN ipv6_complete +);