From 58813ffbe4797012b1bce469940858c500b52b03 Mon Sep 17 00:00:00 2001
From: Bernd Storath <32197462+kaaax0815@users.noreply.github.com>
Date: Thu, 5 Sep 2024 11:24:39 +0200
Subject: [PATCH] Feat: IPv6 (#1354)
* start supporting ipv6
* add ipv6 support
* build server with es2020
es2019 doesn't support bigint
* fix issues, better naming
---
Dockerfile | 2 +
Dockerfile.dev | 2 +
docker-compose.yml | 3 +
src/app/components/Client/Address.vue | 60 ------------
src/app/components/Client/Address4.vue | 60 ++++++++++++
src/app/components/Client/Client.vue | 2 +-
src/app/utils/api.ts | 10 +-
src/nuxt.config.ts | 7 ++
src/package.json | 5 +-
src/pnpm-lock.yaml | 96 +++++++++++++++----
src/server/api/release.get.ts | 3 +-
src/server/api/session.post.ts | 2 +-
.../{address.put.ts => address4.put.ts} | 7 +-
src/server/utils/WireGuard.ts | 93 +++++++++++-------
src/server/utils/config.ts | 3 +
src/server/utils/types.ts | 8 +-
src/services/database/lowdb.ts | 13 +--
src/services/database/migrations/1.ts | 35 ++++---
src/services/database/repositories/client.ts | 5 +-
.../database/repositories/database.ts | 2 +-
src/services/database/repositories/system.ts | 8 +-
src/services/database/repositories/user.ts | 6 +-
22 files changed, 274 insertions(+), 158 deletions(-)
delete mode 100644 src/app/components/Client/Address.vue
create mode 100644 src/app/components/Client/Address4.vue
rename src/server/api/wireguard/client/[clientId]/{address.put.ts => address4.put.ts} (54%)
diff --git a/Dockerfile b/Dockerfile
index 966632b0..8b09d381 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,11 +27,13 @@ RUN apk add --no-cache \
dpkg \
dumb-init \
iptables \
+ ip6tables \
iptables-legacy \
wireguard-tools
# Use iptables-legacy
RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save
+RUN update-alternatives --install /sbin/ip6tables ip6tables /sbin/ip6tables-legacy 10 --slave /sbin/ip6tables-restore ip6tables-restore /sbin/ip6tables-legacy-restore --slave /sbin/ip6tables-save ip6tables-save /sbin/ip6tables-legacy-save
# Set Environment
ENV DEBUG=Server,WireGuard,LowDB
diff --git a/Dockerfile.dev b/Dockerfile.dev
index fed4ec47..9be2832b 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -17,11 +17,13 @@ RUN apk add --no-cache \
dpkg \
dumb-init \
iptables \
+ ip6tables \
iptables-legacy \
wireguard-tools
# Use iptables-legacy
RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save
+RUN update-alternatives --install /sbin/ip6tables ip6tables /sbin/ip6tables-legacy 10 --slave /sbin/ip6tables-restore ip6tables-restore /sbin/ip6tables-legacy-restore --slave /sbin/ip6tables-save ip6tables-save /sbin/ip6tables-legacy-save
# Set Environment
ENV DEBUG=Server,WireGuard,LowDB
diff --git a/docker-compose.yml b/docker-compose.yml
index f607837b..c5a6b346 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -24,3 +24,6 @@ services:
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
+ - net.ipv6.conf.all.disable_ipv6=0
+ - net.ipv6.conf.all.forwarding=1
+ - net.ipv6.conf.default.forwarding=1
diff --git a/src/app/components/Client/Address.vue b/src/app/components/Client/Address.vue
deleted file mode 100644
index ca3e8dc4..00000000
--- a/src/app/components/Client/Address.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
- {{
- client.address
- }}
-
-
- clientAddressInput?.select());
- "
- >
-
-
-
-
-
-
diff --git a/src/app/components/Client/Address4.vue b/src/app/components/Client/Address4.vue
new file mode 100644
index 00000000..e4cdfde5
--- /dev/null
+++ b/src/app/components/Client/Address4.vue
@@ -0,0 +1,60 @@
+
+
+
+
+ {{
+ client.address4
+ }}
+
+
+ clientAddress4Input?.select());
+ "
+ >
+
+
+
+
+
+
diff --git a/src/app/components/Client/Client.vue b/src/app/components/Client/Client.vue
index 71bd6b4c..c1a9295e 100644
--- a/src/app/components/Client/Client.vue
+++ b/src/app/components/Client/Client.vue
@@ -12,7 +12,7 @@
-
+
=8'}
+ cidr-tools@11.0.2:
+ resolution: {integrity: sha512-OLeM9EOXybbhMsGGBNRLCMjn8e+wFOXARIShF/sZwmJLsxWywqfE0By4BMftT6BFWpbcETWpW7TfM2KGCtrZDg==}
+ engines: {node: '>=18'}
+
citty@0.1.6:
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
@@ -1699,6 +1703,10 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
+ clone-regexp@3.0.0:
+ resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==}
+ engines: {node: '>=12'}
+
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
@@ -1784,6 +1792,10 @@ packages:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
+ convert-hrtime@5.0.0:
+ resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==}
+ engines: {node: '>=12'}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@@ -2338,6 +2350,10 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ function-timeout@0.1.1:
+ resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==}
+ engines: {node: '>=14.16'}
+
gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'}
@@ -2555,8 +2571,13 @@ packages:
resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==}
engines: {node: '>=12.22.0'}
- ip@2.0.1:
- resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==}
+ ip-bigint@8.2.0:
+ resolution: {integrity: sha512-46EAEKzGNxojH5JaGEeCix49tL4h1W8ia5mhogZ68HroVAfyLj1E+SFFid4GuyK0mdIKjwcAITLqwg1wlkx2iQ==}
+ engines: {node: '>=18'}
+
+ ip-regex@5.0.0:
+ resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
iron-webcrypto@1.2.1:
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
@@ -2614,6 +2635,10 @@ packages:
resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==}
engines: {node: '>=18'}
+ is-ip@5.0.1:
+ resolution: {integrity: sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==}
+ engines: {node: '>=14.16'}
+
is-module@1.0.0:
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
@@ -2632,6 +2657,10 @@ packages:
is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
+ is-regexp@3.1.0:
+ resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==}
+ engines: {node: '>=12'}
+
is-ssh@1.4.0:
resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==}
@@ -3840,6 +3869,10 @@ packages:
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
+ super-regex@0.2.0:
+ resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==}
+ engines: {node: '>=14.16'}
+
superjson@2.2.1:
resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==}
engines: {node: '>=16'}
@@ -3948,6 +3981,10 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+ time-span@5.1.0:
+ resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
+ engines: {node: '>=12'}
+
timeago.js@4.0.2:
resolution: {integrity: sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==}
@@ -5631,10 +5668,6 @@ snapshots:
dependencies:
'@types/node': 22.5.2
- '@types/ip@1.1.3':
- dependencies:
- '@types/node': 22.5.2
-
'@types/json-schema@7.0.15': {}
'@types/ms@0.7.34': {}
@@ -6211,6 +6244,10 @@ snapshots:
ci-info@4.0.0: {}
+ cidr-tools@11.0.2:
+ dependencies:
+ ip-bigint: 8.2.0
+
citty@0.1.6:
dependencies:
consola: 3.2.3
@@ -6239,6 +6276,10 @@ snapshots:
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
+ clone-regexp@3.0.0:
+ dependencies:
+ is-regexp: 3.1.0
+
cluster-key-slot@1.1.2: {}
co@4.6.0: {}
@@ -6299,6 +6340,8 @@ snapshots:
content-type@1.0.5: {}
+ convert-hrtime@5.0.0: {}
+
convert-source-map@2.0.0: {}
cookie-es@1.2.2: {}
@@ -6948,6 +6991,8 @@ snapshots:
function-bind@1.1.2: {}
+ function-timeout@0.1.1: {}
+
gauge@3.0.2:
dependencies:
aproba: 2.0.0
@@ -7187,7 +7232,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
- ip@2.0.1: {}
+ ip-bigint@8.2.0: {}
+
+ ip-regex@5.0.0: {}
iron-webcrypto@1.2.1: {}
@@ -7232,6 +7279,11 @@ snapshots:
global-directory: 4.0.1
is-path-inside: 4.0.0
+ is-ip@5.0.1:
+ dependencies:
+ ip-regex: 5.0.0
+ super-regex: 0.2.0
+
is-module@1.0.0: {}
is-number@7.0.0: {}
@@ -7244,6 +7296,8 @@ snapshots:
dependencies:
'@types/estree': 1.0.5
+ is-regexp@3.1.0: {}
+
is-ssh@1.4.0:
dependencies:
protocols: 2.0.1
@@ -8593,6 +8647,12 @@ snapshots:
pirates: 4.0.6
ts-interface-checker: 0.1.13
+ super-regex@0.2.0:
+ dependencies:
+ clone-regexp: 3.0.0
+ function-timeout: 0.1.1
+ time-span: 5.1.0
+
superjson@2.2.1:
dependencies:
copy-anything: 3.0.5
@@ -8738,6 +8798,10 @@ snapshots:
dependencies:
any-promise: 1.3.0
+ time-span@5.1.0:
+ dependencies:
+ convert-hrtime: 5.0.0
+
timeago.js@4.0.2: {}
tiny-invariant@1.3.3: {}
diff --git a/src/server/api/release.get.ts b/src/server/api/release.get.ts
index ebca8838..a7c3f02f 100644
--- a/src/server/api/release.get.ts
+++ b/src/server/api/release.get.ts
@@ -1,8 +1,7 @@
export default defineEventHandler(async () => {
- const system = await Database.getSystem();
const latestRelease = await fetchLatestRelease();
return {
- currentRelease: system.release,
+ currentRelease: RELEASE,
latestRelease: latestRelease,
};
});
diff --git a/src/server/api/session.post.ts b/src/server/api/session.post.ts
index c4a9b7e3..0eb77690 100644
--- a/src/server/api/session.post.ts
+++ b/src/server/api/session.post.ts
@@ -29,7 +29,7 @@ export default defineEventHandler(async (event) => {
if (remember) {
conf.cookie = {
...(system.sessionConfig.cookie ?? {}),
- maxAge: system.cookieMaxAge * 60,
+ maxAge: system.sessionTimeout,
};
}
diff --git a/src/server/api/wireguard/client/[clientId]/address.put.ts b/src/server/api/wireguard/client/[clientId]/address4.put.ts
similarity index 54%
rename from src/server/api/wireguard/client/[clientId]/address.put.ts
rename to src/server/api/wireguard/client/[clientId]/address4.put.ts
index eb834521..b646d4fe 100644
--- a/src/server/api/wireguard/client/[clientId]/address.put.ts
+++ b/src/server/api/wireguard/client/[clientId]/address4.put.ts
@@ -3,7 +3,10 @@ export default defineEventHandler(async (event) => {
event,
validateZod(clientIdType)
);
- const { address } = await readValidatedBody(event, validateZod(addressType));
- await WireGuard.updateClientAddress({ clientId, address });
+ const { address4 } = await readValidatedBody(
+ event,
+ validateZod(address4Type)
+ );
+ await WireGuard.updateClientAddress({ clientId, address4 });
return { success: true };
});
diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts
index 85f6462f..99093c0d 100644
--- a/src/server/utils/WireGuard.ts
+++ b/src/server/utils/WireGuard.ts
@@ -6,7 +6,9 @@ import QRCode from 'qrcode';
import CRC32 from 'crc-32';
import type { NewClient } from '~~/services/database/repositories/client';
-import ip from 'ip';
+import { parseCidr } from 'cidr-tools';
+import { stringifyIp } from 'ip-bigint';
+import { isIPv4 } from 'is-ip';
const DEBUG = debug('WireGuard');
@@ -19,9 +21,8 @@ class WireGuard {
async #saveWireguardConfig() {
const system = await Database.getSystem();
const clients = await Database.getClients();
- const cidrBlock = ip.cidrSubnet(
- system.userConfig.addressRange
- ).subnetMaskLength;
+ const cidr4Block = parseCidr(system.userConfig.address4Range).prefix;
+ const cidr6Block = parseCidr(system.userConfig.address6Range).prefix;
let result = `
# Note: Do not edit this file directly.
# Your changes will be overwritten!
@@ -29,7 +30,7 @@ class WireGuard {
# Server
[Interface]
PrivateKey = ${system.interface.privateKey}
-Address = ${system.interface.address}/${cidrBlock}
+Address = ${system.interface.address4}/${cidr4Block}, ${system.interface.address6}/${cidr6Block}
ListenPort = ${system.wgPort}
PreUp = ${system.iptables.PreUp}
PostUp = ${system.iptables.PostUp}
@@ -46,7 +47,7 @@ PostDown = ${system.iptables.PostDown}
[Peer]
PublicKey = ${client.publicKey}
PresharedKey = ${client.preSharedKey}
-AllowedIPs = ${client.address}/32`;
+AllowedIPs = ${client.address4}/32, ${client.address6}/128`;
}
DEBUG('Config saving...');
@@ -68,7 +69,8 @@ AllowedIPs = ${client.address}/32`;
id: clientId,
name: client.name,
enabled: client.enabled,
- address: client.address,
+ address4: client.address4,
+ address6: client.address6,
publicKey: client.publicKey,
createdAt: new Date(client.createdAt),
updatedAt: new Date(client.updatedAt),
@@ -134,18 +136,20 @@ AllowedIPs = ${client.address}/32`;
async getClientConfiguration({ clientId }: { clientId: string }) {
const system = await Database.getSystem();
const client = await this.getClient({ clientId });
+ const cidr4Block = parseCidr(system.userConfig.address4Range).prefix;
+ const cidr6Block = parseCidr(system.userConfig.address6Range).prefix;
return `
[Interface]
PrivateKey = ${client.privateKey}
-Address = ${client.address}
-DNS = ${system.userConfig.defaultDns.join(',')}
+Address = ${client.address4}/${cidr4Block}, ${client.address6}/${cidr6Block}
+DNS = ${system.userConfig.defaultDns.join(', ')}
MTU = ${system.userConfig.mtu}
[Peer]
PublicKey = ${system.interface.publicKey}
PresharedKey = ${client.preSharedKey}
-AllowedIPs = ${client.allowedIPs}
+AllowedIPs = ${client.allowedIPs.join(', ')}
PersistentKeepalive = ${client.persistentKeepalive}
Endpoint = ${system.wgHost}:${system.wgConfigPort}`;
}
@@ -165,10 +169,6 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`;
name: string;
expireDate: string | null;
}) {
- if (!name) {
- throw new Error('Missing: Name');
- }
-
const system = await Database.getSystem();
const clients = await Database.getClients();
@@ -179,26 +179,48 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`;
const preSharedKey = await exec('wg genpsk');
// Calculate next IP
- const cidr = ip.cidrSubnet(system.userConfig.addressRange);
- let address;
- for (
- let i = ip.toLong(cidr.firstAddress) + 1;
- i <= ip.toLong(cidr.lastAddress) - 1;
- i++
- ) {
- const currentIp = ip.fromLong(i);
+ const cidr4 = parseCidr(system.userConfig.address4Range);
+ let address4;
+ for (let i = cidr4.start + 2n; i <= cidr4.end - 1n; i++) {
+ const currentIp4 = stringifyIp({ number: i, version: 4 });
+ const client = Object.values(clients).find((client) => {
+ return client.address4 === currentIp4;
+ });
+
+ if (!client) {
+ address4 = currentIp4;
+ break;
+ }
+ }
+
+ if (!address4) {
+ throw createError({
+ statusCode: 409,
+ statusMessage: 'Maximum number of clients reached.',
+ data: { cause: 'IPv4 Address Pool exhausted' },
+ });
+ }
+
+ const cidr6 = parseCidr(system.userConfig.address6Range);
+ let address6;
+ for (let i = cidr6.start + 2n; i <= cidr6.end - 1n; i++) {
+ const currentIp6 = stringifyIp({ number: i, version: 6 });
const client = Object.values(clients).find((client) => {
- return client.address === currentIp;
+ return client.address6 === currentIp6;
});
if (!client) {
- address = currentIp;
+ address6 = currentIp6;
break;
}
}
- if (!address) {
- throw new Error('Maximum number of clients reached.');
+ if (!address6) {
+ throw createError({
+ statusCode: 409,
+ statusMessage: 'Maximum number of clients reached.',
+ data: { cause: 'IPv6 Address Pool exhausted' },
+ });
}
// Create Client
@@ -207,7 +229,8 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`;
const client: NewClient = {
id,
name,
- address,
+ address4,
+ address6,
privateKey,
publicKey,
preSharedKey,
@@ -281,19 +304,19 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`;
async updateClientAddress({
clientId,
- address,
+ address4,
}: {
clientId: string;
- address: string;
+ address4: string;
}) {
- if (!ip.isV4Format(address)) {
+ if (!isIPv4(address4)) {
throw createError({
statusCode: 400,
- statusMessage: `Invalid Address: ${address}`,
+ statusMessage: `Invalid Address: ${address4}`,
});
}
- await Database.updateClientAddress(clientId, address);
+ await Database.updateClientAddress4(clientId, address4);
await this.saveConfig();
}
@@ -433,9 +456,9 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`;
if (client.endpoint !== null) {
wireguardConnectedPeersCount++;
}
- wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferTx)}\n`;
- wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferRx)}\n`;
- wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`;
+ wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${Number(client.transferTx)}\n`;
+ wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${Number(client.transferRx)}\n`;
+ wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`;
}
let returnText = '# HELP wg-easy and wireguard metrics\n';
diff --git a/src/server/utils/config.ts b/src/server/utils/config.ts
index f9fba02f..31498076 100644
--- a/src/server/utils/config.ts
+++ b/src/server/utils/config.ts
@@ -1,5 +1,8 @@
import debug from 'debug';
+import packageJson from '@@/package.json';
export const WG_PATH = process.env.WG_PATH || '/etc/wireguard/';
+export const RELEASE = packageJson.release.version;
+
export const SERVER_DEBUG = debug('Server');
diff --git a/src/server/utils/types.ts b/src/server/utils/types.ts
index c0f54988..a5acbbcc 100644
--- a/src/server/utils/types.ts
+++ b/src/server/utils/types.ts
@@ -13,8 +13,8 @@ const id = z
.uuid('Client ID must be a valid UUID')
.pipe(safeStringRefine);
-const address = z
- .string({ message: 'Address must be a valid string' })
+const address4 = z
+ .string({ message: 'IPv4 Address must be a valid string' })
.pipe(safeStringRefine);
const name = z
@@ -54,9 +54,9 @@ export const clientIdType = z.object(
{ message: "This shouldn't happen" }
);
-export const addressType = z.object(
+export const address4Type = z.object(
{
- address: address,
+ address4: address4,
},
{ message: 'Body must be a valid object' }
);
diff --git a/src/services/database/lowdb.ts b/src/services/database/lowdb.ts
index c4ff8ad1..1e33787b 100644
--- a/src/services/database/lowdb.ts
+++ b/src/services/database/lowdb.ts
@@ -102,6 +102,7 @@ export default class LowDB extends DatabaseProvider {
id: crypto.randomUUID(),
password: hashPassword(password),
username,
+ name: 'Administrator',
role: isUserEmpty ? 'ADMIN' : 'CLIENT',
enabled: true,
createdAt: now,
@@ -175,17 +176,17 @@ export default class LowDB extends DatabaseProvider {
});
}
- async updateClientAddress(id: string, address: string) {
- DEBUG('Update Client Address');
+ async updateClientAddress4(id: string, address4: string) {
+ DEBUG('Update Client Address4');
await this.#db.update((data) => {
if (data.clients[id]) {
- data.clients[id].address = address;
+ data.clients[id].address4 = address4;
}
});
}
async updateClientExpirationDate(id: string, expirationDate: string | null) {
- DEBUG('Update Client Address');
+ DEBUG('Update Client Expiration Date');
await this.#db.update((data) => {
if (data.clients[id]) {
data.clients[id].expiresAt = expirationDate;
@@ -194,7 +195,7 @@ export default class LowDB extends DatabaseProvider {
}
async deleteOneTimeLink(id: string) {
- DEBUG('Update Client Address');
+ DEBUG('Delete Client One Time Link');
await this.#db.update((data) => {
if (data.clients[id]) {
if (data.clients[id].oneTimeLink) {
@@ -208,7 +209,7 @@ export default class LowDB extends DatabaseProvider {
}
async createOneTimeLink(id: string, oneTimeLink: OneTimeLink) {
- DEBUG('Update Client Address');
+ DEBUG('Create Client One Time Link');
await this.#db.update((data) => {
if (data.clients[id]) {
data.clients[id].oneTimeLink = oneTimeLink;
diff --git a/src/services/database/migrations/1.ts b/src/services/database/migrations/1.ts
index 7f4f5aef..79da292b 100644
--- a/src/services/database/migrations/1.ts
+++ b/src/services/database/migrations/1.ts
@@ -1,33 +1,35 @@
import type { Low } from 'lowdb';
import type { Database } from '../repositories/database';
-import packageJson from '@@/package.json';
import { ChartType } from '../repositories/system';
-import ip from 'ip';
+import { parseCidr } from 'cidr-tools';
+import { stringifyIp } from 'ip-bigint';
export async function run1(db: Low) {
const privateKey = await exec('wg genkey');
const publicKey = await exec(`echo ${privateKey} | wg pubkey`, {
log: 'echo ***hidden*** | wg pubkey',
});
- const addressRange = '10.8.0.0/24';
- const cidr = ip.cidrSubnet(addressRange);
+ const address4Range = '10.8.0.0/24';
+ const address6Range = 'fdcc:ad94:bacf:61a4::cafe:0/112';
+ const cidr4 = parseCidr(address4Range);
+ const cidr6 = parseCidr(address6Range);
const database: Database = {
migrations: [],
system: {
- // TODO: move to var, no need for database
- release: packageJson.release.version,
interface: {
privateKey: privateKey,
publicKey: publicKey,
- address: cidr.firstAddress,
+ address4: stringifyIp({ number: cidr4.start + 1n, version: 4 }),
+ address6: stringifyIp({ number: cidr6.start + 1n, version: 6 }),
},
sessionTimeout: 3600, // 1 hour
lang: 'en',
userConfig: {
mtu: 1420,
persistentKeepalive: 0,
- addressRange: addressRange,
- defaultDns: ['1.1.1.1'],
+ address4Range: address4Range,
+ address6Range: address6Range,
+ defaultDns: ['1.1.1.1', '2606:4700:4700::1111'],
allowedIps: ['0.0.0.0/0', '::/0'],
},
wgDevice: 'eth0',
@@ -63,26 +65,35 @@ export async function run1(db: Low) {
name: 'wg-easy',
cookie: {},
},
- cookieMaxAge: 24 * 60,
},
users: [],
clients: {},
};
// TODO: use variables inside up/down script
+ // TODO: properly check if ipv6 support
database.system.iptables.PostUp = `
-iptables -t nat -A POSTROUTING -s ${database.system.userConfig.addressRange} -o ${database.system.wgDevice} -j MASQUERADE;
+iptables -t nat -A POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE;
iptables -A INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT;
iptables -A FORWARD -i wg0 -j ACCEPT;
iptables -A FORWARD -o wg0 -j ACCEPT;
+ip6tables -t nat -A POSTROUTING -s ${database.system.userConfig.address6Range} -o ${database.system.wgDevice} -j MASQUERADE;
+ip6tables -A INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT;
+ip6tables -A FORWARD -i wg0 -j ACCEPT;
+ip6tables -A FORWARD -o wg0 -j ACCEPT;
`
.split('\n')
.join(' ');
+
database.system.iptables.PostDown = `
-iptables -t nat -D POSTROUTING -s ${database.system.userConfig.addressRange} -o ${database.system.wgDevice} -j MASQUERADE;
+iptables -t nat -D POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE;
iptables -D INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT;
iptables -D FORWARD -i wg0 -j ACCEPT;
iptables -D FORWARD -o wg0 -j ACCEPT;
+ip6tables -t nat -D POSTROUTING -s ${database.system.userConfig.address6Range} -o ${database.system.wgDevice} -j MASQUERADE;
+ip6tables -D INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT;
+ip6tables -D FORWARD -i wg0 -j ACCEPT;
+ip6tables -D FORWARD -o wg0 -j ACCEPT;
`
.split('\n')
.join(' ');
diff --git a/src/services/database/repositories/client.ts b/src/services/database/repositories/client.ts
index 8666f300..3d641c23 100644
--- a/src/services/database/repositories/client.ts
+++ b/src/services/database/repositories/client.ts
@@ -7,7 +7,8 @@ export type OneTimeLink = {
export type Client = {
id: string;
name: string;
- address: string;
+ address4: string;
+ address6: string;
privateKey: string;
publicKey: string;
preSharedKey: string;
@@ -37,7 +38,7 @@ export interface ClientRepository {
deleteClient(id: string): Promise;
toggleClient(id: string, enable: boolean): Promise;
updateClientName(id: string, name: string): Promise;
- updateClientAddress(id: string, address: string): Promise;
+ updateClientAddress4(id: string, address4: string): Promise;
updateClientExpirationDate(
id: string,
expirationDate: string | null
diff --git a/src/services/database/repositories/database.ts b/src/services/database/repositories/database.ts
index 3c8c1e5f..2f90f15f 100644
--- a/src/services/database/repositories/database.ts
+++ b/src/services/database/repositories/database.ts
@@ -59,7 +59,7 @@ export abstract class DatabaseProvider
abstract deleteClient(id: string): Promise;
abstract toggleClient(id: string, enable: boolean): Promise;
abstract updateClientName(id: string, name: string): Promise;
- abstract updateClientAddress(id: string, address: string): Promise;
+ abstract updateClientAddress4(id: string, address4: string): Promise;
abstract updateClientExpirationDate(
id: string,
expirationDate: string | null
diff --git a/src/services/database/repositories/system.ts b/src/services/database/repositories/system.ts
index 6ba9cfac..aba8129a 100644
--- a/src/services/database/repositories/system.ts
+++ b/src/services/database/repositories/system.ts
@@ -12,13 +12,15 @@ export type IpTables = {
export type WGInterface = {
privateKey: string;
publicKey: string;
- address: string;
+ address4: string;
+ address6: string;
};
export type WGConfig = {
mtu: number;
persistentKeepalive: number;
- addressRange: string;
+ address4Range: string;
+ address6Range: string;
defaultDns: string[];
allowedIps: string[];
};
@@ -50,7 +52,6 @@ export type Feature = {
export type System = {
interface: WGInterface;
- release: string;
// maxAge
sessionTimeout: number;
lang: Lang;
@@ -71,7 +72,6 @@ export type System = {
prometheus: Prometheus;
sessionConfig: SessionConfig;
- cookieMaxAge: number;
};
/**
diff --git a/src/services/database/repositories/user.ts b/src/services/database/repositories/user.ts
index 260b113a..6a9c1bb5 100644
--- a/src/services/database/repositories/user.ts
+++ b/src/services/database/repositories/user.ts
@@ -16,11 +16,7 @@ export type User = {
role: ROLE;
username: string;
password: string;
- name?: string;
- address?: string;
- privateKey?: string;
- publicKey?: string;
- preSharedKey?: string;
+ name: string;
/** ISO String */
createdAt: string;
/** ISO String */