This document describes the TollGate architecture for managing data-based (bytes metric) sessions with accurate per-customer tracking.
TollGate uses NoDogSplash's per-client statistics to accurately track data usage for individual customers. When a customer purchases a data-based session, the system:
- Captures a baseline of their current data usage
- Opens the network gate to allow traffic
- Periodically monitors their usage against the purchased allotment
- Automatically closes the gate when the allotment is consumed
- Merchant: Manages session purchases and monitors data usage
- Valve: Controls network gate and tracks per-customer data baselines
- NoDogSplash (ndsctl): Provides per-client network statistics
Instead of monitoring interface-level statistics (which would track all customers together), TollGate uses ndsctl json <mac_address> to get accurate per-customer data:
# Get stats for a specific MAC address
ndsctl json ac:e0:10:12:2d:75Returns:
{
"id": 2,
"ip": "192.168.5.132",
"mac": "ac:e0:10:12:2d:75",
"added": 1576258985,
"active": 1576264663,
"duration": 5678,
"token": "35dfa494",
"state": "Authenticated",
"downloaded": 18663, // in kilobytes
"avg_down_speed": 26.3,
"uploaded": 4986, // in kilobytes
"avg_up_speed": 7.03
}Advantages:
- Accurate per-customer tracking
- Downloaded and uploaded bytes tracked separately
- No interference from other customers on the same interface
- Thread-safe via mutex-protected ndsctl calls
sequenceDiagram
participant Client
participant Server (main.go)
participant Merchant
participant Valve
participant ndsctl
Client->>Server: POST / (Payment Event)
Server->>Merchant: PurchaseSession(event)
Merchant->>Merchant: calculateAllotment() -> 682MB
Merchant->>Merchant: AddAllotment(mac, "bytes", 682MB)
Merchant->>Valve: OpenGate(mac)
Valve->>ndsctl: json <mac>
ndsctl-->>Valve: {downloaded: X, uploaded: Y}
Valve->>Valve: Store baseline (X, Y)
Valve-->>Merchant: Success
Merchant-->>Server: Session Event
Server-->>Client: Session Event
Note over Merchant: Periodically check usage
loop Every 2 seconds
Merchant->>Valve: GetDataUsageSinceBaseline(mac)
Valve->>ndsctl: json <mac>
ndsctl-->>Valve: {downloaded: X', uploaded: Y'}
Valve->>Valve: Calculate: (X'-X) + (Y'-Y)
Valve-->>Merchant: usage bytes
alt usage >= 682MB
Merchant->>Valve: CloseGate(mac)
Valve->>Valve: ClearDataBaseline(mac)
end
end
The Valve module provides these key functions for data tracking:
- Calls
ndsctl json <mac>to retrieve current statistics - Returns downloaded and uploaded bytes (converted from kilobytes)
- Thread-safe via
ndsctlMutex
- Captures current usage as baseline when gate opens
- Stores baseline in
customerDataBaselinesmap - Called automatically by
OpenGate()
- Gets current usage via
GetClientStats() - Calculates:
(current_downloaded - baseline_downloaded) + (current_uploaded - baseline_uploaded) - Returns total bytes used since baseline
- Removes baseline when gate closes
- Called automatically by
CloseGate()
- Checks if a baseline exists for the customer
- Used to prevent re-authorization during session extensions
The Merchant module manages data sessions:
- Calculates data allotment from payment
- Adds allotment to customer's session
- Checks if baseline exists before opening gate (prevents re-authorization)
- Opens gate if no baseline exists
- Runs background goroutine checking usage every 2 seconds
- Monitors all active data-based sessions
- Closes gates when allotment is consumed
- Iterates through all data sessions
- Calls
GetDataUsageSinceBaseline()for each customer - Compares usage against allotment
- Closes gate when
usage >= allotment
Baseline tracking ensures we only count data used during the current session:
- Gate Opens:
OpenGate()callsSetDataBaseline()to capture current usage - Usage Calculation:
GetDataUsageSinceBaseline()returnscurrent - baseline - Session Extension: If customer pays again, baseline is preserved (no re-authorization)
- Gate Closes:
CloseGate()callsClearDataBaseline()to cleanup
All ndsctl calls are serialized using ndsctlMutex to prevent race conditions:
ndsctlMutex.Lock()
cmd := exec.Command("ndsctl", "json", macAddress)
output, err := cmd.CombinedOutput()
ndsctlMutex.Unlock()The mutex is unlocked immediately after the command completes to minimize lock duration.
When a customer makes an additional payment during an active session:
- Merchant checks
HasDataBaseline(mac) - If baseline exists, only adds allotment (no gate re-authorization)
- If no baseline, opens gate and captures new baseline
- Baseline is preserved across multiple payments
- Total usage is tracked cumulatively
This prevents:
- Re-authorization errors from NoDogSplash
- Baseline resets that would allow unlimited data
- Unnecessary gate operations
The architecture maintains clear separation:
- Merchant: Business logic (sessions, payments, monitoring)
- Valve: Gate control and data tracking
- UpstreamSessionManager: Pricing and session calculations (no gate control)
The Merchant works independently for downstream customers without requiring UpstreamSessionManager dependencies.
- Accurate Tracking: Per-customer statistics from NoDogSplash
- Baseline System: Only counts new usage, not historical data
- Thread Safety: Mutex-protected ndsctl calls prevent race conditions
- Session Extensions: Preserves baseline across multiple payments
- Clean Architecture: Proper separation of concerns between modules
- Automatic Enforcement: Background monitoring closes gates when allotment consumed