UNPKG

@push.rocks/smartproxy

Version:

A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.

765 lines 67.5 kB
import { exec, execSync } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); /** * IPTablesProxy sets up iptables NAT rules to forward TCP traffic. * Enhanced with multi-port support, IPv6, and integration with PortProxy/NetworkProxy. */ export class IPTablesProxy { constructor(settings) { this.rules = []; this.customChain = null; // Validate inputs to prevent command injection this.validateSettings(settings); // Set default settings this.settings = { ...settings, toHost: settings.toHost || 'localhost', protocol: settings.protocol || 'tcp', enableLogging: settings.enableLogging !== undefined ? settings.enableLogging : false, ipv6Support: settings.ipv6Support !== undefined ? settings.ipv6Support : false, checkExistingRules: settings.checkExistingRules !== undefined ? settings.checkExistingRules : true, netProxyIntegration: settings.netProxyIntegration || { enabled: false } }; // Generate a unique identifier for the rules added by this instance this.ruleTag = `IPTablesProxy:${Date.now()}:${Math.random().toString(36).substr(2, 5)}`; if (this.settings.addJumpRule) { this.customChain = `IPTablesProxy_${Math.random().toString(36).substr(2, 5)}`; } // Register cleanup handlers if deleteOnExit is true if (this.settings.deleteOnExit) { const cleanup = () => { try { this.stopSync(); } catch (err) { console.error('Error cleaning iptables rules on exit:', err); } }; process.on('exit', cleanup); process.on('SIGINT', () => { cleanup(); process.exit(); }); process.on('SIGTERM', () => { cleanup(); process.exit(); }); } } /** * Validates settings to prevent command injection and ensure valid values */ validateSettings(settings) { // Validate port numbers const validatePorts = (port) => { if (Array.isArray(port)) { port.forEach(p => validatePorts(p)); return; } if (typeof port === 'number') { if (port < 1 || port > 65535) { throw new Error(`Invalid port number: ${port}`); } } else if (typeof port === 'object') { if (port.from < 1 || port.from > 65535 || port.to < 1 || port.to > 65535 || port.from > port.to) { throw new Error(`Invalid port range: ${port.from}-${port.to}`); } } }; validatePorts(settings.fromPort); validatePorts(settings.toPort); // Define regex patterns at the method level so they're available throughout const ipRegex = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$/; const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/; // Validate IP addresses const validateIPs = (ips) => { if (!ips) return; for (const ip of ips) { if (!ipRegex.test(ip) && !ipv6Regex.test(ip)) { throw new Error(`Invalid IP address format: ${ip}`); } } }; validateIPs(settings.allowedSourceIPs); validateIPs(settings.bannedSourceIPs); // Validate toHost - only allow hostnames or IPs if (settings.toHost) { const hostRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; if (!hostRegex.test(settings.toHost) && !ipRegex.test(settings.toHost) && !ipv6Regex.test(settings.toHost)) { throw new Error(`Invalid host format: ${settings.toHost}`); } } } /** * Normalizes port specifications into an array of port ranges */ normalizePortSpec(portSpec) { const result = []; if (Array.isArray(portSpec)) { // If it's an array, process each element for (const spec of portSpec) { result.push(...this.normalizePortSpec(spec)); } } else if (typeof portSpec === 'number') { // Single port becomes a range with the same start and end result.push({ from: portSpec, to: portSpec }); } else { // Already a range result.push(portSpec); } return result; } /** * Gets the appropriate iptables command based on settings */ getIptablesCommand(isIpv6 = false) { return isIpv6 ? 'ip6tables' : 'iptables'; } /** * Checks if a rule already exists in iptables */ async ruleExists(table, command, isIpv6 = false) { try { const iptablesCmd = this.getIptablesCommand(isIpv6); const { stdout } = await execAsync(`${iptablesCmd}-save -t ${table}`); // Convert the command to the format found in iptables-save output // (This is a simplification - in reality, you'd need more parsing) const rulePattern = command.replace(`${iptablesCmd} -t ${table} -A `, '-A '); return stdout.split('\n').some(line => line.trim() === rulePattern); } catch (err) { this.log('error', `Failed to check if rule exists: ${err}`); return false; } } /** * Sets up a custom chain for better rule management */ async setupCustomChain(isIpv6 = false) { if (!this.customChain) return true; const iptablesCmd = this.getIptablesCommand(isIpv6); const table = 'nat'; try { // Create the chain await execAsync(`${iptablesCmd} -t ${table} -N ${this.customChain}`); this.log('info', `Created custom chain: ${this.customChain}`); // Add jump rule to PREROUTING chain const jumpCommand = `${iptablesCmd} -t ${table} -A PREROUTING -j ${this.customChain} -m comment --comment "${this.ruleTag}:JUMP"`; await execAsync(jumpCommand); this.log('info', `Added jump rule to ${this.customChain}`); // Store the jump rule this.rules.push({ table, chain: 'PREROUTING', command: jumpCommand, tag: `${this.ruleTag}:JUMP`, added: true }); return true; } catch (err) { this.log('error', `Failed to set up custom chain: ${err}`); return false; } } /** * Add a source IP filter rule */ async addSourceIPFilter(isIpv6 = false) { if (!this.settings.allowedSourceIPs && !this.settings.bannedSourceIPs) { return true; } const iptablesCmd = this.getIptablesCommand(isIpv6); const table = 'nat'; const chain = this.customChain || 'PREROUTING'; try { // Add banned IPs first (explicit deny) if (this.settings.bannedSourceIPs && this.settings.bannedSourceIPs.length > 0) { for (const ip of this.settings.bannedSourceIPs) { const command = `${iptablesCmd} -t ${table} -A ${chain} -s ${ip} -j DROP -m comment --comment "${this.ruleTag}:BANNED"`; // Check if rule already exists if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) { this.log('info', `Rule already exists, skipping: ${command}`); continue; } await execAsync(command); this.log('info', `Added banned IP rule: ${command}`); this.rules.push({ table, chain, command, tag: `${this.ruleTag}:BANNED`, added: true }); } } // Add allowed IPs (explicit allow) if (this.settings.allowedSourceIPs && this.settings.allowedSourceIPs.length > 0) { // First add a default deny for all const denyAllCommand = `${iptablesCmd} -t ${table} -A ${chain} -p ${this.settings.protocol} -j DROP -m comment --comment "${this.ruleTag}:DENY_ALL"`; // Add allow rules for specific IPs for (const ip of this.settings.allowedSourceIPs) { const command = `${iptablesCmd} -t ${table} -A ${chain} -s ${ip} -p ${this.settings.protocol} -j ACCEPT -m comment --comment "${this.ruleTag}:ALLOWED"`; // Check if rule already exists if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) { this.log('info', `Rule already exists, skipping: ${command}`); continue; } await execAsync(command); this.log('info', `Added allowed IP rule: ${command}`); this.rules.push({ table, chain, command, tag: `${this.ruleTag}:ALLOWED`, added: true }); } // Now add the default deny after all allows if (this.settings.checkExistingRules && await this.ruleExists(table, denyAllCommand, isIpv6)) { this.log('info', `Rule already exists, skipping: ${denyAllCommand}`); } else { await execAsync(denyAllCommand); this.log('info', `Added default deny rule: ${denyAllCommand}`); this.rules.push({ table, chain, command: denyAllCommand, tag: `${this.ruleTag}:DENY_ALL`, added: true }); } } return true; } catch (err) { this.log('error', `Failed to add source IP filter rules: ${err}`); return false; } } /** * Adds a port forwarding rule */ async addPortForwardingRule(fromPortRange, toPortRange, isIpv6 = false) { const iptablesCmd = this.getIptablesCommand(isIpv6); const table = 'nat'; const chain = this.customChain || 'PREROUTING'; try { // Handle single port case if (fromPortRange.from === fromPortRange.to && toPortRange.from === toPortRange.to) { // Single port forward const command = `${iptablesCmd} -t ${table} -A ${chain} -p ${this.settings.protocol} --dport ${fromPortRange.from} ` + `-j DNAT --to-destination ${this.settings.toHost}:${toPortRange.from} ` + `-m comment --comment "${this.ruleTag}:DNAT"`; // Check if rule already exists if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) { this.log('info', `Rule already exists, skipping: ${command}`); } else { await execAsync(command); this.log('info', `Added port forwarding rule: ${command}`); this.rules.push({ table, chain, command, tag: `${this.ruleTag}:DNAT`, added: true }); } } else if (fromPortRange.to - fromPortRange.from === toPortRange.to - toPortRange.from) { // Port range forward with equal ranges const command = `${iptablesCmd} -t ${table} -A ${chain} -p ${this.settings.protocol} --dport ${fromPortRange.from}:${fromPortRange.to} ` + `-j DNAT --to-destination ${this.settings.toHost}:${toPortRange.from}-${toPortRange.to} ` + `-m comment --comment "${this.ruleTag}:DNAT_RANGE"`; // Check if rule already exists if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) { this.log('info', `Rule already exists, skipping: ${command}`); } else { await execAsync(command); this.log('info', `Added port range forwarding rule: ${command}`); this.rules.push({ table, chain, command, tag: `${this.ruleTag}:DNAT_RANGE`, added: true }); } } else { // Unequal port ranges need individual rules for (let i = 0; i <= fromPortRange.to - fromPortRange.from; i++) { const fromPort = fromPortRange.from + i; const toPort = toPortRange.from + i % (toPortRange.to - toPortRange.from + 1); const command = `${iptablesCmd} -t ${table} -A ${chain} -p ${this.settings.protocol} --dport ${fromPort} ` + `-j DNAT --to-destination ${this.settings.toHost}:${toPort} ` + `-m comment --comment "${this.ruleTag}:DNAT_INDIVIDUAL"`; // Check if rule already exists if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) { this.log('info', `Rule already exists, skipping: ${command}`); continue; } await execAsync(command); this.log('info', `Added individual port forwarding rule: ${command}`); this.rules.push({ table, chain, command, tag: `${this.ruleTag}:DNAT_INDIVIDUAL`, added: true }); } } // If preserveSourceIP is false, add a MASQUERADE rule if (!this.settings.preserveSourceIP) { // For port range const masqCommand = `${iptablesCmd} -t nat -A POSTROUTING -p ${this.settings.protocol} -d ${this.settings.toHost} ` + `--dport ${toPortRange.from}:${toPortRange.to} -j MASQUERADE ` + `-m comment --comment "${this.ruleTag}:MASQ"`; // Check if rule already exists if (this.settings.checkExistingRules && await this.ruleExists('nat', masqCommand, isIpv6)) { this.log('info', `Rule already exists, skipping: ${masqCommand}`); } else { await execAsync(masqCommand); this.log('info', `Added MASQUERADE rule: ${masqCommand}`); this.rules.push({ table: 'nat', chain: 'POSTROUTING', command: masqCommand, tag: `${this.ruleTag}:MASQ`, added: true }); } } return true; } catch (err) { this.log('error', `Failed to add port forwarding rule: ${err}`); // Try to roll back any rules that were already added await this.rollbackRules(); return false; } } /** * Special handling for NetworkProxy integration */ async setupNetworkProxyIntegration(isIpv6 = false) { if (!this.settings.netProxyIntegration?.enabled) { return true; } const netProxyConfig = this.settings.netProxyIntegration; const iptablesCmd = this.getIptablesCommand(isIpv6); const table = 'nat'; const chain = this.customChain || 'PREROUTING'; try { // If redirectLocalhost is true, set up special rule to redirect localhost traffic to NetworkProxy if (netProxyConfig.redirectLocalhost && netProxyConfig.sslTerminationPort) { const redirectCommand = `${iptablesCmd} -t ${table} -A OUTPUT -p tcp -d 127.0.0.1 -j REDIRECT ` + `--to-port ${netProxyConfig.sslTerminationPort} ` + `-m comment --comment "${this.ruleTag}:NETPROXY_REDIRECT"`; // Check if rule already exists if (this.settings.checkExistingRules && await this.ruleExists(table, redirectCommand, isIpv6)) { this.log('info', `Rule already exists, skipping: ${redirectCommand}`); } else { await execAsync(redirectCommand); this.log('info', `Added NetworkProxy redirection rule: ${redirectCommand}`); this.rules.push({ table, chain: 'OUTPUT', command: redirectCommand, tag: `${this.ruleTag}:NETPROXY_REDIRECT`, added: true }); } } return true; } catch (err) { this.log('error', `Failed to set up NetworkProxy integration: ${err}`); return false; } } /** * Rolls back rules that were added in case of error */ async rollbackRules() { // Process rules in reverse order (LIFO) for (let i = this.rules.length - 1; i >= 0; i--) { const rule = this.rules[i]; if (rule.added) { try { // Convert -A (add) to -D (delete) const deleteCommand = rule.command.replace('-A', '-D'); await execAsync(deleteCommand); this.log('info', `Rolled back rule: ${deleteCommand}`); rule.added = false; } catch (err) { this.log('error', `Failed to roll back rule: ${err}`); } } } } /** * Sets up iptables rules for port forwarding with enhanced features */ async start() { // Optionally clean the slate first if (this.settings.forceCleanSlate) { await IPTablesProxy.cleanSlate(); } // First set up any custom chains if (this.settings.addJumpRule) { const chainSetupSuccess = await this.setupCustomChain(); if (!chainSetupSuccess) { throw new Error('Failed to set up custom chain'); } // For IPv6 if enabled if (this.settings.ipv6Support) { const chainSetupSuccessIpv6 = await this.setupCustomChain(true); if (!chainSetupSuccessIpv6) { this.log('warn', 'Failed to set up IPv6 custom chain, continuing with IPv4 only'); } } } // Add source IP filters await this.addSourceIPFilter(); if (this.settings.ipv6Support) { await this.addSourceIPFilter(true); } // Set up NetworkProxy integration if enabled if (this.settings.netProxyIntegration?.enabled) { const netProxySetupSuccess = await this.setupNetworkProxyIntegration(); if (!netProxySetupSuccess) { this.log('warn', 'Failed to set up NetworkProxy integration'); } if (this.settings.ipv6Support) { await this.setupNetworkProxyIntegration(true); } } // Normalize port specifications const fromPortRanges = this.normalizePortSpec(this.settings.fromPort); const toPortRanges = this.normalizePortSpec(this.settings.toPort); // Handle the case where fromPort and toPort counts don't match if (fromPortRanges.length !== toPortRanges.length) { if (toPortRanges.length === 1) { // If there's only one toPort, use it for all fromPorts for (const fromRange of fromPortRanges) { await this.addPortForwardingRule(fromRange, toPortRanges[0]); if (this.settings.ipv6Support) { await this.addPortForwardingRule(fromRange, toPortRanges[0], true); } } } else { throw new Error('Mismatched port counts: fromPort and toPort arrays must have equal length or toPort must be a single value'); } } else { // Add port forwarding rules for each port specification for (let i = 0; i < fromPortRanges.length; i++) { await this.addPortForwardingRule(fromPortRanges[i], toPortRanges[i]); if (this.settings.ipv6Support) { await this.addPortForwardingRule(fromPortRanges[i], toPortRanges[i], true); } } } // Final check - ensure we have at least one rule added if (this.rules.filter(r => r.added).length === 0) { throw new Error('No rules were added'); } } /** * Removes all added iptables rules */ async stop() { // Process rules in reverse order (LIFO) for (let i = this.rules.length - 1; i >= 0; i--) { const rule = this.rules[i]; if (rule.added) { try { // Convert -A (add) to -D (delete) const deleteCommand = rule.command.replace('-A', '-D'); await execAsync(deleteCommand); this.log('info', `Removed rule: ${deleteCommand}`); rule.added = false; } catch (err) { this.log('error', `Failed to remove rule: ${err}`); } } } // If we created a custom chain, we need to clean it up if (this.customChain) { try { // First flush the chain await execAsync(`iptables -t nat -F ${this.customChain}`); this.log('info', `Flushed custom chain: ${this.customChain}`); // Then delete it await execAsync(`iptables -t nat -X ${this.customChain}`); this.log('info', `Deleted custom chain: ${this.customChain}`); // Same for IPv6 if enabled if (this.settings.ipv6Support) { try { await execAsync(`ip6tables -t nat -F ${this.customChain}`); await execAsync(`ip6tables -t nat -X ${this.customChain}`); this.log('info', `Deleted IPv6 custom chain: ${this.customChain}`); } catch (err) { this.log('error', `Failed to delete IPv6 custom chain: ${err}`); } } } catch (err) { this.log('error', `Failed to delete custom chain: ${err}`); } } // Clear rules array this.rules = []; } /** * Synchronous version of stop, for use in exit handlers */ stopSync() { // Process rules in reverse order (LIFO) for (let i = this.rules.length - 1; i >= 0; i--) { const rule = this.rules[i]; if (rule.added) { try { // Convert -A (add) to -D (delete) const deleteCommand = rule.command.replace('-A', '-D'); execSync(deleteCommand); this.log('info', `Removed rule: ${deleteCommand}`); rule.added = false; } catch (err) { this.log('error', `Failed to remove rule: ${err}`); } } } // If we created a custom chain, we need to clean it up if (this.customChain) { try { // First flush the chain execSync(`iptables -t nat -F ${this.customChain}`); // Then delete it execSync(`iptables -t nat -X ${this.customChain}`); this.log('info', `Deleted custom chain: ${this.customChain}`); // Same for IPv6 if enabled if (this.settings.ipv6Support) { try { execSync(`ip6tables -t nat -F ${this.customChain}`); execSync(`ip6tables -t nat -X ${this.customChain}`); } catch (err) { // IPv6 failures are non-critical } } } catch (err) { this.log('error', `Failed to delete custom chain: ${err}`); } } // Clear rules array this.rules = []; } /** * Asynchronously cleans up any iptables rules in the nat table that were added by this module. * It looks for rules with comments containing "IPTablesProxy:". */ static async cleanSlate() { await IPTablesProxy.cleanSlateInternal(); // Also clean IPv6 rules await IPTablesProxy.cleanSlateInternal(true); } /** * Internal implementation of cleanSlate with IPv6 support */ static async cleanSlateInternal(isIpv6 = false) { const iptablesCmd = isIpv6 ? 'ip6tables' : 'iptables'; try { const { stdout } = await execAsync(`${iptablesCmd}-save -t nat`); const lines = stdout.split('\n'); const proxyLines = lines.filter(line => line.includes('IPTablesProxy:')); // First, find and remove any custom chains const customChains = new Set(); const jumpRules = []; for (const line of proxyLines) { if (line.includes('IPTablesProxy:JUMP')) { // Extract chain name from jump rule const match = line.match(/\s+-j\s+(\S+)\s+/); if (match && match[1].startsWith('IPTablesProxy_')) { customChains.add(match[1]); jumpRules.push(line); } } } // Remove jump rules first for (const line of jumpRules) { const trimmedLine = line.trim(); if (trimmedLine.startsWith('-A')) { // Replace the "-A" with "-D" to form a deletion command const deleteRule = trimmedLine.replace('-A', '-D'); const cmd = `${iptablesCmd} -t nat ${deleteRule}`; try { await execAsync(cmd); console.log(`Cleaned up iptables jump rule: ${cmd}`); } catch (err) { console.error(`Failed to remove iptables jump rule: ${cmd}`, err); } } } // Then remove all other rules for (const line of proxyLines) { if (!line.includes('IPTablesProxy:JUMP')) { // Skip jump rules we already handled const trimmedLine = line.trim(); if (trimmedLine.startsWith('-A')) { // Replace the "-A" with "-D" to form a deletion command const deleteRule = trimmedLine.replace('-A', '-D'); const cmd = `${iptablesCmd} -t nat ${deleteRule}`; try { await execAsync(cmd); console.log(`Cleaned up iptables rule: ${cmd}`); } catch (err) { console.error(`Failed to remove iptables rule: ${cmd}`, err); } } } } // Finally clean up custom chains for (const chain of customChains) { try { // Flush the chain await execAsync(`${iptablesCmd} -t nat -F ${chain}`); console.log(`Flushed custom chain: ${chain}`); // Delete the chain await execAsync(`${iptablesCmd} -t nat -X ${chain}`); console.log(`Deleted custom chain: ${chain}`); } catch (err) { console.error(`Failed to delete custom chain ${chain}:`, err); } } } catch (err) { console.error(`Failed to run ${iptablesCmd}-save: ${err}`); } } /** * Synchronously cleans up any iptables rules in the nat table that were added by this module. * It looks for rules with comments containing "IPTablesProxy:". * This method is intended for use in process exit handlers. */ static cleanSlateSync() { IPTablesProxy.cleanSlateSyncInternal(); // Also clean IPv6 rules IPTablesProxy.cleanSlateSyncInternal(true); } /** * Internal implementation of cleanSlateSync with IPv6 support */ static cleanSlateSyncInternal(isIpv6 = false) { const iptablesCmd = isIpv6 ? 'ip6tables' : 'iptables'; try { const stdout = execSync(`${iptablesCmd}-save -t nat`).toString(); const lines = stdout.split('\n'); const proxyLines = lines.filter(line => line.includes('IPTablesProxy:')); // First, find and remove any custom chains const customChains = new Set(); const jumpRules = []; for (const line of proxyLines) { if (line.includes('IPTablesProxy:JUMP')) { // Extract chain name from jump rule const match = line.match(/\s+-j\s+(\S+)\s+/); if (match && match[1].startsWith('IPTablesProxy_')) { customChains.add(match[1]); jumpRules.push(line); } } } // Remove jump rules first for (const line of jumpRules) { const trimmedLine = line.trim(); if (trimmedLine.startsWith('-A')) { // Replace the "-A" with "-D" to form a deletion command const deleteRule = trimmedLine.replace('-A', '-D'); const cmd = `${iptablesCmd} -t nat ${deleteRule}`; try { execSync(cmd); console.log(`Cleaned up iptables jump rule: ${cmd}`); } catch (err) { console.error(`Failed to remove iptables jump rule: ${cmd}`, err); } } } // Then remove all other rules for (const line of proxyLines) { if (!line.includes('IPTablesProxy:JUMP')) { // Skip jump rules we already handled const trimmedLine = line.trim(); if (trimmedLine.startsWith('-A')) { const deleteRule = trimmedLine.replace('-A', '-D'); const cmd = `${iptablesCmd} -t nat ${deleteRule}`; try { execSync(cmd); console.log(`Cleaned up iptables rule: ${cmd}`); } catch (err) { console.error(`Failed to remove iptables rule: ${cmd}`, err); } } } } // Finally clean up custom chains for (const chain of customChains) { try { // Flush the chain execSync(`${iptablesCmd} -t nat -F ${chain}`); // Delete the chain execSync(`${iptablesCmd} -t nat -X ${chain}`); console.log(`Deleted custom chain: ${chain}`); } catch (err) { console.error(`Failed to delete custom chain ${chain}:`, err); } } } catch (err) { console.error(`Failed to run ${iptablesCmd}-save: ${err}`); } } /** * Logging utility that respects the enableLogging setting */ log(level, message) { if (!this.settings.enableLogging && level === 'info') { return; } const timestamp = new Date().toISOString(); switch (level) { case 'info': console.log(`[${timestamp}] [INFO] ${message}`); break; case 'warn': console.warn(`[${timestamp}] [WARN] ${message}`); break; case 'error': console.error(`[${timestamp}] [ERROR] ${message}`); break; } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5pcHRhYmxlc3Byb3h5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5pcHRhYmxlc3Byb3h5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQy9DLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFFakMsTUFBTSxTQUFTLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0FBc0RsQzs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sYUFBYTtJQU14QixZQUFZLFFBQStCO1FBSm5DLFVBQUssR0FBbUIsRUFBRSxDQUFDO1FBRTNCLGdCQUFXLEdBQWtCLElBQUksQ0FBQztRQUd4QywrQ0FBK0M7UUFDL0MsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRWhDLHVCQUF1QjtRQUN2QixJQUFJLENBQUMsUUFBUSxHQUFHO1lBQ2QsR0FBRyxRQUFRO1lBQ1gsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNLElBQUksV0FBVztZQUN0QyxRQUFRLEVBQUUsUUFBUSxDQUFDLFFBQVEsSUFBSSxLQUFLO1lBQ3BDLGFBQWEsRUFBRSxRQUFRLENBQUMsYUFBYSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsS0FBSztZQUNwRixXQUFXLEVBQUUsUUFBUSxDQUFDLFdBQVcsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEtBQUs7WUFDOUUsa0JBQWtCLEVBQUUsUUFBUSxDQUFDLGtCQUFrQixLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDLENBQUMsQ0FBQyxJQUFJO1lBQ2xHLG1CQUFtQixFQUFFLFFBQVEsQ0FBQyxtQkFBbUIsSUFBSSxFQUFFLE9BQU8sRUFBRSxL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