feat(proxies): introduce nftables command executor and utilities, default certificate provider, expanded route/socket helper modules, and security improvements

This commit is contained in:
2026-01-30 04:06:32 +00:00
parent f25be4c55a
commit 9697ab3078
27 changed files with 2453 additions and 2048 deletions

View File

@@ -3,10 +3,8 @@ import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { delay } from '../../core/utils/async-utils.js';
import { AsyncFileSystem } from '../../core/utils/fs-utils.js';
import {
NftBaseError,
NftValidationError,
NftExecutionError,
NftResourceError
@@ -16,6 +14,12 @@ import type {
NfTableProxyOptions,
NfTablesStatus
} from './models/index.js';
import {
NftCommandExecutor,
normalizePortSpec,
validateSettings,
filterIPsByFamily
} from './utils/index.js';
const execAsync = promisify(exec);
@@ -44,11 +48,12 @@ export class NfTablesProxy {
private ruleTag: string;
private tableName: string;
private tempFilePath: string;
private executor: NftCommandExecutor;
private static NFT_CMD = 'nft';
constructor(settings: NfTableProxyOptions) {
// Validate inputs to prevent command injection
this.validateSettings(settings);
validateSettings(settings);
// Set default settings
this.settings = {
@@ -74,6 +79,16 @@ export class NfTablesProxy {
// Create a temp file path for batch operations
this.tempFilePath = path.join(os.tmpdir(), `nft-rules-${Date.now()}.nft`);
// Create the command executor
this.executor = new NftCommandExecutor(
(level, message, data) => this.log(level, message, data),
{
maxRetries: this.settings.maxRetries,
retryDelayMs: this.settings.retryDelayMs,
tempFilePath: this.tempFilePath
}
);
// Register cleanup handlers if deleteOnExit is true
if (this.settings.deleteOnExit) {
// Synchronous cleanup for 'exit' event (only sync code runs here)
@@ -104,183 +119,17 @@ export class NfTablesProxy {
}
}
/**
* Validates settings to prevent command injection and ensure valid values
*/
private validateSettings(settings: NfTableProxyOptions): void {
// Validate port numbers
const validatePorts = (port: number | PortRange | Array<number | PortRange>) => {
if (Array.isArray(port)) {
port.forEach(p => validatePorts(p));
return;
}
if (typeof port === 'number') {
if (port < 1 || port > 65535) {
throw new NftValidationError(`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 NftValidationError(`Invalid port range: ${port.from}-${port.to}`);
}
}
};
validatePorts(settings.fromPort);
validatePorts(settings.toPort);
// Define regex patterns for validation
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?: string[]) => {
if (!ips) return;
for (const ip of ips) {
if (!ipRegex.test(ip) && !ipv6Regex.test(ip)) {
throw new NftValidationError(`Invalid IP address format: ${ip}`);
}
}
};
validateIPs(settings.ipAllowList);
validateIPs(settings.ipBlockList);
// 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 NftValidationError(`Invalid host format: ${settings.toHost}`);
}
}
// Validate table name to prevent command injection
if (settings.tableName) {
const tableNameRegex = /^[a-zA-Z0-9_]+$/;
if (!tableNameRegex.test(settings.tableName)) {
throw new NftValidationError(`Invalid table name: ${settings.tableName}. Only alphanumeric characters and underscores are allowed.`);
}
}
// Validate QoS settings if enabled
if (settings.qos?.enabled) {
if (settings.qos.maxRate) {
const rateRegex = /^[0-9]+[kKmMgG]?bps$/;
if (!rateRegex.test(settings.qos.maxRate)) {
throw new NftValidationError(`Invalid rate format: ${settings.qos.maxRate}. Use format like "10mbps", "1gbps", etc.`);
}
}
if (settings.qos.priority !== undefined) {
if (settings.qos.priority < 1 || settings.qos.priority > 10 || !Number.isInteger(settings.qos.priority)) {
throw new NftValidationError(`Invalid priority: ${settings.qos.priority}. Must be an integer between 1 and 10.`);
}
}
}
}
/**
* Normalizes port specifications into an array of port ranges
*/
private normalizePortSpec(portSpec: number | PortRange | Array<number | PortRange>): PortRange[] {
const result: PortRange[] = [];
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;
}
/**
* Execute a command with retry capability
*/
private async executeWithRetry(command: string, maxRetries = 3, retryDelayMs = 1000): Promise<string> {
let lastError: Error | undefined;
for (let i = 0; i < maxRetries; i++) {
try {
const { stdout } = await execAsync(command);
return stdout;
} catch (err) {
lastError = err;
this.log('warn', `Command failed (attempt ${i+1}/${maxRetries}): ${command}`, { error: err.message });
// Wait before retry, unless it's the last attempt
if (i < maxRetries - 1) {
await delay(retryDelayMs);
}
}
}
throw new NftExecutionError(`Failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`);
}
/**
* Execute system command synchronously (single attempt, no retry)
* Used only for exit handlers where the process is terminating anyway.
* For normal operations, use the async executeWithRetry method.
*/
private executeSync(command: string): string {
try {
return execSync(command, { timeout: 5000 }).toString();
} catch (err) {
this.log('warn', `Sync command failed: ${command}`, { error: err.message });
throw err;
}
}
/**
* Execute nftables commands with a temporary file
* This helper handles the common pattern of writing rules to a temp file,
* executing nftables with the file, and cleaning up
*/
private async executeWithTempFile(rulesetContent: string): Promise<void> {
await AsyncFileSystem.writeFile(this.tempFilePath, rulesetContent);
try {
await this.executeWithRetry(
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
this.settings.maxRetries,
this.settings.retryDelayMs
);
} finally {
// Always clean up the temp file
await AsyncFileSystem.remove(this.tempFilePath);
}
}
/**
* Checks if nftables is available and the required modules are loaded
*/
private async checkNftablesAvailability(): Promise<boolean> {
try {
await this.executeWithRetry(`${NfTablesProxy.NFT_CMD} --version`, this.settings.maxRetries, this.settings.retryDelayMs);
// Check for conntrack support if we're using advanced NAT
if (this.settings.useAdvancedNAT) {
try {
await this.executeWithRetry('lsmod | grep nf_conntrack', this.settings.maxRetries, this.settings.retryDelayMs);
} catch (err) {
this.log('warn', 'Connection tracking modules might not be loaded, advanced NAT features may not work');
}
}
return true;
} catch (err) {
this.log('error', `nftables is not available: ${err.message}`);
return false;
const available = await this.executor.checkAvailability();
if (available && this.settings.useAdvancedNAT) {
await this.executor.checkConntrackModules();
}
return available;
}
/**
@@ -291,7 +140,7 @@ export class NfTablesProxy {
try {
// Check if the table already exists
const stdout = await this.executeWithRetry(
const stdout = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list tables ${family}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -301,7 +150,7 @@ export class NfTablesProxy {
if (!tableExists) {
// Create the table
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} add table ${family} ${this.tableName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -310,7 +159,7 @@ export class NfTablesProxy {
this.log('info', `Created table ${family} ${this.tableName}`);
// Create the nat chain for the prerouting hook
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_prerouting { type nat hook prerouting priority -100 ; }`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -320,7 +169,7 @@ export class NfTablesProxy {
// Create the nat chain for the postrouting hook if not preserving source IP
if (!this.settings.preserveSourceIP) {
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_postrouting { type nat hook postrouting priority 100 ; }`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -331,7 +180,7 @@ export class NfTablesProxy {
// Create the chain for NetworkProxy integration if needed
if (this.settings.netProxyIntegration?.enabled && this.settings.netProxyIntegration.redirectLocalhost) {
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_output { type nat hook output priority 0 ; }`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -342,7 +191,7 @@ export class NfTablesProxy {
// Create the QoS chain if needed
if (this.settings.qos?.enabled) {
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} qos_forward { type filter hook forward priority 0 ; }`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -372,11 +221,7 @@ export class NfTablesProxy {
): Promise<boolean> {
try {
// Filter IPs based on family
const filteredIPs = ips.filter(ip => {
if (family === 'ip6' && ip.includes(':')) return true;
if (family === 'ip' && ip.includes('.')) return true;
return false;
});
const filteredIPs = filterIPsByFamily(ips, family as 'ip' | 'ip6');
if (filteredIPs.length === 0) {
this.log('info', `No IP addresses of type ${setType} to add to set ${setName}`);
@@ -385,7 +230,7 @@ export class NfTablesProxy {
// Check if set already exists
try {
const sets = await this.executeWithRetry(
const sets = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list sets ${family} ${this.tableName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -395,7 +240,7 @@ export class NfTablesProxy {
this.log('info', `IP set ${setName} already exists, will add elements`);
} else {
// Create the set
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} add set ${family} ${this.tableName} ${setName} { type ${setType}; }`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -405,7 +250,7 @@ export class NfTablesProxy {
}
} catch (err) {
// Set might not exist yet, create it
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} add set ${family} ${this.tableName} ${setName} { type ${setType}; }`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -420,7 +265,7 @@ export class NfTablesProxy {
const batch = filteredIPs.slice(i, i + batchSize);
const elements = batch.join(', ');
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} add element ${family} ${this.tableName} ${setName} { ${elements} }`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -563,7 +408,7 @@ export class NfTablesProxy {
// Only write and apply if we have rules to add
if (rulesetContent) {
// Apply the ruleset using the helper
await this.executeWithTempFile(rulesetContent);
await this.executor.executeWithTempFile(rulesetContent);
this.log('info', `Added source IP filter rules for ${family}`);
@@ -593,7 +438,7 @@ export class NfTablesProxy {
* Gets a comma-separated list of all ports from a port specification
*/
private getAllPorts(portSpec: number | PortRange | Array<number | PortRange>): string {
const portRanges = this.normalizePortSpec(portSpec);
const portRanges = normalizePortSpec(portSpec);
const ports: string[] = [];
for (const range of portRanges) {
@@ -620,8 +465,8 @@ export class NfTablesProxy {
try {
// Get the port ranges
const fromPortRanges = this.normalizePortSpec(this.settings.fromPort);
const toPortRanges = this.normalizePortSpec(this.settings.toPort);
const fromPortRanges = normalizePortSpec(this.settings.fromPort);
const toPortRanges = normalizePortSpec(this.settings.toPort);
let rulesetContent = '';
@@ -670,7 +515,7 @@ export class NfTablesProxy {
// Apply the rules if we have any
if (rulesetContent) {
await this.executeWithTempFile(rulesetContent);
await this.executor.executeWithTempFile(rulesetContent);
this.log('info', `Added advanced NAT rules for ${family}`);
@@ -708,8 +553,8 @@ export class NfTablesProxy {
try {
// Normalize port specifications
const fromPortRanges = this.normalizePortSpec(this.settings.fromPort);
const toPortRanges = this.normalizePortSpec(this.settings.toPort);
const fromPortRanges = normalizePortSpec(this.settings.fromPort);
const toPortRanges = normalizePortSpec(this.settings.toPort);
// Handle the case where fromPort and toPort counts don't match
if (fromPortRanges.length !== toPortRanges.length) {
@@ -815,7 +660,7 @@ export class NfTablesProxy {
// Apply the ruleset if we have any rules
if (rulesetContent) {
// Apply the ruleset using the helper
await this.executeWithTempFile(rulesetContent);
await this.executor.executeWithTempFile(rulesetContent);
this.log('info', `Added port forwarding rules for ${family}`);
@@ -919,7 +764,7 @@ export class NfTablesProxy {
// Apply the ruleset if we have any rules
if (rulesetContent) {
await this.executeWithTempFile(rulesetContent);
await this.executor.executeWithTempFile(rulesetContent);
this.log('info', `Added port forwarding rules for ${family}`);
@@ -972,7 +817,7 @@ export class NfTablesProxy {
// Add priority marking if specified
if (this.settings.qos.priority !== undefined) {
// Check if the chain exists
const chainsOutput = await this.executeWithRetry(
const chainsOutput = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list chains ${family} ${this.tableName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -988,7 +833,7 @@ export class NfTablesProxy {
}
// Add the rules to mark packets with this priority
for (const range of this.normalizePortSpec(this.settings.toPort)) {
for (const range of normalizePortSpec(this.settings.toPort)) {
const markRule = `add rule ${family} ${this.tableName} ${qosChain} ${this.settings.protocol} dport ${range.from}-${range.to} counter goto prio${this.settings.qos.priority} comment "${this.ruleTag}:QOS_PRIORITY"`;
rulesetContent += `${markRule}\n`;
@@ -1005,7 +850,7 @@ export class NfTablesProxy {
// Apply the ruleset if we have any rules
if (rulesetContent) {
// Apply the ruleset using the helper
await this.executeWithTempFile(rulesetContent);
await this.executor.executeWithTempFile(rulesetContent);
this.log('info', `Added QoS rules for ${family}`);
@@ -1048,7 +893,7 @@ export class NfTablesProxy {
const rule = `add rule ${family} ${this.tableName} ${outputChain} ${this.settings.protocol} daddr ${localhost} redirect to :${netProxyConfig.sslTerminationPort} comment "${this.ruleTag}:NETPROXY_REDIRECT"`;
// Apply the rule
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} ${rule}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1091,7 +936,7 @@ export class NfTablesProxy {
const commentTag = commentMatch[1];
// List the chain to check if our rule is there
const stdout = await this.executeWithRetry(
const stdout = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list chain ${tableFamily} ${tableName} ${chainName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1127,7 +972,7 @@ export class NfTablesProxy {
try {
// For nftables, create a delete rule by replacing 'add' with 'delete'
const deleteRule = rule.ruleContents.replace('add rule', 'delete rule');
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} ${deleteRule}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1149,7 +994,7 @@ export class NfTablesProxy {
*/
private async tableExists(family: string, tableName: string): Promise<boolean> {
try {
const stdout = await this.executeWithRetry(
const stdout = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list tables ${family}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1178,7 +1023,7 @@ export class NfTablesProxy {
try {
// Try to get connection metrics if conntrack is available
try {
const stdout = await this.executeWithRetry('conntrack -C', this.settings.maxRetries, this.settings.retryDelayMs);
const stdout = await this.executor.executeWithRetry('conntrack -C', this.settings.maxRetries, this.settings.retryDelayMs);
metrics.activeConnections = parseInt(stdout.trim(), 10);
} catch (err) {
// conntrack not available, skip this metric
@@ -1187,7 +1032,7 @@ export class NfTablesProxy {
// Try to get forwarded connections count from nftables counters
try {
// Look for counters in our rules
const stdout = await this.executeWithRetry(
const stdout = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list table ip ${this.tableName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1238,7 +1083,7 @@ export class NfTablesProxy {
try {
for (const family of ['ip', 'ip6']) {
try {
const stdout = await this.executeWithRetry(
const stdout = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list sets ${family} ${this.tableName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1290,7 +1135,7 @@ export class NfTablesProxy {
try {
// Get list of configured tables
const stdout = await this.executeWithRetry(
const stdout = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list tables`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1396,8 +1241,8 @@ export class NfTablesProxy {
// Port forwarding rules
if (this.settings.useAdvancedNAT) {
// Advanced NAT with connection tracking
const fromPortRanges = this.normalizePortSpec(this.settings.fromPort);
const toPortRanges = this.normalizePortSpec(this.settings.toPort);
const fromPortRanges = normalizePortSpec(this.settings.fromPort);
const toPortRanges = normalizePortSpec(this.settings.toPort);
if (fromPortRanges.length === 1 && toPortRanges.length === 1) {
const fromRange = fromPortRanges[0];
@@ -1413,8 +1258,8 @@ export class NfTablesProxy {
}
} else {
// Standard NAT rules
const fromRanges = this.normalizePortSpec(this.settings.fromPort);
const toRanges = this.normalizePortSpec(this.settings.toPort);
const fromRanges = normalizePortSpec(this.settings.fromPort);
const toRanges = normalizePortSpec(this.settings.toPort);
if (fromRanges.length === 1 && toRanges.length === 1) {
const fromRange = fromRanges[0];
@@ -1460,7 +1305,7 @@ export class NfTablesProxy {
if (this.settings.qos.priority !== undefined) {
commands.push(`add chain ip ${this.tableName} prio${this.settings.qos.priority} { type filter hook forward priority ${this.settings.qos.priority * 10}; }`);
for (const range of this.normalizePortSpec(this.settings.toPort)) {
for (const range of normalizePortSpec(this.settings.toPort)) {
commands.push(`add rule ip ${this.tableName} qos_forward ${this.settings.protocol} dport ${range.from}-${range.to} counter goto prio${this.settings.qos.priority} comment "${this.ruleTag}:QOS_PRIORITY"`);
}
}
@@ -1586,7 +1431,7 @@ export class NfTablesProxy {
try {
// Apply the ruleset
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1611,7 +1456,7 @@ export class NfTablesProxy {
const [family, setName] = key.split(':');
try {
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} delete set ${family} ${this.tableName} ${setName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1661,7 +1506,7 @@ export class NfTablesProxy {
fs.writeFileSync(this.tempFilePath, rulesetContent);
// Apply the ruleset (single attempt, no retry - process is exiting)
this.executeSync(`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`);
this.executor.executeSync(`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`);
this.log('info', 'Removed all added rules');
@@ -1685,7 +1530,7 @@ export class NfTablesProxy {
const [family, setName] = key.split(':');
try {
this.executeSync(
this.executor.executeSync(
`${NfTablesProxy.NFT_CMD} delete set ${family} ${this.tableName} ${setName}`
);
} catch {
@@ -1722,7 +1567,7 @@ export class NfTablesProxy {
}
// Check if the table has any rules
const stdout = await this.executeWithRetry(
const stdout = await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} list table ${family} ${this.tableName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1732,7 +1577,7 @@ export class NfTablesProxy {
if (!hasRules) {
// Table is empty, delete it
await this.executeWithRetry(
await this.executor.executeWithRetry(
`${NfTablesProxy.NFT_CMD} delete table ${family} ${this.tableName}`,
this.settings.maxRetries,
this.settings.retryDelayMs
@@ -1759,7 +1604,7 @@ export class NfTablesProxy {
try {
// Check if table exists
const tableExistsOutput = this.executeSync(
const tableExistsOutput = this.executor.executeSync(
`${NfTablesProxy.NFT_CMD} list tables ${family}`
);
@@ -1770,7 +1615,7 @@ export class NfTablesProxy {
}
// Check if the table has any rules
const stdout = this.executeSync(
const stdout = this.executor.executeSync(
`${NfTablesProxy.NFT_CMD} list table ${family} ${this.tableName}`
);
@@ -1778,7 +1623,7 @@ export class NfTablesProxy {
if (!hasRules) {
// Table is empty, delete it
this.executeSync(
this.executor.executeSync(
`${NfTablesProxy.NFT_CMD} delete table ${family} ${this.tableName}`
);