From 0f663df7f6ac6eb283cd7f3a53de19e593b6d01c Mon Sep 17 00:00:00 2001 From: Bernd Storath <32197462+kaaax0815@users.noreply.github.com> Date: Tue, 1 Jul 2025 07:57:14 +0200 Subject: [PATCH] Add option to disable ipv6 (#1951) * add option to disable ipv6 * don't add ipv6 address * update docs --- Dockerfile | 1 + Dockerfile.dev | 1 + .../advanced/config/optional-config.md | 21 ++++++-- src/server/database/sqlite.ts | 51 +++++++++++++++++++ src/server/utils/WireGuard.ts | 16 ++++-- src/server/utils/config.ts | 2 + src/server/utils/wgHelper.ts | 38 +++++++++++--- 7 files changed, 116 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 71a85257..236cb4eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,6 +54,7 @@ ENV PORT=51821 ENV HOST=0.0.0.0 ENV INSECURE=false ENV INIT_ENABLED=false +ENV DISABLE_IPV6=false LABEL org.opencontainers.image.source=https://github.com/wg-easy/wg-easy diff --git a/Dockerfile.dev b/Dockerfile.dev index d130ad49..d0857ad8 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -28,6 +28,7 @@ ENV PORT=51821 ENV HOST=0.0.0.0 ENV INSECURE=true ENV INIT_ENABLED=false +ENV DISABLE_IPV6=false # Install Dependencies COPY src/package.json src/pnpm-lock.yaml ./ diff --git a/docs/content/advanced/config/optional-config.md b/docs/content/advanced/config/optional-config.md index 34d94db7..c58f5636 100644 --- a/docs/content/advanced/config/optional-config.md +++ b/docs/content/advanced/config/optional-config.md @@ -4,8 +4,19 @@ title: Optional Configuration You can set these environment variables to configure the container. They are not required, but can be useful in some cases. -| Env | Default | Example | Description | -| ---------- | --------- | ----------- | ------------------------------ | -| `PORT` | `51821` | `6789` | TCP port for Web UI. | -| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. | -| `INSECURE` | `false` | `true` | If access over http is allowed | +| Env | Default | Example | Description | +| -------------- | --------- | ----------- | ---------------------------------- | +| `PORT` | `51821` | `6789` | TCP port for Web UI. | +| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. | +| `INSECURE` | `false` | `true` | If access over http is allowed | +| `DISABLE_IPV6` | `false` | `true` | If IPv6 support should be disabled | + +/// note | IPv6 Caveats + +Disabling IPv6 will disable the creation of the default IPv6 firewall rules and won't add a IPv6 address to the interface and clients. + +You will however still see a IPv6 address in the Web UI, but it won't be used. + +This option can be removed in the future, as more devices support IPv6. + +/// diff --git a/src/server/database/sqlite.ts b/src/server/database/sqlite.ts index dbfade9b..c09523c7 100644 --- a/src/server/database/sqlite.ts +++ b/src/server/database/sqlite.ts @@ -2,6 +2,7 @@ import { drizzle } from 'drizzle-orm/libsql'; import { migrate as drizzleMigrate } from 'drizzle-orm/libsql/migrator'; import { createClient } from '@libsql/client'; import debug from 'debug'; +import { eq } from 'drizzle-orm'; import * as schema from './schema'; import { ClientService } from './repositories/client/service'; @@ -25,6 +26,11 @@ export async function connect() { await initialSetup(dbService); } + if (WG_ENV.DISABLE_IPV6) { + DB_DEBUG('Warning: Disabling IPv6...'); + await disableIpv6(db); + } + return dbService; } @@ -108,3 +114,48 @@ async function initialSetup(db: DBServiceType) { await db.general.setSetupStep(0); } } + +async function disableIpv6(db: DBType) { + // This should match the initial value migration + const postUpMatch = + ' ip6tables -t nat -A POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT;'; + const postDownMatch = + ' ip6tables -t nat -D POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT;'; + + await db.transaction(async (tx) => { + const hooks = await tx.query.hooks.findFirst({ + where: eq(schema.hooks.id, 'wg0'), + }); + + if (!hooks) { + throw new Error('Hooks not found'); + } + + if (hooks.postUp.includes(postUpMatch)) { + DB_DEBUG('Disabling IPv6 in Post Up hooks...'); + await tx + .update(schema.hooks) + .set({ + postUp: hooks.postUp.replace(postUpMatch, ''), + postDown: hooks.postDown.replace(postDownMatch, ''), + }) + .where(eq(schema.hooks.id, 'wg0')) + .execute(); + } else { + DB_DEBUG('IPv6 Post Up hooks already disabled, skipping...'); + } + if (hooks.postDown.includes(postDownMatch)) { + DB_DEBUG('Disabling IPv6 in Post Down hooks...'); + await tx + .update(schema.hooks) + .set({ + postUp: hooks.postUp.replace(postUpMatch, ''), + postDown: hooks.postDown.replace(postDownMatch, ''), + }) + .where(eq(schema.hooks.id, 'wg0')) + .execute(); + } else { + DB_DEBUG('IPv6 Post Down hooks already disabled, skipping...'); + } + }); +} diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index a44a3b11..0cf44006 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -25,13 +25,21 @@ class WireGuard { const hooks = await Database.hooks.get(); const result = []; - result.push(wg.generateServerInterface(wgInterface, hooks)); + result.push( + wg.generateServerInterface(wgInterface, hooks, { + enableIpv6: !WG_ENV.DISABLE_IPV6, + }) + ); for (const client of clients) { if (!client.enabled) { continue; } - result.push(wg.generateServerPeer(client)); + result.push( + wg.generateServerPeer(client, { + enableIpv6: !WG_ENV.DISABLE_IPV6, + }) + ); } result.push(''); @@ -125,7 +133,9 @@ class WireGuard { throw new Error('Client not found'); } - return wg.generateClientConfig(wgInterface, userConfig, client); + return wg.generateClientConfig(wgInterface, userConfig, client, { + enableIpv6: !WG_ENV.DISABLE_IPV6, + }); } async getClientQRCodeSVG({ clientId }: { clientId: ID }) { diff --git a/src/server/utils/config.ts b/src/server/utils/config.ts index f6e89242..886c0a28 100644 --- a/src/server/utils/config.ts +++ b/src/server/utils/config.ts @@ -17,6 +17,8 @@ export const WG_ENV = { INSECURE: process.env.INSECURE === 'true', /** Port the UI is listening on */ PORT: assertEnv('PORT'), + /** If IPv6 should be disabled */ + DISABLE_IPV6: process.env.DISABLE_IPV6 === 'true', }; export const WG_INITIAL_ENV = { diff --git a/src/server/utils/wgHelper.ts b/src/server/utils/wgHelper.ts index 7f1c44ec..6d096e99 100644 --- a/src/server/utils/wgHelper.ts +++ b/src/server/utils/wgHelper.ts @@ -5,11 +5,20 @@ import type { InterfaceType } from '#db/repositories/interface/types'; import type { UserConfigType } from '#db/repositories/userConfig/types'; import type { HooksType } from '#db/repositories/hooks/types'; +type Options = { + enableIpv6?: boolean; +}; + export const wg = { - generateServerPeer: (client: Omit) => { + generateServerPeer: ( + client: Omit, + options: Options = {} + ) => { + const { enableIpv6 = true } = options; + const allowedIps = [ `${client.ipv4Address}/32`, - `${client.ipv6Address}/128`, + ...(enableIpv6 ? [`${client.ipv6Address}/128`] : []), ...(client.serverAllowedIps ?? []), ]; @@ -25,19 +34,29 @@ PresharedKey = ${client.preSharedKey} AllowedIPs = ${allowedIps.join(', ')}${extraLines.length ? `\n${extraLines.join('\n')}` : ''}`; }, - generateServerInterface: (wgInterface: InterfaceType, hooks: HooksType) => { + generateServerInterface: ( + wgInterface: InterfaceType, + hooks: HooksType, + options: Options = {} + ) => { + const { enableIpv6 = true } = options; + const cidr4 = parseCidr(wgInterface.ipv4Cidr); const cidr6 = parseCidr(wgInterface.ipv6Cidr); const ipv4Addr = stringifyIp({ number: cidr4.start + 1n, version: 4 }); const ipv6Addr = stringifyIp({ number: cidr6.start + 1n, version: 6 }); + const address = + `${ipv4Addr}/${cidr4.prefix}` + + (enableIpv6 ? `, ${ipv6Addr}/${cidr6.prefix}` : ''); + return `# Note: Do not edit this file directly. # Your changes will be overwritten! # Server [Interface] PrivateKey = ${wgInterface.privateKey} -Address = ${ipv4Addr}/${cidr4.prefix}, ${ipv6Addr}/${cidr6.prefix} +Address = ${address} ListenPort = ${wgInterface.port} MTU = ${wgInterface.mtu} PreUp = ${iptablesTemplate(hooks.preUp, wgInterface)} @@ -49,11 +68,18 @@ PostDown = ${iptablesTemplate(hooks.postDown, wgInterface)}`; generateClientConfig: ( wgInterface: InterfaceType, userConfig: UserConfigType, - client: ClientType + client: ClientType, + options: Options = {} ) => { + const { enableIpv6 = true } = options; + const cidr4Block = parseCidr(wgInterface.ipv4Cidr).prefix; const cidr6Block = parseCidr(wgInterface.ipv6Cidr).prefix; + const address = + `${client.ipv4Address}/${cidr4Block}` + + (enableIpv6 ? `, ${client.ipv6Address}/${cidr6Block}` : ''); + const hookLines = [ client.preUp ? `PreUp = ${client.preUp}` : null, client.postUp ? `PostUp = ${client.postUp}` : null, @@ -63,7 +89,7 @@ PostDown = ${iptablesTemplate(hooks.postDown, wgInterface)}`; return `[Interface] PrivateKey = ${client.privateKey} -Address = ${client.ipv4Address}/${cidr4Block}, ${client.ipv6Address}/${cidr6Block} +Address = ${address} DNS = ${(client.dns ?? userConfig.defaultDns).join(', ')} MTU = ${client.mtu} ${hookLines.length ? `${hookLines.join('\n')}\n` : ''}