fix: skip ip6tables when IPv6 is disabled (#2701)
fix: skip ip6tables when IPv6 is disabled (#2698) Co-authored-by: potatosips <potatosips@users.noreply.github.com>
This commit is contained in:
@@ -164,32 +164,38 @@ export const firewall = {
|
|||||||
/**
|
/**
|
||||||
* Initialize the custom chain if it doesn't exist
|
* Initialize the custom chain if it doesn't exist
|
||||||
*/
|
*/
|
||||||
async initChain(interfaceName: string): Promise<void> {
|
async initChain(interfaceName: string, enableIpv6: boolean): Promise<void> {
|
||||||
FW_DEBUG(
|
FW_DEBUG(
|
||||||
`Initializing firewall chain ${CHAIN_NAME} for interface ${interfaceName}`
|
`Initializing firewall chain ${CHAIN_NAME} for interface ${interfaceName}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create chain if not exists (iptables returns error if exists, so we ignore)
|
// Create chain if not exists (iptables returns error if exists, so we ignore)
|
||||||
await exec(`iptables -N ${CHAIN_NAME} 2>/dev/null || true`);
|
await exec(`iptables -N ${CHAIN_NAME} 2>/dev/null || true`);
|
||||||
await exec(`ip6tables -N ${CHAIN_NAME} 2>/dev/null || true`);
|
if (enableIpv6) {
|
||||||
|
await exec(`ip6tables -N ${CHAIN_NAME} 2>/dev/null || true`);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure chain is referenced from FORWARD (if not already)
|
// Ensure chain is referenced from FORWARD (if not already)
|
||||||
// Insert at position 1 to process before generic ACCEPT rules
|
// Insert at position 1 to process before generic ACCEPT rules
|
||||||
await exec(
|
await exec(
|
||||||
`iptables -C FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || iptables -I FORWARD 1 -i ${interfaceName} -j ${CHAIN_NAME}`
|
`iptables -C FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || iptables -I FORWARD 1 -i ${interfaceName} -j ${CHAIN_NAME}`
|
||||||
);
|
);
|
||||||
await exec(
|
if (enableIpv6) {
|
||||||
`ip6tables -C FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || ip6tables -I FORWARD 1 -i ${interfaceName} -j ${CHAIN_NAME}`
|
await exec(
|
||||||
);
|
`ip6tables -C FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || ip6tables -I FORWARD 1 -i ${interfaceName} -j ${CHAIN_NAME}`
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush all rules in the custom chain
|
* Flush all rules in the custom chain
|
||||||
*/
|
*/
|
||||||
async flushChain(): Promise<void> {
|
async flushChain(enableIpv6: boolean): Promise<void> {
|
||||||
FW_DEBUG(`Flushing firewall chain ${CHAIN_NAME}`);
|
FW_DEBUG(`Flushing firewall chain ${CHAIN_NAME}`);
|
||||||
await exec(`iptables -F ${CHAIN_NAME} 2>/dev/null || true`);
|
await exec(`iptables -F ${CHAIN_NAME} 2>/dev/null || true`);
|
||||||
await exec(`ip6tables -F ${CHAIN_NAME} 2>/dev/null || true`);
|
if (enableIpv6) {
|
||||||
|
await exec(`ip6tables -F ${CHAIN_NAME} 2>/dev/null || true`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,7 +251,7 @@ export const firewall = {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!wgInterface.firewallEnabled) {
|
if (!wgInterface.firewallEnabled) {
|
||||||
FW_DEBUG('Firewall filtering disabled, removing any existing rules');
|
FW_DEBUG('Firewall filtering disabled, removing any existing rules');
|
||||||
await this.removeFiltering(wgInterface.name);
|
await this.removeFiltering(wgInterface.name, enableIpv6);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,10 +268,10 @@ export const firewall = {
|
|||||||
FW_DEBUG('Rebuilding firewall rules...');
|
FW_DEBUG('Rebuilding firewall rules...');
|
||||||
|
|
||||||
// Initialize chain structure
|
// Initialize chain structure
|
||||||
await this.initChain(wgInterface.name);
|
await this.initChain(wgInterface.name, enableIpv6);
|
||||||
|
|
||||||
// Flush existing rules
|
// Flush existing rules
|
||||||
await this.flushChain();
|
await this.flushChain(enableIpv6);
|
||||||
|
|
||||||
// Apply rules for each enabled client
|
// Apply rules for each enabled client
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
@@ -299,22 +305,29 @@ export const firewall = {
|
|||||||
/**
|
/**
|
||||||
* Remove all firewall filtering (when feature is disabled)
|
* Remove all firewall filtering (when feature is disabled)
|
||||||
*/
|
*/
|
||||||
async removeFiltering(interfaceName: string): Promise<void> {
|
async removeFiltering(
|
||||||
|
interfaceName: string,
|
||||||
|
enableIpv6: boolean
|
||||||
|
): Promise<void> {
|
||||||
FW_DEBUG(`Removing firewall filtering for interface ${interfaceName}`);
|
FW_DEBUG(`Removing firewall filtering for interface ${interfaceName}`);
|
||||||
|
|
||||||
// Remove jump rules from FORWARD chain
|
// Remove jump rules from FORWARD chain
|
||||||
await exec(
|
await exec(
|
||||||
`iptables -D FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || true`
|
`iptables -D FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || true`
|
||||||
);
|
);
|
||||||
await exec(
|
if (enableIpv6) {
|
||||||
`ip6tables -D FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || true`
|
await exec(
|
||||||
);
|
`ip6tables -D FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || true`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Flush and delete the chain
|
// Flush and delete the chain
|
||||||
await exec(`iptables -F ${CHAIN_NAME} 2>/dev/null || true`);
|
await exec(`iptables -F ${CHAIN_NAME} 2>/dev/null || true`);
|
||||||
await exec(`ip6tables -F ${CHAIN_NAME} 2>/dev/null || true`);
|
|
||||||
await exec(`iptables -X ${CHAIN_NAME} 2>/dev/null || true`);
|
await exec(`iptables -X ${CHAIN_NAME} 2>/dev/null || true`);
|
||||||
await exec(`ip6tables -X ${CHAIN_NAME} 2>/dev/null || true`);
|
if (enableIpv6) {
|
||||||
|
await exec(`ip6tables -F ${CHAIN_NAME} 2>/dev/null || true`);
|
||||||
|
await exec(`ip6tables -X ${CHAIN_NAME} 2>/dev/null || true`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,45 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import { firewallTestExports } from '#server/utils/firewall';
|
import { exec } from '#server/utils/cmd';
|
||||||
|
import { firewall, firewallTestExports } from '#server/utils/firewall';
|
||||||
import { typesTestExports } from '#server/utils/types';
|
import { typesTestExports } from '#server/utils/types';
|
||||||
|
|
||||||
|
vi.mock('#server/utils/cmd', () => ({
|
||||||
|
exec: vi.fn().mockResolvedValue(''),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const execMock = vi.mocked(exec);
|
||||||
|
|
||||||
describe('firewall', () => {
|
describe('firewall', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
execMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('IPv4-only chain management', () => {
|
||||||
|
test('does not invoke ip6tables when initializing and flushing', async () => {
|
||||||
|
await firewall.initChain('wg0', false);
|
||||||
|
await firewall.flushChain(false);
|
||||||
|
|
||||||
|
expect(execMock).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('iptables -C FORWARD -i wg0')
|
||||||
|
);
|
||||||
|
expect(execMock).not.toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('ip6tables')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not invoke ip6tables when removing filtering', async () => {
|
||||||
|
await firewall.removeFiltering('wg0', false);
|
||||||
|
|
||||||
|
expect(execMock).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('iptables -D FORWARD -i wg0')
|
||||||
|
);
|
||||||
|
expect(execMock).not.toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('ip6tables')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('isValidFirewallEntry', () => {
|
describe('isValidFirewallEntry', () => {
|
||||||
test('invalid ips', () => {
|
test('invalid ips', () => {
|
||||||
expect(() => typesTestExports.FirewallIpEntrySchema.parse('')).toThrow();
|
expect(() => typesTestExports.FirewallIpEntrySchema.parse('')).toThrow();
|
||||||
|
|||||||
Reference in New Issue
Block a user