Browse Source

Add support ipv6

pull/708/head
Astound 3 years ago
parent
commit
f463cffa0f
No known key found for this signature in database GPG Key ID: 97504AF0027B1A56
  1. 7
      README.md
  2. 4
      docker-compose.yml
  3. 10
      src/config.js
  4. 8
      src/lib/Server.js
  5. 5
      src/lib/Util.js
  6. 53
      src/lib/WireGuard.js
  7. 27
      src/www/index.html
  8. 8
      src/www/js/api.js
  9. 7
      src/www/js/app.js

7
README.md

@ -56,6 +56,7 @@ $ docker run -d \
-v ~/.wg-easy:/etc/wireguard \ -v ~/.wg-easy:/etc/wireguard \
-p 51820:51820/udp \ -p 51820:51820/udp \
-p 51821:51821/tcp \ -p 51821:51821/tcp \
--privileged=true \
--cap-add=NET_ADMIN \ --cap-add=NET_ADMIN \
--cap-add=SYS_MODULE \ --cap-add=SYS_MODULE \
--sysctl="net.ipv4.conf.all.src_valid_mark=1" \ --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_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_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_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_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IPv4 address range. |
| `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. | | `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_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_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. | | `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. |

4
docker-compose.yml

@ -13,6 +13,7 @@ services:
# - PASSWORD=foobar123 # - PASSWORD=foobar123
# - WG_PORT=51820 # - WG_PORT=51820
# - WG_DEFAULT_ADDRESS=10.8.0.x # - WG_DEFAULT_ADDRESS=10.8.0.x
# - WG_DEFAULT_ADDRESS6=fd42:42:42::x
# - WG_DEFAULT_DNS=1.1.1.1 # - WG_DEFAULT_DNS=1.1.1.1
# - WG_MTU=1420 # - WG_MTU=1420
# - WG_ALLOWED_IPS=192.168.15.0/24, 10.0.1.0/24 # - 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 image: ghcr.io/wg-easy/wg-easy
container_name: wg-easy container_name: wg-easy
privileged: true # for podman issues
volumes: volumes:
- etc_wireguard:/etc/wireguard - etc_wireguard:/etc/wireguard
ports: ports:
@ -36,3 +38,5 @@ services:
sysctls: sysctls:
- net.ipv4.ip_forward=1 - net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1 - net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.all.src_valid_mark=1

10
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_MTU = process.env.WG_MTU || null;
module.exports.WG_PERSISTENT_KEEPALIVE = process.env.WG_PERSISTENT_KEEPALIVE || 0; 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_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' module.exports.WG_DEFAULT_DNS = typeof process.env.WG_DEFAULT_DNS === 'string'
? process.env.WG_DEFAULT_DNS ? 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_ALLOWED_IPS = process.env.WG_ALLOWED_IPS || '0.0.0.0/0, ::/0';
module.exports.WG_PRE_UP = process.env.WG_PRE_UP || ''; 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 INPUT -p udp -m udp --dport 51820 -j ACCEPT;
iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT;
iptables -A FORWARD -o 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(' '); `.split('\n').join(' ');
module.exports.WG_PRE_DOWN = process.env.WG_PRE_DOWN || ''; module.exports.WG_PRE_DOWN = process.env.WG_PRE_DOWN || '';

8
src/lib/Server.js

@ -168,6 +168,14 @@ module.exports = class Server {
const { address } = req.body; const { address } = req.body;
return WireGuard.updateClientAddress({ clientId, address }); 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, () => { .listen(PORT, WEBUI_HOST, () => {
debug(`Listening on http://${WEBUI_HOST}:${PORT}`); debug(`Listening on http://${WEBUI_HOST}:${PORT}`);

5
src/lib/Util.js

@ -17,6 +17,11 @@ module.exports = class Util {
return true; 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) { static promisify(fn) {
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
return function(req, res) { return function(req, res) {

53
src/lib/WireGuard.js

@ -16,7 +16,9 @@ const {
WG_PORT, WG_PORT,
WG_MTU, WG_MTU,
WG_DEFAULT_DNS, WG_DEFAULT_DNS,
WG_DEFAULT_DNS6,
WG_DEFAULT_ADDRESS, WG_DEFAULT_ADDRESS,
WG_DEFAULT_ADDRESS6,
WG_PERSISTENT_KEEPALIVE, WG_PERSISTENT_KEEPALIVE,
WG_ALLOWED_IPS, WG_ALLOWED_IPS,
WG_PRE_UP, WG_PRE_UP,
@ -46,12 +48,14 @@ module.exports = class WireGuard {
log: 'echo ***hidden*** | wg pubkey', log: 'echo ***hidden*** | wg pubkey',
}); });
const address = WG_DEFAULT_ADDRESS.replace('x', '1'); const address = WG_DEFAULT_ADDRESS.replace('x', '1');
const address6 = WG_DEFAULT_ADDRESS6.replace('x', '1');
config = { config = {
server: { server: {
privateKey, privateKey,
publicKey, publicKey,
address, address,
address6,
}, },
clients: {}, clients: {},
}; };
@ -67,10 +71,6 @@ module.exports = class WireGuard {
throw err; 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(); await this.__syncConfig();
return config; return config;
@ -94,7 +94,7 @@ module.exports = class WireGuard {
# Server # Server
[Interface] [Interface]
PrivateKey = ${config.server.privateKey} PrivateKey = ${config.server.privateKey}
Address = ${config.server.address}/24 Address = ${config.server.address}/24, ${config.server.address6}/64
ListenPort = ${WG_PORT} ListenPort = ${WG_PORT}
PreUp = ${WG_PRE_UP} PreUp = ${WG_PRE_UP}
PostUp = ${WG_POST_UP} PostUp = ${WG_POST_UP}
@ -111,7 +111,7 @@ PostDown = ${WG_POST_DOWN}
[Peer] [Peer]
PublicKey = ${client.publicKey} PublicKey = ${client.publicKey}
PresharedKey = ${client.preSharedKey} PresharedKey = ${client.preSharedKey}
AllowedIPs = ${client.address}/32`; AllowedIPs = ${client.address}/32, ${client.address6}/128`;
} }
debug('Config saving...'); debug('Config saving...');
@ -137,6 +137,7 @@ AllowedIPs = ${client.address}/32`;
name: client.name, name: client.name,
enabled: client.enabled, enabled: client.enabled,
address: client.address, address: client.address,
address6: client.address6,
publicKey: client.publicKey, publicKey: client.publicKey,
createdAt: new Date(client.createdAt), createdAt: new Date(client.createdAt),
updatedAt: new Date(client.updatedAt), updatedAt: new Date(client.updatedAt),
@ -195,12 +196,15 @@ AllowedIPs = ${client.address}/32`;
async getClientConfiguration({ clientId }) { async getClientConfiguration({ clientId }) {
const config = await this.getConfig(); const config = await this.getConfig();
const client = await this.getClient({ clientId }); 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 ` return `
[Interface] [Interface]
PrivateKey = ${client.privateKey} PrivateKey = ${client.privateKey}
Address = ${client.address}/24 Address = ${client.address}/24, ${client.address6}/64
${WG_DEFAULT_DNS ? `DNS = ${WG_DEFAULT_DNS}` : ''} ListenPort = ${WG_PORT}
${isDnsSet ? `DNS = ${dnsServers}` : ''}
${WG_MTU ? `MTU = ${WG_MTU}` : ''} ${WG_MTU ? `MTU = ${WG_MTU}` : ''}
[Peer] [Peer]
@ -230,7 +234,7 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`); const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`);
const preSharedKey = await Util.exec('wg genpsk'); const preSharedKey = await Util.exec('wg genpsk');
// Calculate next IP // Calculate next IPv4
let address; let address;
for (let i = 2; i < 255; i++) { for (let i = 2; i < 255; i++) {
const client = Object.values(config.clients).find((client) => { 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.'); 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 // Create Client
const id = uuid.v4(); const id = uuid.v4();
const client = { const client = {
id, id,
name, name,
address, address,
address6,
privateKey, privateKey,
publicKey, publicKey,
preSharedKey, preSharedKey,
@ -319,4 +341,17 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
await this.saveConfig(); 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();
}
}; };

27
src/www/index.html

@ -145,7 +145,7 @@
<!-- Info --> <!-- Info -->
<div class="text-gray-400 dark:text-neutral-400 text-xs"> <div class="text-gray-400 dark:text-neutral-400 text-xs">
<!-- Address --> <!-- Address IPv4 -->
<span class="group block md:inline-block pb-1 md:pb-0"> <span class="group block md:inline-block pb-1 md:pb-0">
<!-- Show --> <!-- Show -->
@ -170,6 +170,31 @@
</span> </span>
</span> </span>
<!-- Address IPv6 -->
<span class="group block md:inline-block pb-1 md:pb-0">
<!-- Show -->
<input v-show="clientEditAddressId6 === client.id" v-model="clientEditAddress6"
v-on:keyup.enter="updateClientaddress6(client, clientEditAddress6); clientEditAddress6 = null; clientEditAddressId6 = null;"
v-on:keyup.escape="clientEditAddress6 = null; clientEditAddressId6 = null;"
:ref="'client-' + client.id + '-address'"
class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-20 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500" />
<span v-show="clientEditAddressId6 !== client.id"
class="inline-block border-t-2 border-b-2 border-transparent">{{client.address6}}</span>
<!-- Edit -->
<span v-show="clientEditAddressId6 !== client.id"
@click="clientEditAddress6 = client.address6; clientEditAddressId6 = client.id; setTimeout(() => $refs['client-' + client.id + '-address'][0].select(), 1);"
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity">
<svg xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</span>
</span>
<!-- Transfer TX --> <!-- Transfer TX -->
<span v-if="client.transferTx" :title="'Total Download: ' + bytes(client.transferTx)"> <span v-if="client.transferTx" :title="'Total Download: ' + bytes(client.transferTx)">
· ·

8
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 },
});
}
} }

7
src/www/js/app.js

@ -40,6 +40,8 @@ new Vue({
clientEditNameId: null, clientEditNameId: null,
clientEditAddress: null, clientEditAddress: null,
clientEditAddressId: null, clientEditAddressId: null,
clientEditAddress6: null,
clientEditAddressId6: null,
qrcode: null, qrcode: null,
currentRelease: null, currentRelease: null,
@ -241,6 +243,11 @@ new Vue({
.catch((err) => alert(err.message || err.toString())) .catch((err) => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error)); .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() { toggleTheme() {
if (this.isDark) { if (this.isDark) {
localStorage.theme = 'light'; localStorage.theme = 'light';

Loading…
Cancel
Save