Public btest server: byte budget, multi-conn, web dashboard, quotas
- Inline byte budget in BandwidthState prevents quota overshoot at any
link speed (TX/RX loops check per-packet, not per-interval)
- TCP multi-connection support for server-pro (session tokens, secondary
connection joins, delegates to standard multi-conn handler)
- MD5 password verification against stored raw passwords in user DB
- Web dashboard: quota progress bars (daily/weekly/monthly), JSON export
endpoint (/api/ip/{ip}/export), quota API (/api/ip/{ip}/quota)
- Landing page with usage instructions, UDP NAT warning, credentials
- Fix IP usage double-counting bug in QuotaManager::record_usage
- UserDb now stores DB path and raw passwords for MD5 auth
- 10 enforcer tests (4 new: budget calc, budget stop, budget exhausted,
unlimited passthrough)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,9 @@ pub struct BandwidthState {
|
||||
pub intervals: AtomicU32,
|
||||
/// Remote peer's CPU usage (received via status messages)
|
||||
pub remote_cpu: AtomicU8,
|
||||
/// Remaining byte budget (TX + RX combined). When this reaches 0 the test
|
||||
/// stops immediately. u64::MAX means unlimited (default for non-pro server).
|
||||
pub byte_budget: AtomicU64,
|
||||
}
|
||||
|
||||
impl BandwidthState {
|
||||
@@ -38,6 +41,7 @@ impl BandwidthState {
|
||||
total_lost_packets: AtomicU64::new(0),
|
||||
intervals: AtomicU32::new(0),
|
||||
remote_cpu: AtomicU8::new(0),
|
||||
byte_budget: AtomicU64::new(u64::MAX),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,6 +54,29 @@ impl BandwidthState {
|
||||
self.intervals.fetch_add(1, Relaxed);
|
||||
}
|
||||
|
||||
/// Try to spend `amount` bytes from the budget. Returns `true` if allowed,
|
||||
/// `false` if the budget is exhausted (and sets `running = false`).
|
||||
#[inline]
|
||||
pub fn spend_budget(&self, amount: u64) -> bool {
|
||||
use std::sync::atomic::Ordering::{Relaxed, SeqCst};
|
||||
// Fast path: unlimited budget (non-pro server)
|
||||
let current = self.byte_budget.load(Relaxed);
|
||||
if current == u64::MAX {
|
||||
return true;
|
||||
}
|
||||
if current < amount {
|
||||
self.running.store(false, SeqCst);
|
||||
return false;
|
||||
}
|
||||
self.byte_budget.fetch_sub(amount, Relaxed);
|
||||
true
|
||||
}
|
||||
|
||||
/// Set the byte budget (total bytes allowed for the entire test).
|
||||
pub fn set_budget(&self, budget: u64) {
|
||||
self.byte_budget.store(budget, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Get summary for syslog reporting.
|
||||
pub fn summary(&self) -> (u64, u64, u64, u32) {
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
Reference in New Issue
Block a user