55 * and AP interfaces to intercept packets for filtering and monitoring.
66 */
77
8+ #include <inttypes.h>
89#include <string.h>
910#include <time.h>
1011#include "esp_log.h"
1819#include "lwip/prot/ip4.h"
1920#include "lwip/inet_chksum.h"
2021#include "acl.h"
22+ #include "client_stats.h"
2123#include "pcap_capture.h"
2224#include "router_config.h"
2325#include "wifi_config.h"
@@ -43,6 +45,88 @@ static netif_input_fn original_ap_netif_input = NULL;
4345static netif_linkoutput_fn original_ap_netif_linkoutput = NULL ;
4446static struct netif * ap_netif = NULL ;
4547
48+ // Per-client traffic statistics for AP clients
49+ static client_stats_entry_t client_stats [CLIENT_STATS_MAX ];
50+
51+ static inline client_stats_entry_t * find_client_stats (const uint8_t * mac ) {
52+ for (int i = 0 ; i < CLIENT_STATS_MAX ; i ++ ) {
53+ if (client_stats [i ].active && memcmp (client_stats [i ].mac , mac , 6 ) == 0 ) {
54+ return & client_stats [i ];
55+ }
56+ }
57+ return NULL ;
58+ }
59+
60+ void client_stats_on_connect (const uint8_t * mac ) {
61+ // Keep existing stats on reconnect
62+ client_stats_entry_t * existing = find_client_stats (mac );
63+ if (existing ) {
64+ existing -> connected = 1 ;
65+ return ;
66+ }
67+ // Find free slot: prefer inactive, then disconnected
68+ int free_slot = -1 ;
69+ int disconnected_slot = -1 ;
70+ for (int i = 0 ; i < CLIENT_STATS_MAX ; i ++ ) {
71+ if (!client_stats [i ].active ) {
72+ free_slot = i ;
73+ break ;
74+ } else if (!client_stats [i ].connected && disconnected_slot < 0 ) {
75+ disconnected_slot = i ;
76+ }
77+ }
78+ int slot = (free_slot >= 0 ) ? free_slot : disconnected_slot ;
79+ if (slot >= 0 ) {
80+ memcpy (client_stats [slot ].mac , mac , 6 );
81+ client_stats [slot ].bytes_sent = 0 ;
82+ client_stats [slot ].bytes_received = 0 ;
83+ client_stats [slot ].packets_sent = 0 ;
84+ client_stats [slot ].packets_received = 0 ;
85+ client_stats [slot ].active = 1 ;
86+ client_stats [slot ].connected = 1 ;
87+ }
88+ }
89+
90+ void client_stats_on_disconnect (const uint8_t * mac ) {
91+ client_stats_entry_t * entry = find_client_stats (mac );
92+ if (entry ) {
93+ entry -> connected = 0 ;
94+ }
95+ }
96+
97+ int client_stats_get_all (client_stats_entry_t * out , int max_entries ) {
98+ int count = 0 ;
99+ for (int i = 0 ; i < CLIENT_STATS_MAX && count < max_entries ; i ++ ) {
100+ if (client_stats [i ].active ) {
101+ memcpy (& out [count ], & client_stats [i ], sizeof (client_stats_entry_t ));
102+ count ++ ;
103+ }
104+ }
105+ return count ;
106+ }
107+
108+ void client_stats_reset_all (void ) {
109+ for (int i = 0 ; i < CLIENT_STATS_MAX ; i ++ ) {
110+ if (client_stats [i ].active ) {
111+ client_stats [i ].bytes_sent = 0 ;
112+ client_stats [i ].bytes_received = 0 ;
113+ client_stats [i ].packets_sent = 0 ;
114+ client_stats [i ].packets_received = 0 ;
115+ }
116+ }
117+ }
118+
119+ void format_bytes_human (uint64_t bytes , char * buf , size_t len ) {
120+ if (bytes >= 1073741824ULL )
121+ snprintf (buf , len , "%.1f GB" , (double )bytes / 1073741824.0 );
122+ else if (bytes >= 1048576ULL )
123+ snprintf (buf , len , "%.1f MB" , (double )bytes / 1048576.0 );
124+ else if (bytes >= 1024ULL )
125+ snprintf (buf , len , "%.1f KB" , (double )bytes / 1024.0 );
126+ else
127+ snprintf (buf , len , "%" PRIu64 " B" , bytes );
128+ }
129+
46130// Hook function to count received bytes via netif input and ACL check
47131static err_t netif_input_hook (struct pbuf * p , struct netif * netif ) {
48132 bool is_acl_monitored = false;
@@ -456,6 +540,16 @@ static err_t ap_netif_input_hook(struct pbuf *p, struct netif *netif) {
456540 // Clamp TCP MSS on SYN packets from clients
457541 clamp_tcp_mss (p , ap_mss_clamp );
458542
543+ // Per-client byte counting: source MAC = client
544+ if (p != NULL && p -> len >= 14 ) {
545+ const uint8_t * src_mac = ((const uint8_t * )p -> payload ) + 6 ;
546+ client_stats_entry_t * entry = find_client_stats (src_mac );
547+ if (entry ) {
548+ entry -> bytes_received += p -> tot_len ;
549+ entry -> packets_received ++ ;
550+ }
551+ }
552+
459553 // Capture packet based on mode and ACL monitor flag (AP interface = true)
460554 if (pcap_should_capture (is_acl_monitored , true)) {
461555 pcap_capture_packet (p );
@@ -492,6 +586,16 @@ static err_t ap_netif_linkoutput_hook(struct netif *netif, struct pbuf *p) {
492586 // Clamp TCP MSS on SYN/SYN-ACK packets to clients
493587 clamp_tcp_mss (p , ap_mss_clamp );
494588
589+ // Per-client byte counting: dest MAC = client
590+ if (p != NULL && p -> len >= 14 ) {
591+ const uint8_t * dst_mac = (const uint8_t * )p -> payload ;
592+ client_stats_entry_t * entry = find_client_stats (dst_mac );
593+ if (entry ) {
594+ entry -> bytes_sent += p -> tot_len ;
595+ entry -> packets_sent ++ ;
596+ }
597+ }
598+
495599 // Capture packet based on mode and ACL monitor flag (AP interface = true)
496600 if (pcap_should_capture (is_acl_monitored , true)) {
497601 pcap_capture_packet (p );
0 commit comments