Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions src/vpn/v2ray/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,11 @@ export class V2Ray {
config: V2RayConf;
uuid: string;
child: null | ChildProcessWithoutNullStreams;
configPath: string | null;

constructor() {
this.child = null;
this.configPath = null;
this.uuid = randomUUID();
// https://github.com/sentinel-official/sentinel-go-sdk/blob/development/v2ray/client.json.tmpl
this.config = {
Expand Down Expand Up @@ -252,6 +254,7 @@ export class V2Ray {
output = path.join(tempDirectory, "v2ray_" + randomBytes(8).toString('hex') + ".json");
}
fs.writeFileSync(output, JSON.stringify(this.config, null, 4));
this.configPath = output;
return output;
}

Expand Down Expand Up @@ -284,8 +287,29 @@ export class V2Ray {
* @returns `true` if the signal was sent successfully, `false` if no process is running.
*/
public disconnect(): boolean {
if (this.child) return this.child.kill('SIGINT');
return false;
const killed = this.child ? this.child.kill('SIGINT') : false;
this.cleanup();
return killed;
}

/**
* Removes config files from disk.
*
* @param configFile - Optional path. Falls back to the last written config.
*/
public cleanup(configFile?: string): void {
const target = configFile || this.configPath;
if (!target) return;
try {
fs.unlinkSync(target);
const dir = path.dirname(target);
if (dir.includes('sentinel-js-sdk')) {
fs.rmdirSync(dir);
}
} catch {
// Best-effort cleanup
}
if (target === this.configPath) this.configPath = null;
}

/**
Expand Down
51 changes: 44 additions & 7 deletions src/vpn/wireguard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ export class Wireguard {

publicKey: string
privateKey: string
configPath: string | null

constructor() {
this.interface = null;
this.peer = null;
this.configPath = null;

const keys = this.genKeys();
this.publicKey = keys.pub
Expand Down Expand Up @@ -163,6 +165,8 @@ export class Wireguard {
if (this.peer.presharedKey) config += "PresharedKey = " + this.peer.presharedKey + "\n"

fs.writeFileSync(output, config);
this.configPath = output;
try { fs.chmodSync(output, 0o600); } catch {}
return output
}
return null
Expand Down Expand Up @@ -253,14 +257,47 @@ export class Wireguard {
*
* @param configFile - Path to the `.conf` file used when connecting.
*/
public disconnect(configFile: string) {
const child = spawn("wg-quick", ["down", configFile])
child.stdout.setEncoding('utf8');
child.stdout.on('data', function (data) { console.log('stdout: ' + data.trim()); });
public disconnect(configFile: string): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn("wg-quick", ["down", configFile]);
let stderr = '';

child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => { stderr += data; });
child.on('error', (err) => reject(new Error(`Failed to start wg-quick: ${err.message}`)));
child.on('close', (code) => {
// Clean up config file (contains private key in plaintext)
this.cleanup(configFile);
if (code === 0) resolve();
else reject(new Error(`wg-quick down failed (exit code ${code}): ${stderr.trim()}`));
});
});
}

child.stderr.setEncoding('utf8');
child.stderr.on('data', function (data) { console.log('stderr: ' + data.trim()); });
child.on('close', (code) => console.log(`wg-quick down exited with code ${code}.`));
/**
* Removes config files from disk. Overwrites with zeros before deletion
* to scrub the private key from the filesystem.
*
* @param configFile - Path to the config file to clean up.
*/
public cleanup(configFile?: string): void {
const target = configFile || this.configPath;
if (!target) return;
try {
// Overwrite with zeros to scrub private key before unlinking
const size = fs.statSync(target).size;
fs.writeFileSync(target, Buffer.alloc(size, 0));
fs.unlinkSync(target);
// Try to remove parent temp directory if empty
const dir = path.dirname(target);
if (dir.includes('sentinel-js-sdk')) {
fs.rmdirSync(dir);
}
} catch {
// Best-effort cleanup
}
if (target === this.configPath) this.configPath = null;
}

/**
Expand Down