diff --git a/README.md b/README.md index 6d72c9d6..dadaf945 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ $ docker run -d \ -v ~/.wg-easy:/etc/wireguard \ -p 51820:51820/udp \ -p 51821:51821/tcp \ + --privileged=true \ --cap-add=NET_ADMIN \ --cap-add=SYS_MODULE \ --sysctl="net.ipv4.conf.all.src_valid_mark=1" \ @@ -90,8 +91,10 @@ These options can be configured by setting environment variables using `-e KEY=" | `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will always listen on `51820` inside the Docker container. | | `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. | | `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. | -| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. | -| `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. | +| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IPv4 address range. | +| `WG_DEFAULT_ADDRESS6` | `fd42:42:42::x` | `fd86:ea04:1115::x` | Clients IPv6 address range. | +| `WG_DEFAULT_DNS` | `84.200.69.80` | `84.200.69.80, 84.200.70.40` | DNS server clients will use. | +| `WG_DEFAULT_DNS6` | `2001:1608:10:25::1c04:b12f` | `2001:1608:10:25::1c04:b12f, 2001:1608:10:25::9249:d69b` | DNSv6 server clients will use. | | `WG_ALLOWED_IPS` | `0.0.0.0/0, ::/0` | `192.168.15.0/24, 10.0.1.0/24` | Allowed IPs clients will use. | | `WG_PRE_UP` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L19) for the default value. | | `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. | diff --git a/docker-compose.yml b/docker-compose.yml index d9ddcf7c..57dafe4e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: # - PASSWORD=foobar123 # - WG_PORT=51820 # - WG_DEFAULT_ADDRESS=10.8.0.x + # - WG_DEFAULT_ADDRESS6=fd42:42:42::x # - WG_DEFAULT_DNS=1.1.1.1 # - WG_MTU=1420 # - WG_ALLOWED_IPS=192.168.15.0/24, 10.0.1.0/24 @@ -24,6 +25,7 @@ services: image: ghcr.io/wg-easy/wg-easy container_name: wg-easy + privileged: true # for podman issues volumes: - etc_wireguard:/etc/wireguard ports: @@ -36,3 +38,5 @@ services: sysctls: - net.ipv4.ip_forward=1 - net.ipv4.conf.all.src_valid_mark=1 + - net.ipv6.conf.all.forwarding=1 + - net.ipv6.conf.all.src_valid_mark=1 diff --git a/src/config.js b/src/config.js index 3281523f..54cd7bee 100644 --- a/src/config.js +++ b/src/config.js @@ -13,9 +13,13 @@ module.exports.WG_PORT = process.env.WG_PORT || 51820; module.exports.WG_MTU = process.env.WG_MTU || null; module.exports.WG_PERSISTENT_KEEPALIVE = process.env.WG_PERSISTENT_KEEPALIVE || 0; module.exports.WG_DEFAULT_ADDRESS = process.env.WG_DEFAULT_ADDRESS || '10.8.0.x'; +module.exports.WG_DEFAULT_ADDRESS6 = process.env.WG_DEFAULT_ADDRESS6 || 'fd42:42:42::x'; module.exports.WG_DEFAULT_DNS = typeof process.env.WG_DEFAULT_DNS === 'string' ? process.env.WG_DEFAULT_DNS - : '1.1.1.1'; + : '84.200.69.80'; +module.exports.WG_DEFAULT_DNS6 = typeof process.env.WG_DEFAULT_DNS6 === 'string' + ? process.env.WG_DEFAULT_DNS6 + : '2001:1608:10:25::1c04:b12f'; module.exports.WG_ALLOWED_IPS = process.env.WG_ALLOWED_IPS || '0.0.0.0/0, ::/0'; module.exports.WG_PRE_UP = process.env.WG_PRE_UP || ''; @@ -24,6 +28,10 @@ iptables -t nat -A POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS.replace('x iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; +ip6tables -t nat -A POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS6.replace('x', '0')}/64 -o ${module.exports.WG_DEVICE} -j MASQUERADE; +ip6tables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; +ip6tables -A FORWARD -i wg0 -j ACCEPT; +ip6tables -A FORWARD -o wg0 -j ACCEPT; `.split('\n').join(' '); module.exports.WG_PRE_DOWN = process.env.WG_PRE_DOWN || ''; diff --git a/src/lib/Server.js b/src/lib/Server.js index b92ca7a2..f8e7371a 100644 --- a/src/lib/Server.js +++ b/src/lib/Server.js @@ -168,6 +168,14 @@ module.exports = class Server { const { address } = req.body; return WireGuard.updateClientAddress({ clientId, address }); })) + .put('/api/wireguard/client/:clientId/address6', Util.promisify(async (req, res) => { + const { clientId } = req.params; + if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { + res.end(403); + } + const { address6 } = req.body; + return WireGuard.updateClientAddress({ clientId, address6 }); + })) .listen(PORT, WEBUI_HOST, () => { debug(`Listening on http://${WEBUI_HOST}:${PORT}`); diff --git a/src/lib/Util.js b/src/lib/Util.js index cc6e89c2..5510bd38 100644 --- a/src/lib/Util.js +++ b/src/lib/Util.js @@ -17,6 +17,11 @@ module.exports = class Util { return true; } + static isValidIPv6(str) { + const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/; + return ipv6Regex.test(str); + } + static promisify(fn) { // eslint-disable-next-line func-names return function(req, res) { diff --git a/src/lib/WireGuard.js b/src/lib/WireGuard.js index cadb47e2..7f8b060a 100644 --- a/src/lib/WireGuard.js +++ b/src/lib/WireGuard.js @@ -16,7 +16,9 @@ const { WG_PORT, WG_MTU, WG_DEFAULT_DNS, + WG_DEFAULT_DNS6, WG_DEFAULT_ADDRESS, + WG_DEFAULT_ADDRESS6, WG_PERSISTENT_KEEPALIVE, WG_ALLOWED_IPS, WG_PRE_UP, @@ -46,12 +48,14 @@ module.exports = class WireGuard { log: 'echo ***hidden*** | wg pubkey', }); const address = WG_DEFAULT_ADDRESS.replace('x', '1'); + const address6 = WG_DEFAULT_ADDRESS6.replace('x', '1'); config = { server: { privateKey, publicKey, address, + address6, }, clients: {}, }; @@ -67,10 +71,6 @@ module.exports = class WireGuard { throw err; }); - // await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o eth0 -j MASQUERADE`); - // await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT'); - // await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT'); - // await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT'); await this.__syncConfig(); return config; @@ -94,7 +94,7 @@ module.exports = class WireGuard { # Server [Interface] PrivateKey = ${config.server.privateKey} -Address = ${config.server.address}/24 +Address = ${config.server.address}/24, ${config.server.address6}/64 ListenPort = ${WG_PORT} PreUp = ${WG_PRE_UP} PostUp = ${WG_POST_UP} @@ -111,7 +111,7 @@ PostDown = ${WG_POST_DOWN} [Peer] PublicKey = ${client.publicKey} PresharedKey = ${client.preSharedKey} -AllowedIPs = ${client.address}/32`; +AllowedIPs = ${client.address}/32, ${client.address6}/128`; } debug('Config saving...'); @@ -137,6 +137,7 @@ AllowedIPs = ${client.address}/32`; name: client.name, enabled: client.enabled, address: client.address, + address6: client.address6, publicKey: client.publicKey, createdAt: new Date(client.createdAt), updatedAt: new Date(client.updatedAt), @@ -195,12 +196,15 @@ AllowedIPs = ${client.address}/32`; async getClientConfiguration({ clientId }) { const config = await this.getConfig(); const client = await this.getClient({ clientId }); + const isDnsSet = WG_DEFAULT_DNS || WG_DEFAULT_DNS6; + const dnsServers = [WG_DEFAULT_DNS, WG_DEFAULT_DNS6].filter(item => !!item).join(', ') return ` [Interface] PrivateKey = ${client.privateKey} -Address = ${client.address}/24 -${WG_DEFAULT_DNS ? `DNS = ${WG_DEFAULT_DNS}` : ''} +Address = ${client.address}/24, ${client.address6}/64 +ListenPort = ${WG_PORT} +${isDnsSet ? `DNS = ${dnsServers}` : ''} ${WG_MTU ? `MTU = ${WG_MTU}` : ''} [Peer] @@ -230,7 +234,7 @@ Endpoint = ${WG_HOST}:${WG_PORT}`; const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`); const preSharedKey = await Util.exec('wg genpsk'); - // Calculate next IP + // Calculate next IPv4 let address; for (let i = 2; i < 255; i++) { const client = Object.values(config.clients).find((client) => { @@ -247,12 +251,30 @@ Endpoint = ${WG_HOST}:${WG_PORT}`; throw new Error('Maximum number of clients reached.'); } + // Calculate next IPv6 + let address6; + for (let i = 2; i < 255; i++) { + const client = Object.values(config.clients).find((client) => { + return client.address6 === WG_DEFAULT_ADDRESS6.replace('x', i); + }); + + if (!client) { + address6 = WG_DEFAULT_ADDRESS6.replace('x', i); + break; + } + } + + if (!address6) { + throw new Error('Maximum number of clients reached.'); + } + // Create Client const id = uuid.v4(); const client = { id, name, address, + address6, privateKey, publicKey, preSharedKey, @@ -319,4 +341,17 @@ Endpoint = ${WG_HOST}:${WG_PORT}`; await this.saveConfig(); } + async updateClientaddress6({ clientId, address6 }) { + const client = await this.getClient({ clientId }); + + if (!Util.isValidIPv6(address6)) { + throw new ServerError(`Invalid Address: ${address6}`, 400); + } + + client.address6 = address6; + client.updatedAt = new Date(); + + await this.saveConfig(); + } + }; diff --git a/src/www/index.html b/src/www/index.html index 4b3ba6ef..aa96f993 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -145,7 +145,7 @@
- + @@ -170,6 +170,31 @@ + + + + + + {{client.address6}} + + + + + + + + + ยท diff --git a/src/www/js/api.js b/src/www/js/api.js index 826a7652..d5f0ca9d 100644 --- a/src/www/js/api.js +++ b/src/www/js/api.js @@ -117,4 +117,12 @@ class API { }); } + async updateClientaddress6({ clientId, address6 }) { + return this.call({ + method: 'put', + path: `/wireguard/client/${clientId}/address6/`, + body: { address6 }, + }); + } + } diff --git a/src/www/js/app.js b/src/www/js/app.js index 4a258d95..75469a18 100644 --- a/src/www/js/app.js +++ b/src/www/js/app.js @@ -40,6 +40,8 @@ new Vue({ clientEditNameId: null, clientEditAddress: null, clientEditAddressId: null, + clientEditAddress6: null, + clientEditAddressId6: null, qrcode: null, currentRelease: null, @@ -241,6 +243,11 @@ new Vue({ .catch((err) => alert(err.message || err.toString())) .finally(() => this.refresh().catch(console.error)); }, + updateClientaddress6(client, address6) { + this.api.updateClientaddress6({ clientId: client.id, address6 }) + .catch((err) => alert(err.message || err.toString())) + .finally(() => this.refresh().catch(console.error)); + }, toggleTheme() { if (this.isDark) { localStorage.theme = 'light';