How S-UI Implements Traffic Statistics and Client Monitoring: A Deep Dive into Sing-Box Integration
S-UI implements traffic statistics and client monitoring through two specialized trackers—StatsTracker for atomic byte counting and ConnTracker for live connection mapping—both integrated with the underlying Sing-Box core.
S-UI is a modern web interface built on top of Sing-Box that requires granular visibility into network traffic and active client connections. The implementation relies on atomic counters for precise traffic measurement and a UUID-based registry for real-time connection tracking, all residing in the core package of the alireza0/s-ui repository.
Core Architecture: The Two-Tracker System
S-UI's monitoring capabilities rest on two complementary structures that intercept connections at the routing layer.
StatsTracker: Atomic Byte Counters
The StatsTracker (core/tracker_stats.go) maintains thread-safe statistics for every inbound, outbound, and user using atomic.Int64 counters. When Sing-Box routes a connection, the tracker wraps it with bufio.NewInt64CounterConn (for TCP) or bufio.NewInt64CounterPacketConn (for UDP). These wrappers automatically increment counters on every read and write operation without blocking the I/O stream.
ConnTracker: Live Connection Registry
The ConnTracker (core/tracker_conn.go) manages a concurrent map of active connections indexed by UUID. It generates unique identifiers via generateConnectionID and stores ConnectionInfo structs containing the connection object, inbound tag, and protocol type. This registry powers the "online" indicators in the UI by maintaining only live connections.
Traffic Statistics Implementation Details
The flow from raw packets to persistent database records involves connection interception, periodic aggregation, and structured storage.
Connection Routing and Wrapping
When the Sing-Box core starts via Core.Start in core/main.go, it exposes a StatsTracker instance. During routing, the system calls StatsTracker.RoutedConnection:
readCounters, writeCounters := c.getReadCounters(metadata.Inbound,
matchOutbound.Tag(),
metadata.User)
// Wrap connection to auto-update counters on every Read/Write
return bufio.NewInt64CounterConn(conn, readCounters, writeCounters)
The wrapper updates atomic counters transparently, ensuring zero-overhead statistics collection during data transfer.
Periodic Collection and Persistence
A background cron job (cronjob/statsJob.go) periodically invokes StatsService.SaveStats. The StatsTracker.GetStats() method performs a zeroing read on each counter—swapping the current value into a local variable and resetting the counter to zero. It emits model.Stats rows for download (direction=false) and upload (direction=true) per resource.
The service layer writes aggregated per-user traffic to the clients table. If traffic recording is enabled, it persists raw statistical rows to the stats table using tx.Create(&stats).
Querying and Downsampling
Administrators query historical data via StatsService.GetStats(resource, tag, limit). This method reads recent rows and applies downsampleStats to compress data into fixed buckets, optimizing chart rendering in the web interface without losing trend visibility.
Client Monitoring and Online Detection
Real-time client monitoring depends on accurate lifecycle tracking of every TCP and UDP session.
Connection Registration and UUID Tracking
When a connection enters the system, ConnTracker.RoutedConnection (or RoutedPacketConnection for UDP) executes:
- Generates a UUID via
generateConnectionID - Stores a
ConnectionInfostruct inc.connections - Returns a wrapped connection that maintains the registry entry
Automatic Cleanup on Close or Error
Each connection wraps in wrappedConn or wrappedPacketConn, which implements doUntrack(). This cleanup function executes exactly once when:
ReadorWritereturns EOF or a non-temporary network error (checked viashouldUntrackIOErr)- The consumer explicitly calls
Close
The atomic removal from c.connections ensures the map always reflects genuinely active sessions, not stale entries.
Building Online Resource Snapshots
During each statistics collection cycle, StatsService.SaveStats constructs an onlineResources structure from direction-true (upload) rows. This snapshot identifies which inbounds, outbounds, and users currently exhibit traffic activity. The StatsService.GetOnlines method exposes this data to the frontend, enabling the UI to display live connection indicators.
Additionally, administrative methods like CloseConnByInbound allow bulk termination of connections belonging to a specific inbound tag—useful when removing or restarting proxy listeners.
Practical Code Examples
Retrieving Live Traffic Counters
Access real-time statistics within a request handler by querying the Sing-Box instance:
// Assume corePtr is the global Core instance
if corePtr != nil && corePtr.IsRunning() {
box := corePtr.GetInstance()
stats := box.StatsTracker().GetStats() // *[]model.Stats
// Marshal to JSON for API response
}
Wrapping Connections Manually
For custom middleware or debugging, manually wrap connections to capture statistics:
func wrapForStats(conn net.Conn, inbound, outbound, user string) net.Conn {
tracker := corePtr.GetInstance().StatsTracker()
read, write := tracker.getReadCounters(inbound, outbound, user)
// Auto-updating wrapper
return bufio.NewInt64CounterConn(conn, read, write)
}
Listing Active Inbounds
Query the service layer to identify currently active resources:
service := &service.StatsService{}
online, err := service.GetOnlines()
if err == nil {
fmt.Printf("Active inbounds: %v\n", online.Inbound)
}
Terminating Connections by Resource
Forcefully close all connections associated with a specific inbound tag:
tracker := core.NewConnTracker() // Usually obtained from Core instance
closed := tracker.CloseConnByInbound("my-inbound-tag")
fmt.Printf("Closed %d connections belonging to my-inbound-tag\n", closed)
Summary
- StatsTracker (
core/tracker_stats.go) provides thread-safe byte counting using atomic integers and automatic counter wrapping viabufio.NewInt64CounterConn. - ConnTracker (
core/tracker_conn.go) maintains a live UUID-indexed map of active TCP and UDP connections with automatic cleanup on close or error. - Periodic aggregation occurs via cron jobs that zero counters and persist data to the
clientsandstatstables throughStatsService. - Online detection relies on upload-direction statistics rows to build real-time snapshots of active inbounds, outbounds, and users.
- Administrative control includes bulk connection termination via
CloseConnByInboundand downsampling for efficient historical queries.
Frequently Asked Questions
How does S-UI ensure traffic counters remain accurate under high concurrency?
S-UI uses atomic.Int64 counters within StatsTracker that survive concurrent accesses without locks. The counters are updated via bufio wrappers that increment atomically during read/write operations, ensuring precise statistics even under heavy parallel load.
What happens to connection tracking when a client disconnects unexpectedly?
The wrappedConn and wrappedPacketConn structures in ConnTracker automatically detect EOF or network errors through shouldUntrackIOErr. When detected, they invoke doUntrack() exactly once to remove the connection from the live map, ensuring the "online" status remains accurate without manual intervention.
Where does S-UI store historical traffic data?
Historical data persists in two locations: aggregated per-user totals update the clients table, while detailed raw statistics insert into the stats table when traffic recording is enabled. The StatsService in service/stats.go handles these database operations and supports downsampling for efficient chart rendering.
Can administrators forcefully terminate active client connections?
Yes. The ConnTracker exposes CloseConnByInbound, which iterates the live connection map and closes all connections matching a specific inbound tag. This functionality triggers automatically when inbounds are removed from the configuration or can be called programmatically for administrative overrides.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →