Browse Source

refactor(bandwidth): move validation to Zod and simplify tc commands

pull/2368/head
Aaron Vu 6 months ago
parent
commit
c36d358f37
  1. 3
      src/server/database/repositories/general/types.ts
  2. 52
      src/server/utils/bandwidth.ts

3
src/server/database/repositories/general/types.ts

@ -18,7 +18,8 @@ const bandwidthEnabled = z.boolean({
}); });
const bandwidthLimit = z const bandwidthLimit = z
.number({ message: t('zod.general.bandwidthLimit') }) .number({ message: t('zod.general.bandwidthLimit') })
.min(0); .min(0)
.max(10000);
export const GeneralUpdateSchema = z.object({ export const GeneralUpdateSchema = z.object({
sessionTimeout: sessionTimeout, sessionTimeout: sessionTimeout,

52
src/server/utils/bandwidth.ts

@ -14,28 +14,6 @@ export interface TcCommands {
postDown: string; 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 * Check if IFB kernel module is available for upload limiting
*/ */
@ -52,7 +30,8 @@ export async function checkIfbAvailable(): Promise<boolean> {
} }
/** /**
* 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 interfaceName - WireGuard interface name (e.g., wg0)
* @param config - Bandwidth configuration * @param config - Bandwidth configuration
* @param ifbAvailable - Whether IFB module is available for upload limiting * @param ifbAvailable - Whether IFB module is available for upload limiting
@ -62,36 +41,28 @@ export function generateTcCommands(
config: BandwidthConfig, config: BandwidthConfig,
ifbAvailable: boolean = true ifbAvailable: boolean = true
): TcCommands { ): TcCommands {
// Validate interface name to prevent command injection const downloadMbps = config.downloadMbps;
if (!isValidInterfaceName(interfaceName)) { const uploadMbps = config.uploadMbps;
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);
if (!config.enabled || (downloadMbps === 0 && uploadMbps === 0)) { if (!config.enabled || (downloadMbps <= 0 && uploadMbps <= 0)) {
return { postUp: '', postDown: '' }; return { postUp: '', postDown: '' };
} }
const postUpParts: string[] = []; const postUpParts: string[] = [];
const postDownParts: string[] = []; const postDownParts: string[] = [];
// Download limit (egress on wg interface) // Download limit (egress on wg interface) - simplified single class
if (downloadMbps > 0) { if (downloadMbps > 0) {
postUpParts.push( postUpParts.push(
`tc qdisc add dev ${interfaceName} root handle 1: htb default 10`, `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`, `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`
); );
postDownParts.push( postDownParts.push(
`tc qdisc del dev ${interfaceName} root 2>/dev/null || true` `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) { if (uploadMbps > 0 && ifbAvailable) {
const ifbDev = 'ifb0'; const ifbDev = 'ifb0';
postUpParts.push( postUpParts.push(
@ -99,9 +70,8 @@ export function generateTcCommands(
`ip link set ${ifbDev} up`, `ip link set ${ifbDev} up`,
`tc qdisc add dev ${interfaceName} handle ffff: ingress`, `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 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 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`, `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`
); );
postDownParts.push( postDownParts.push(
`tc qdisc del dev ${interfaceName} handle ffff: ingress 2>/dev/null || true`, `tc qdisc del dev ${interfaceName} handle ffff: ingress 2>/dev/null || true`,

Loading…
Cancel
Save