From 8fb616afe85ba91e33fa1ebe4ef1c67121cfc378 Mon Sep 17 00:00:00 2001 From: carmcc Date: Thu, 3 Aug 2023 22:18:13 +0200 Subject: [PATCH 1/4] Started working on Linux support --- electron/main/vpn/linux/wireguard.ts | 100 +++++++++++++++++++++++++++ electron/main/vpn/vpn.ts | 3 + 2 files changed, 103 insertions(+) create mode 100644 electron/main/vpn/linux/wireguard.ts diff --git a/electron/main/vpn/linux/wireguard.ts b/electron/main/vpn/linux/wireguard.ts new file mode 100644 index 0000000..de3e681 --- /dev/null +++ b/electron/main/vpn/linux/wireguard.ts @@ -0,0 +1,100 @@ +import { spawn } from 'child_process'; +import fs from 'fs'; +import { WireGuard } from '../wireguard'; +import {DATA_PATH, EXECUTABLE_NAME, getWireGuardClientPath, PID_FILE_WIREGUARD} from '../../globals'; + +export class WireGuardLinux extends WireGuard { + constructor( + private ip: string, + private port: number, + private privateKey: string, + private publicKey: string, + private internalIp: string, + private ips: string[], + private isIpsAllowlist: boolean, + private apps: string[], + private isAppsAllowlist: boolean + ) { + super(); + } + + async start(): Promise { + this.writeConfig(); + const child = spawn('wg-quick', ['up', 'wireguard']); // Adjust the 'wireguard' to your connection name. + if (child.pid) { + fs.writeFileSync(PID_FILE_WIREGUARD, child.pid.toString()); + } + child.stdout.on('data', (data) => { + console.log(`[WireGuard stdout] ${data}`); + }); + + child.stderr.on('data', (data) => { + console.error(`[WireGuard stderr] ${data}`); + }); + } + + async stop(): Promise { + // First, check if the PID_FILE_WIREGUARD exists and contains a valid PID + if (fs.existsSync(PID_FILE_WIREGUARD)) { + const pid = parseInt(fs.readFileSync(PID_FILE_WIREGUARD, 'utf8')); + if (pid) { + try { + // Use SIGTERM to stop the process gracefully + process.kill(pid, 'SIGTERM'); + fs.unlinkSync(PID_FILE_WIREGUARD); + console.log(`WireGuard process with PID ${pid} has been stopped.`); + } catch (err) { + console.error('Error stopping WireGuard process:', err); + } + } else { + console.warn('Invalid PID in the PID_FILE_WIREGUARD.'); + } + } else { + console.warn('PID_FILE_WIREGUARD does not exist.'); + } + } + + protected getConfig(): string { + // ... (rest of the getConfig() implementation as before) + let disallowedIPs = ''; + let allowedIPs = 'AllowedIPs = '; + if (this.ips.length > 0) { // enable split tunneling for IPs + if (this.isIpsAllowlist) { // allowlist + allowedIPs += this.apps.join(','); + } else { // denylist + disallowedIPs = 'DisallowedIPs = ' + this.ips.join(','); + allowedIPs += '0.0.0.0/1, 128.0.0.0/1'; // route all traffic through the VPN + } + } else { + allowedIPs += '0.0.0.0/1, 128.0.0.0/1'; + } + // route all IPv6 traffic, prevents leaks through IPv6 + allowedIPs += ', ::/0'; + + let disallowedApps = 'DisallowedApps = ' + EXECUTABLE_NAME; // don't route the VPN app through the VPN + let allowedApps = ''; + if (this.apps.length > 0) { + if (this.isAppsAllowlist) { // allowlist + allowedApps = 'AllowedApps = ' + this.apps.join(','); + } else { // denylist + disallowedApps += ',' + this.apps.join(','); + } + } + + + return ` + [Interface] + PrivateKey = ${this.privateKey} + Address = ${this.internalIp}/32 + DNS = 1.1.1.1, 1.0.0.1 + + [Peer] + PublicKey = ${this.publicKey} + Endpoint = ${this.ip}:${this.port} + PersistentKeepalive = 15 + ${allowedIPs} + ${disallowedIPs} + ${allowedApps} + ${disallowedApps}`; + } +} diff --git a/electron/main/vpn/vpn.ts b/electron/main/vpn/vpn.ts index ebf3704..fc73c39 100644 --- a/electron/main/vpn/vpn.ts +++ b/electron/main/vpn/vpn.ts @@ -7,6 +7,7 @@ import {Status} from './status'; import {createApi} from '../../../common/api'; import {INVALID_REFRESH_TOKEN} from '../../../common/channels'; import {WireGuardWindows} from './windows/wireguard'; +import {WireGuardLinux} from "./linux/wireguard"; export interface VPN { start(): Promise; @@ -108,6 +109,8 @@ function getNewWireGuardInstance(serverIp: string, port: number, privateKey: str switch (process.platform) { case 'win32': return new WireGuardWindows(serverIp, port, privateKey, publicKey, internalIp, ips, isIpsAllowlist, apps, isAppsAllowlist); + case 'linux': + return new WireGuardLinux(serverIp, port, privateKey, publicKey, internalIp, ips, isIpsAllowlist, apps, isAppsAllowlist); } throw new Error('Unsupported platform'); } From 384c006a68b2d0ebcb430df96a0d50d0c5b7d0e6 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Thu, 3 Aug 2023 22:24:19 +0200 Subject: [PATCH 2/4] Remove unused imports --- electron/main/vpn/linux/wireguard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/main/vpn/linux/wireguard.ts b/electron/main/vpn/linux/wireguard.ts index de3e681..d635982 100644 --- a/electron/main/vpn/linux/wireguard.ts +++ b/electron/main/vpn/linux/wireguard.ts @@ -1,7 +1,7 @@ import { spawn } from 'child_process'; import fs from 'fs'; import { WireGuard } from '../wireguard'; -import {DATA_PATH, EXECUTABLE_NAME, getWireGuardClientPath, PID_FILE_WIREGUARD} from '../../globals'; +import {EXECUTABLE_NAME, PID_FILE_WIREGUARD} from '../../globals'; export class WireGuardLinux extends WireGuard { constructor( From 8ce95024bb2efeadd8b377f34fcd47dea05c260a Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Thu, 3 Aug 2023 23:33:49 +0200 Subject: [PATCH 3/4] Use sudo-prompt to execute wg-quick commands. Use the correct configuration file --- electron/main/index.ts | 27 ++++++++-- electron/main/vpn/linux/wireguard.ts | 73 ++++++-------------------- electron/main/vpn/windows/wireguard.ts | 2 +- electron/main/vpn/wireguard.ts | 4 +- package-lock.json | 6 +++ package.json | 1 + src/App.vue | 8 +-- 7 files changed, 54 insertions(+), 67 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 55023dc..0789650 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -5,9 +5,10 @@ import * as electronRemote from '@electron/remote/main'; import {is, optimizer} from '@electron-toolkit/utils'; import windowStateKeeper from 'electron-window-state'; import {autoUpdater} from 'electron-updater'; +import sudo from 'sudo-prompt'; import './appinfo'; import * as ipc from './ipc'; -import {PID_FILE_OPENVPN, PID_FILE_WIREGUARD} from './globals'; +import {DATA_PATH, PID_FILE_OPENVPN, PID_FILE_WIREGUARD} from './globals'; import settings from './settings'; import {getCurrentMonitor} from './vpn/monitor'; import {Status} from './vpn/status'; @@ -208,10 +209,26 @@ export async function stopVPN(isSwitchingServer = false): Promise { if (!isSwitchingServer) { getCurrentMonitor()?.setStatus(Status.DISCONNECTING); } - await Promise.all([ - stopVPNFromPidFile(PID_FILE_WIREGUARD), - stopVPNFromPidFile(PID_FILE_OPENVPN) - ]); + if (process.platform === 'linux') { + sudo.exec(`wg-quick down "${DATA_PATH}/dicyvpn.conf"`, { + name: 'DicyVPN' + }, (error, stdout, stderr) => { + if (error) { + console.error(`[WireGuard] error: ${error.message}`); + return; + } + if (stderr) { + console.error(`[WireGuard stderr] ${stderr}`); + return; + } + console.log(`[WireGuard stdout] ${stdout}`); + }); + } else { + await Promise.all([ + stopVPNFromPidFile(PID_FILE_WIREGUARD), + stopVPNFromPidFile(PID_FILE_OPENVPN) + ]); + } updateTray(false); } diff --git a/electron/main/vpn/linux/wireguard.ts b/electron/main/vpn/linux/wireguard.ts index d635982..c8f7461 100644 --- a/electron/main/vpn/linux/wireguard.ts +++ b/electron/main/vpn/linux/wireguard.ts @@ -1,7 +1,6 @@ -import { spawn } from 'child_process'; -import fs from 'fs'; +import sudo from 'sudo-prompt'; import { WireGuard } from '../wireguard'; -import {EXECUTABLE_NAME, PID_FILE_WIREGUARD} from '../../globals'; +import {DATA_PATH} from '../../globals'; export class WireGuardLinux extends WireGuard { constructor( @@ -19,69 +18,34 @@ export class WireGuardLinux extends WireGuard { } async start(): Promise { - this.writeConfig(); - const child = spawn('wg-quick', ['up', 'wireguard']); // Adjust the 'wireguard' to your connection name. - if (child.pid) { - fs.writeFileSync(PID_FILE_WIREGUARD, child.pid.toString()); - } - child.stdout.on('data', (data) => { - console.log(`[WireGuard stdout] ${data}`); - }); - - child.stderr.on('data', (data) => { - console.error(`[WireGuard stderr] ${data}`); - }); - } - - async stop(): Promise { - // First, check if the PID_FILE_WIREGUARD exists and contains a valid PID - if (fs.existsSync(PID_FILE_WIREGUARD)) { - const pid = parseInt(fs.readFileSync(PID_FILE_WIREGUARD, 'utf8')); - if (pid) { - try { - // Use SIGTERM to stop the process gracefully - process.kill(pid, 'SIGTERM'); - fs.unlinkSync(PID_FILE_WIREGUARD); - console.log(`WireGuard process with PID ${pid} has been stopped.`); - } catch (err) { - console.error('Error stopping WireGuard process:', err); - } - } else { - console.warn('Invalid PID in the PID_FILE_WIREGUARD.'); + this.writeConfig('dicyvpn.conf'); + sudo.exec(`wg-quick up "${DATA_PATH}/dicyvpn.conf"`, { + name: 'DicyVPN' + }, (error, stdout, stderr) => { + if (error) { + console.error(`[WireGuard] error: ${error.message}`); + return; } - } else { - console.warn('PID_FILE_WIREGUARD does not exist.'); - } + if (stderr) { + console.error(`[WireGuard stderr] ${stderr}`); + return; + } + console.log(`[WireGuard stdout] ${stdout}`); + }); } protected getConfig(): string { - // ... (rest of the getConfig() implementation as before) - let disallowedIPs = ''; let allowedIPs = 'AllowedIPs = '; if (this.ips.length > 0) { // enable split tunneling for IPs if (this.isIpsAllowlist) { // allowlist allowedIPs += this.apps.join(','); - } else { // denylist - disallowedIPs = 'DisallowedIPs = ' + this.ips.join(','); - allowedIPs += '0.0.0.0/1, 128.0.0.0/1'; // route all traffic through the VPN } } else { - allowedIPs += '0.0.0.0/1, 128.0.0.0/1'; + allowedIPs += '0.0.0.0/0'; } // route all IPv6 traffic, prevents leaks through IPv6 allowedIPs += ', ::/0'; - let disallowedApps = 'DisallowedApps = ' + EXECUTABLE_NAME; // don't route the VPN app through the VPN - let allowedApps = ''; - if (this.apps.length > 0) { - if (this.isAppsAllowlist) { // allowlist - allowedApps = 'AllowedApps = ' + this.apps.join(','); - } else { // denylist - disallowedApps += ',' + this.apps.join(','); - } - } - - return ` [Interface] PrivateKey = ${this.privateKey} @@ -92,9 +56,6 @@ export class WireGuardLinux extends WireGuard { PublicKey = ${this.publicKey} Endpoint = ${this.ip}:${this.port} PersistentKeepalive = 15 - ${allowedIPs} - ${disallowedIPs} - ${allowedApps} - ${disallowedApps}`; + ${allowedIPs}`; } } diff --git a/electron/main/vpn/windows/wireguard.ts b/electron/main/vpn/windows/wireguard.ts index 8712d32..5f257c0 100644 --- a/electron/main/vpn/windows/wireguard.ts +++ b/electron/main/vpn/windows/wireguard.ts @@ -20,7 +20,7 @@ export class WireGuardWindows extends WireGuard { } async start(): Promise { - this.writeConfig(); + this.writeConfig('wireguard.conf'); const exe = getWireGuardClientPath(); const args = ['run', '-config', `${DATA_PATH}/wireguard.conf`, '-log-level', 'info']; const child = spawn(exe, args); diff --git a/electron/main/vpn/wireguard.ts b/electron/main/vpn/wireguard.ts index 3b5461e..10e0267 100644 --- a/electron/main/vpn/wireguard.ts +++ b/electron/main/vpn/wireguard.ts @@ -6,9 +6,9 @@ export abstract class WireGuard implements VPN { abstract start(): Promise; - protected writeConfig() { + protected writeConfig(filename: string) { const conf = this.getConfig(); - fs.writeFileSync(`${DATA_PATH}/wireguard.conf`, conf); + fs.writeFileSync(`${DATA_PATH}/${filename}`, conf); } protected abstract getConfig(): string; diff --git a/package-lock.json b/package-lock.json index 458313a..e644609 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "i18n-iso-countries": "^7.6.0", "ping": "^0.4.2", "pinia": "^2.1.6", + "sudo-prompt": "^9.2.1", "svg-pan-zoom-container": "^0.6.1", "tail": "^2.2.6", "vue": "^3.2.45", @@ -6038,6 +6039,11 @@ "node": "*" } }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", diff --git a/package.json b/package.json index 93658cd..46b565b 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "i18n-iso-countries": "^7.6.0", "ping": "^0.4.2", "pinia": "^2.1.6", + "sudo-prompt": "^9.2.1", "svg-pan-zoom-container": "^0.6.1", "tail": "^2.2.6", "vue": "^3.2.45", diff --git a/src/App.vue b/src/App.vue index 44228e7..032fe46 100644 --- a/src/App.vue +++ b/src/App.vue @@ -60,9 +60,11 @@ export default { this.$router.push('/'); }); - this.currentServer.$patch({ - status: await window.preload.isRunning() ? Status.CONNECTED : Status.NOT_RUNNING - }); + if (window.electron.process.platform !== 'linux') { + this.currentServer.$patch({ + status: await window.preload.isRunning() ? Status.CONNECTED : Status.NOT_RUNNING + }); + } }, beforeUnmount() { window.preload.removeListener('status-change', this.onStatusChange); From b60e61c5d82cb3749a480a4ebdcd709aa3b34f6b Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Thu, 10 Aug 2023 11:39:48 +0200 Subject: [PATCH 4/4] Use custom DNS for Linux --- electron/main/vpn/linux/wireguard.ts | 9 ++++----- electron/main/vpn/vpn.ts | 5 ++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/electron/main/vpn/linux/wireguard.ts b/electron/main/vpn/linux/wireguard.ts index c8f7461..bcf440c 100644 --- a/electron/main/vpn/linux/wireguard.ts +++ b/electron/main/vpn/linux/wireguard.ts @@ -1,5 +1,5 @@ import sudo from 'sudo-prompt'; -import { WireGuard } from '../wireguard'; +import {WireGuard} from '../wireguard'; import {DATA_PATH} from '../../globals'; export class WireGuardLinux extends WireGuard { @@ -11,8 +11,7 @@ export class WireGuardLinux extends WireGuard { private internalIp: string, private ips: string[], private isIpsAllowlist: boolean, - private apps: string[], - private isAppsAllowlist: boolean + private dns: string[] ) { super(); } @@ -38,7 +37,7 @@ export class WireGuardLinux extends WireGuard { let allowedIPs = 'AllowedIPs = '; if (this.ips.length > 0) { // enable split tunneling for IPs if (this.isIpsAllowlist) { // allowlist - allowedIPs += this.apps.join(','); + allowedIPs += this.ips.join(', '); } } else { allowedIPs += '0.0.0.0/0'; @@ -50,7 +49,7 @@ export class WireGuardLinux extends WireGuard { [Interface] PrivateKey = ${this.privateKey} Address = ${this.internalIp}/32 - DNS = 1.1.1.1, 1.0.0.1 + DNS = ${this.dns.join(', ')} [Peer] PublicKey = ${this.publicKey} diff --git a/electron/main/vpn/vpn.ts b/electron/main/vpn/vpn.ts index 34f667f..3c6d198 100644 --- a/electron/main/vpn/vpn.ts +++ b/electron/main/vpn/vpn.ts @@ -114,7 +114,10 @@ function getNewWireGuardInstance(serverIp: string, port: number, privateKey: str ips, isIpsAllowlist, apps, isAppsAllowlist, dns ); case 'linux': - return new WireGuardLinux(serverIp, port, privateKey, publicKey, internalIp, ips, isIpsAllowlist, apps, isAppsAllowlist); + return new WireGuardLinux( + serverIp, port, privateKey, publicKey, internalIp, + ips, isIpsAllowlist, dns + ); } throw new Error('Unsupported platform'); }