From c36d358f372464842b7582eae88c9b9a01289890 Mon Sep 17 00:00:00 2001 From: Aaron Vu Date: Thu, 8 Jan 2026 15:15:59 +0700 Subject: [PATCH] refactor(bandwidth): move validation to Zod and simplify tc commands --- .../database/repositories/general/types.ts | 3 +- src/server/utils/bandwidth.ts | 52 ++++--------------- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/src/server/database/repositories/general/types.ts b/src/server/database/repositories/general/types.ts index 6a728e0a..493f53bc 100644 --- a/src/server/database/repositories/general/types.ts +++ b/src/server/database/repositories/general/types.ts @@ -18,7 +18,8 @@ const bandwidthEnabled = z.boolean({ }); const bandwidthLimit = z .number({ message: t('zod.general.bandwidthLimit') }) - .min(0); + .min(0) + .max(10000); export const GeneralUpdateSchema = z.object({ sessionTimeout: sessionTimeout, diff --git a/src/server/utils/bandwidth.ts b/src/server/utils/bandwidth.ts index 3610cba9..e28646cd 100644 --- a/src/server/utils/bandwidth.ts +++ b/src/server/utils/bandwidth.ts @@ -14,28 +14,6 @@ export interface TcCommands { postDown: string; } -// Valid interface name pattern (e.g., wg0, eth0, enp0s3) -const VALID_INTERFACE_PATTERN = /^[a-zA-Z0-9_-]{1,15}$/; - -// Bandwidth limits (1 Mbps to 10 Gbps) -const MIN_BANDWIDTH_MBPS = 1; -const MAX_BANDWIDTH_MBPS = 10000; - -/** - * Validate interface name to prevent command injection - */ -function isValidInterfaceName(name: string): boolean { - return VALID_INTERFACE_PATTERN.test(name); -} - -/** - * Clamp bandwidth value to safe range - */ -function clampBandwidth(value: number): number { - if (value <= 0) return 0; - return Math.min(Math.max(value, MIN_BANDWIDTH_MBPS), MAX_BANDWIDTH_MBPS); -} - /** * Check if IFB kernel module is available for upload limiting */ @@ -52,7 +30,8 @@ export async function checkIfbAvailable(): Promise { } /** - * Generate tc commands for bandwidth limiting + * Generate tc commands for bandwidth limiting using simplified HTB qdisc + * Reference: https://www.procustodibus.com/blog/2022/12/limit-wireguard-bandwidth/ * @param interfaceName - WireGuard interface name (e.g., wg0) * @param config - Bandwidth configuration * @param ifbAvailable - Whether IFB module is available for upload limiting @@ -62,36 +41,28 @@ export function generateTcCommands( config: BandwidthConfig, ifbAvailable: boolean = true ): TcCommands { - // Validate interface name to prevent command injection - if (!isValidInterfaceName(interfaceName)) { - BW_DEBUG(`Invalid interface name: ${interfaceName}`); - return { postUp: '', postDown: '' }; - } - - // Clamp bandwidth values to safe range - const downloadMbps = clampBandwidth(config.downloadMbps); - const uploadMbps = clampBandwidth(config.uploadMbps); + const downloadMbps = config.downloadMbps; + const uploadMbps = config.uploadMbps; - if (!config.enabled || (downloadMbps === 0 && uploadMbps === 0)) { + if (!config.enabled || (downloadMbps <= 0 && uploadMbps <= 0)) { return { postUp: '', postDown: '' }; } const postUpParts: string[] = []; const postDownParts: string[] = []; - // Download limit (egress on wg interface) + // Download limit (egress on wg interface) - simplified single class if (downloadMbps > 0) { postUpParts.push( - `tc qdisc add dev ${interfaceName} root handle 1: htb default 10`, - `tc class add dev ${interfaceName} parent 1: classid 1:1 htb rate ${downloadMbps}mbit ceil ${downloadMbps}mbit`, - `tc class add dev ${interfaceName} parent 1:1 classid 1:10 htb rate ${downloadMbps}mbit ceil ${downloadMbps}mbit` + `tc qdisc add dev ${interfaceName} root handle 1: htb default 1`, + `tc class add dev ${interfaceName} parent 1: classid 1:1 htb rate ${downloadMbps}mbit ceil ${downloadMbps}mbit` ); postDownParts.push( `tc qdisc del dev ${interfaceName} root 2>/dev/null || true` ); } - // Upload limit (ingress via IFB mirror) + // Upload limit (ingress via IFB mirror) - simplified single class if (uploadMbps > 0 && ifbAvailable) { const ifbDev = 'ifb0'; postUpParts.push( @@ -99,9 +70,8 @@ export function generateTcCommands( `ip link set ${ifbDev} up`, `tc qdisc add dev ${interfaceName} handle ffff: ingress`, `tc filter add dev ${interfaceName} parent ffff: protocol all u32 match u32 0 0 action mirred egress redirect dev ${ifbDev}`, - `tc qdisc add dev ${ifbDev} root handle 1: htb default 10`, - `tc class add dev ${ifbDev} parent 1: classid 1:1 htb rate ${uploadMbps}mbit ceil ${uploadMbps}mbit`, - `tc class add dev ${ifbDev} parent 1:1 classid 1:10 htb rate ${uploadMbps}mbit ceil ${uploadMbps}mbit` + `tc qdisc add dev ${ifbDev} root handle 1: htb default 1`, + `tc class add dev ${ifbDev} parent 1: classid 1:1 htb rate ${uploadMbps}mbit ceil ${uploadMbps}mbit` ); postDownParts.push( `tc qdisc del dev ${interfaceName} handle ffff: ingress 2>/dev/null || true`,