|
|
@ -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`, |
|
|
|