green-tunnel 2.0.2 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/config.d.ts +13 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +87 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/dns/decode.d.ts +12 -0
- package/dist/core/dns/decode.d.ts.map +1 -0
- package/dist/core/dns/decode.js +23 -0
- package/dist/core/dns/decode.js.map +1 -0
- package/dist/core/dns/doh.d.ts +19 -0
- package/dist/core/dns/doh.d.ts.map +1 -0
- package/dist/core/dns/doh.js +54 -0
- package/dist/core/dns/doh.js.map +1 -0
- package/dist/core/dns/dot.d.ts +13 -0
- package/dist/core/dns/dot.d.ts.map +1 -0
- package/dist/core/dns/dot.js +79 -0
- package/dist/core/dns/dot.js.map +1 -0
- package/dist/core/dns/index.d.ts +8 -0
- package/dist/core/dns/index.d.ts.map +1 -0
- package/dist/core/dns/index.js +18 -0
- package/dist/core/dns/index.js.map +1 -0
- package/dist/core/dns/plain.d.ts +17 -0
- package/dist/core/dns/plain.d.ts.map +1 -0
- package/dist/core/dns/plain.js +45 -0
- package/dist/core/dns/plain.js.map +1 -0
- package/dist/core/dns/resolver.d.ts +27 -0
- package/dist/core/dns/resolver.d.ts.map +1 -0
- package/dist/core/dns/resolver.js +81 -0
- package/dist/core/dns/resolver.js.map +1 -0
- package/dist/core/errors.d.ts +32 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +37 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/http/head.d.ts +40 -0
- package/dist/core/http/head.d.ts.map +1 -0
- package/dist/core/http/head.js +122 -0
- package/dist/core/http/head.js.map +1 -0
- package/dist/core/http/rewriter.d.ts +20 -0
- package/dist/core/http/rewriter.d.ts.map +1 -0
- package/dist/core/http/rewriter.js +184 -0
- package/dist/core/http/rewriter.js.map +1 -0
- package/dist/core/index.d.ts +28 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +27 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +38 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +92 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/net/socket.d.ts +30 -0
- package/dist/core/net/socket.d.ts.map +1 -0
- package/dist/core/net/socket.js +98 -0
- package/dist/core/net/socket.js.map +1 -0
- package/dist/core/proxy/connect-tunnel.d.ts +14 -0
- package/dist/core/proxy/connect-tunnel.d.ts.map +1 -0
- package/dist/core/proxy/connect-tunnel.js +44 -0
- package/dist/core/proxy/connect-tunnel.js.map +1 -0
- package/dist/core/proxy/connection.d.ts +12 -0
- package/dist/core/proxy/connection.d.ts.map +1 -0
- package/dist/core/proxy/connection.js +88 -0
- package/dist/core/proxy/connection.js.map +1 -0
- package/dist/core/proxy/context.d.ts +15 -0
- package/dist/core/proxy/context.d.ts.map +1 -0
- package/dist/core/proxy/context.js +2 -0
- package/dist/core/proxy/context.js.map +1 -0
- package/dist/core/proxy/index.d.ts +33 -0
- package/dist/core/proxy/index.d.ts.map +1 -0
- package/dist/core/proxy/index.js +162 -0
- package/dist/core/proxy/index.js.map +1 -0
- package/dist/core/proxy/pipe.d.ts +12 -0
- package/dist/core/proxy/pipe.d.ts.map +1 -0
- package/dist/core/proxy/pipe.js +42 -0
- package/dist/core/proxy/pipe.js.map +1 -0
- package/dist/core/proxy/plain-tunnel.d.ts +12 -0
- package/dist/core/proxy/plain-tunnel.d.ts.map +1 -0
- package/dist/core/proxy/plain-tunnel.js +29 -0
- package/dist/core/proxy/plain-tunnel.js.map +1 -0
- package/dist/core/system-proxy/darwin.d.ts +32 -0
- package/dist/core/system-proxy/darwin.d.ts.map +1 -0
- package/dist/core/system-proxy/darwin.js +146 -0
- package/dist/core/system-proxy/darwin.js.map +1 -0
- package/dist/core/system-proxy/driver.d.ts +21 -0
- package/dist/core/system-proxy/driver.d.ts.map +1 -0
- package/dist/core/system-proxy/driver.js +2 -0
- package/dist/core/system-proxy/driver.js.map +1 -0
- package/dist/core/system-proxy/exec.d.ts +21 -0
- package/dist/core/system-proxy/exec.d.ts.map +1 -0
- package/dist/core/system-proxy/exec.js +62 -0
- package/dist/core/system-proxy/exec.js.map +1 -0
- package/dist/core/system-proxy/index.d.ts +45 -0
- package/dist/core/system-proxy/index.d.ts.map +1 -0
- package/dist/core/system-proxy/index.js +151 -0
- package/dist/core/system-proxy/index.js.map +1 -0
- package/dist/core/system-proxy/linux.d.ts +18 -0
- package/dist/core/system-proxy/linux.d.ts.map +1 -0
- package/dist/core/system-proxy/linux.js +100 -0
- package/dist/core/system-proxy/linux.js.map +1 -0
- package/dist/core/system-proxy/recovery.d.ts +23 -0
- package/dist/core/system-proxy/recovery.d.ts.map +1 -0
- package/dist/core/system-proxy/recovery.js +147 -0
- package/dist/core/system-proxy/recovery.js.map +1 -0
- package/dist/core/system-proxy/windows.d.ts +15 -0
- package/dist/core/system-proxy/windows.d.ts.map +1 -0
- package/dist/core/system-proxy/windows.js +96 -0
- package/dist/core/system-proxy/windows.js.map +1 -0
- package/dist/core/tls/fragment.d.ts +28 -0
- package/dist/core/tls/fragment.d.ts.map +1 -0
- package/dist/core/tls/fragment.js +81 -0
- package/dist/core/tls/fragment.js.map +1 -0
- package/dist/core/types.d.ts +138 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +155 -0
- package/dist/main.js.map +1 -0
- package/dist/options.d.ts +25 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +144 -0
- package/dist/options.js.map +1 -0
- package/dist/ui.d.ts +17 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +89 -0
- package/dist/ui.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/package.json +31 -30
- package/LICENSE +0 -21
- package/README.md +0 -172
- package/bin/gt.js +0 -170
- package/src/config.js +0 -15
- package/src/dns/base.js +0 -47
- package/src/dns/https.js +0 -30
- package/src/dns/tls.js +0 -15
- package/src/dns/unencrypted.js +0 -25
- package/src/handlers/http.js +0 -65
- package/src/handlers/https.js +0 -78
- package/src/handlers/request.js +0 -32
- package/src/http/request.js +0 -45
- package/src/http/response.js +0 -45
- package/src/http/utils.js +0 -86
- package/src/index.cjs +0 -7
- package/src/index.js +0 -4
- package/src/logger.js +0 -11
- package/src/proxy.js +0 -74
- package/src/scripts/windows/wininet-reset-settings.ps1 +0 -14
- package/src/utils/analytics.js +0 -59
- package/src/utils/buffer.js +0 -35
- package/src/utils/socket.js +0 -32
- package/src/utils/system-proxy.js +0 -153
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { SystemProxyError } from '../errors.js';
|
|
2
|
+
import { exec, isAvailable, settle, tryExec } from './exec.js';
|
|
3
|
+
const GSETTINGS = 'gsettings';
|
|
4
|
+
const SCHEMA = 'org.gnome.system.proxy';
|
|
5
|
+
/**
|
|
6
|
+
* Linux driver, GNOME/GSettings flavour — which also covers Unity, Cinnamon,
|
|
7
|
+
* Budgie and anything else reading the same schema.
|
|
8
|
+
*
|
|
9
|
+
* Note this only affects applications that honour the desktop proxy settings.
|
|
10
|
+
* Terminal tools read `http_proxy`/`https_proxy`, which no process can set for
|
|
11
|
+
* another; the CLI prints them for you to export.
|
|
12
|
+
*/
|
|
13
|
+
export class LinuxSystemProxy {
|
|
14
|
+
platform = 'linux';
|
|
15
|
+
async snapshot() {
|
|
16
|
+
await this.#assertAvailable();
|
|
17
|
+
const mode = unquote(await exec(GSETTINGS, ['get', SCHEMA, 'mode']));
|
|
18
|
+
const enabled = mode === 'manual';
|
|
19
|
+
return {
|
|
20
|
+
platform: 'linux',
|
|
21
|
+
entries: [
|
|
22
|
+
{
|
|
23
|
+
target: 'mode',
|
|
24
|
+
enabled,
|
|
25
|
+
...(await readEndpoint('http')),
|
|
26
|
+
secure: { enabled, ...(await readEndpoint('https')) },
|
|
27
|
+
bypass: parseGVariantList((await tryExec(GSETTINGS, ['get', SCHEMA, 'ignore-hosts'])) ?? ''),
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async apply(settings) {
|
|
33
|
+
await this.#assertAvailable();
|
|
34
|
+
const port = String(settings.port);
|
|
35
|
+
for (const protocol of ['http', 'https']) {
|
|
36
|
+
await exec(GSETTINGS, ['set', `${SCHEMA}.${protocol}`, 'host', settings.host]);
|
|
37
|
+
await exec(GSETTINGS, ['set', `${SCHEMA}.${protocol}`, 'port', port]);
|
|
38
|
+
}
|
|
39
|
+
await exec(GSETTINGS, ['set', SCHEMA, 'ignore-hosts', toGVariantList(settings.bypass)]);
|
|
40
|
+
// Flip the mode last, so nothing is ever routed at a half-written config.
|
|
41
|
+
await exec(GSETTINGS, ['set', SCHEMA, 'mode', 'manual']);
|
|
42
|
+
}
|
|
43
|
+
async restore(snapshot) {
|
|
44
|
+
await this.#assertAvailable();
|
|
45
|
+
const previous = snapshot.entries[0];
|
|
46
|
+
if (!previous)
|
|
47
|
+
return;
|
|
48
|
+
// Turn the mode off *first*: until it is `none`, every write below is live
|
|
49
|
+
// configuration that apps may pick up.
|
|
50
|
+
await settle([
|
|
51
|
+
async () => {
|
|
52
|
+
if (!previous.enabled)
|
|
53
|
+
await exec(GSETTINGS, ['set', SCHEMA, 'mode', 'none']);
|
|
54
|
+
},
|
|
55
|
+
() => writeEndpoint('http', previous),
|
|
56
|
+
() => writeEndpoint('https', previous.secure ?? previous),
|
|
57
|
+
() => exec(GSETTINGS, [
|
|
58
|
+
'set',
|
|
59
|
+
SCHEMA,
|
|
60
|
+
'ignore-hosts',
|
|
61
|
+
toGVariantList(previous.bypass ?? []),
|
|
62
|
+
]).then(() => undefined),
|
|
63
|
+
async () => {
|
|
64
|
+
if (previous.enabled)
|
|
65
|
+
await exec(GSETTINGS, ['set', SCHEMA, 'mode', 'manual']);
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
async #assertAvailable() {
|
|
70
|
+
if (!(await isAvailable(GSETTINGS, ['--version']))) {
|
|
71
|
+
throw new SystemProxyError('gsettings is not available — set the system proxy manually, or use the ' +
|
|
72
|
+
'proxy directly at the address GreenTunnel printed.');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function readEndpoint(protocol) {
|
|
77
|
+
const host = unquote((await tryExec(GSETTINGS, ['get', `${SCHEMA}.${protocol}`, 'host'])) ?? '');
|
|
78
|
+
const port = Number.parseInt((await tryExec(GSETTINGS, ['get', `${SCHEMA}.${protocol}`, 'port'])) ?? '0', 10);
|
|
79
|
+
return { host, port: port || 0 };
|
|
80
|
+
}
|
|
81
|
+
async function writeEndpoint(protocol, state) {
|
|
82
|
+
await exec(GSETTINGS, ['set', `${SCHEMA}.${protocol}`, 'host', state.host]);
|
|
83
|
+
await exec(GSETTINGS, ['set', `${SCHEMA}.${protocol}`, 'port', String(state.port)]);
|
|
84
|
+
}
|
|
85
|
+
/** `gsettings get` wraps strings in single quotes. */
|
|
86
|
+
function unquote(value) {
|
|
87
|
+
return value.trim().replace(/^'(.*)'$/s, '$1');
|
|
88
|
+
}
|
|
89
|
+
function toGVariantList(values) {
|
|
90
|
+
return `[${values.map((value) => `'${value.replace(/'/g, "\\'")}'`).join(', ')}]`;
|
|
91
|
+
}
|
|
92
|
+
/** `['localhost', '127.0.0.0/8']` — or `@as []` for an empty list. */
|
|
93
|
+
function parseGVariantList(value) {
|
|
94
|
+
const items = [];
|
|
95
|
+
for (const match of value.matchAll(/'((?:[^'\\]|\\.)*)'/g)) {
|
|
96
|
+
items.push((match[1] ?? '').replace(/\\(.)/g, '$1'));
|
|
97
|
+
}
|
|
98
|
+
return items;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=linux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linux.js","sourceRoot":"","sources":["../../../src/core/system-proxy/linux.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SystemProxySnapshot } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where the pending snapshot lives while the proxy is active.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately *shared* between the CLI and the desktop app: if the CLI is
|
|
6
|
+
* killed mid-run, the next launch of either one can put the machine back.
|
|
7
|
+
* Deliberately *not* in `os.tmpdir()`, which macOS periodically sweeps — the
|
|
8
|
+
* whole value of this file is surviving a crash and a reboot.
|
|
9
|
+
*/
|
|
10
|
+
export declare function defaultRecoveryPath(): string;
|
|
11
|
+
/** Record a snapshot before touching the OS, so a crash is still recoverable. */
|
|
12
|
+
export declare function writeRecoveryFile(path: string, snapshot: SystemProxySnapshot): Promise<void>;
|
|
13
|
+
export declare function removeRecoveryFile(path: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Read a snapshot left behind by a previous run.
|
|
16
|
+
*
|
|
17
|
+
* Returns `undefined` when there is nothing to recover, when the file is
|
|
18
|
+
* unreadable or corrupt, when it came from a different OS, or when the process
|
|
19
|
+
* that wrote it is **still running** — that last case is a live owner, not an
|
|
20
|
+
* orphan, and stealing its snapshot would rip the proxy out from under it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readRecoveryFile(path: string): Promise<SystemProxySnapshot | undefined>;
|
|
23
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../../../src/core/system-proxy/recovery.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,mBAAmB,EAEpB,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAoB5C;AAED,iFAAiF;AACjF,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAsB7F"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
/**
|
|
6
|
+
* Where the pending snapshot lives while the proxy is active.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately *shared* between the CLI and the desktop app: if the CLI is
|
|
9
|
+
* killed mid-run, the next launch of either one can put the machine back.
|
|
10
|
+
* Deliberately *not* in `os.tmpdir()`, which macOS periodically sweeps — the
|
|
11
|
+
* whole value of this file is surviving a crash and a reboot.
|
|
12
|
+
*/
|
|
13
|
+
export function defaultRecoveryPath() {
|
|
14
|
+
const file = 'system-proxy-snapshot.json';
|
|
15
|
+
const home = homedir();
|
|
16
|
+
switch (process.platform) {
|
|
17
|
+
case 'darwin':
|
|
18
|
+
return join(home, 'Library', 'Application Support', 'green-tunnel', file);
|
|
19
|
+
case 'win32':
|
|
20
|
+
return join(process.env['LOCALAPPDATA'] ?? join(home, 'AppData', 'Local'), 'green-tunnel', file);
|
|
21
|
+
default:
|
|
22
|
+
return join(process.env['XDG_STATE_HOME'] ?? join(home, '.local', 'state'), 'green-tunnel', file);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Record a snapshot before touching the OS, so a crash is still recoverable. */
|
|
26
|
+
export async function writeRecoveryFile(path, snapshot) {
|
|
27
|
+
await mkdir(dirname(path), { recursive: true });
|
|
28
|
+
await writeFile(path, JSON.stringify({ ...snapshot, pid: process.pid }, null, 2), 'utf8');
|
|
29
|
+
}
|
|
30
|
+
export async function removeRecoveryFile(path) {
|
|
31
|
+
await rm(path, { force: true });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Read a snapshot left behind by a previous run.
|
|
35
|
+
*
|
|
36
|
+
* Returns `undefined` when there is nothing to recover, when the file is
|
|
37
|
+
* unreadable or corrupt, when it came from a different OS, or when the process
|
|
38
|
+
* that wrote it is **still running** — that last case is a live owner, not an
|
|
39
|
+
* orphan, and stealing its snapshot would rip the proxy out from under it.
|
|
40
|
+
*/
|
|
41
|
+
export async function readRecoveryFile(path) {
|
|
42
|
+
let text;
|
|
43
|
+
try {
|
|
44
|
+
text = await readFile(path, 'utf8');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
let parsed;
|
|
50
|
+
try {
|
|
51
|
+
parsed = JSON.parse(text);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const snapshot = parseSnapshot(parsed);
|
|
57
|
+
if (!snapshot)
|
|
58
|
+
return undefined;
|
|
59
|
+
if (snapshot.platform !== process.platform)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (snapshot.pid !== undefined && snapshot.pid !== process.pid && isProcessAlive(snapshot.pid)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return snapshot;
|
|
65
|
+
}
|
|
66
|
+
/** Signal 0 tests for existence without delivering anything. */
|
|
67
|
+
function isProcessAlive(pid) {
|
|
68
|
+
try {
|
|
69
|
+
process.kill(pid, 0);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
// EPERM means it exists but belongs to someone else — still alive.
|
|
74
|
+
return error.code === 'EPERM';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Validate rather than cast. This file is replayed straight into `networksetup`
|
|
79
|
+
* / `reg.exe` argument arrays, so a truncated or hand-edited file must be
|
|
80
|
+
* rejected outright instead of turning into nonsense arguments.
|
|
81
|
+
*/
|
|
82
|
+
function parseSnapshot(value) {
|
|
83
|
+
if (typeof value !== 'object' || value === null)
|
|
84
|
+
return undefined;
|
|
85
|
+
const raw = value;
|
|
86
|
+
const platform = raw['platform'];
|
|
87
|
+
const rawEntries = raw['entries'];
|
|
88
|
+
if (typeof platform !== 'string' || !Array.isArray(rawEntries))
|
|
89
|
+
return undefined;
|
|
90
|
+
const entries = [];
|
|
91
|
+
for (const candidate of rawEntries) {
|
|
92
|
+
const entry = parseEntry(candidate);
|
|
93
|
+
if (!entry)
|
|
94
|
+
return undefined;
|
|
95
|
+
entries.push(entry);
|
|
96
|
+
}
|
|
97
|
+
const pid = raw['pid'];
|
|
98
|
+
return {
|
|
99
|
+
platform: platform,
|
|
100
|
+
entries,
|
|
101
|
+
...(typeof pid === 'number' ? { pid } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function parseEntry(value) {
|
|
105
|
+
const endpoint = parseEndpoint(value);
|
|
106
|
+
if (!endpoint)
|
|
107
|
+
return undefined;
|
|
108
|
+
const raw = value;
|
|
109
|
+
const target = raw['target'];
|
|
110
|
+
if (typeof target !== 'string' || target.length === 0)
|
|
111
|
+
return undefined;
|
|
112
|
+
const secure = raw['secure'] === undefined ? undefined : parseEndpoint(raw['secure']);
|
|
113
|
+
if (raw['secure'] !== undefined && !secure)
|
|
114
|
+
return undefined;
|
|
115
|
+
const rawBypass = raw['bypass'];
|
|
116
|
+
let bypass;
|
|
117
|
+
if (rawBypass !== undefined) {
|
|
118
|
+
if (!Array.isArray(rawBypass))
|
|
119
|
+
return undefined;
|
|
120
|
+
if (!rawBypass.every((item) => typeof item === 'string'))
|
|
121
|
+
return undefined;
|
|
122
|
+
bypass = rawBypass;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
target,
|
|
126
|
+
...endpoint,
|
|
127
|
+
...(secure ? { secure } : {}),
|
|
128
|
+
...(bypass ? { bypass } : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function parseEndpoint(value) {
|
|
132
|
+
if (typeof value !== 'object' || value === null)
|
|
133
|
+
return undefined;
|
|
134
|
+
const raw = value;
|
|
135
|
+
const { enabled, host, port } = {
|
|
136
|
+
enabled: raw['enabled'],
|
|
137
|
+
host: raw['host'],
|
|
138
|
+
port: raw['port'],
|
|
139
|
+
};
|
|
140
|
+
if (typeof enabled !== 'boolean' || typeof host !== 'string')
|
|
141
|
+
return undefined;
|
|
142
|
+
if (typeof port !== 'number' || !Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return { enabled, host, port };
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../../src/core/system-proxy/recovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SystemProxySettings, SystemProxySnapshot } from '../types.js';
|
|
2
|
+
import type { SystemProxyDriver } from './driver.js';
|
|
3
|
+
/**
|
|
4
|
+
* Windows driver, writing the WinINet settings every browser and most apps read.
|
|
5
|
+
*
|
|
6
|
+
* Uses `reg.exe` directly instead of the `winreg` package — one fewer
|
|
7
|
+
* dependency, and the argument-array form removes v2's quoting problems.
|
|
8
|
+
*/
|
|
9
|
+
export declare class WindowsSystemProxy implements SystemProxyDriver {
|
|
10
|
+
readonly platform: NodeJS.Platform;
|
|
11
|
+
snapshot(): Promise<SystemProxySnapshot>;
|
|
12
|
+
apply(settings: SystemProxySettings): Promise<void>;
|
|
13
|
+
restore(snapshot: SystemProxySnapshot): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=windows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.d.ts","sourceRoot":"","sources":["../../../src/core/system-proxy/windows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAMrD;;;;;GAKG;AACH,qBAAa,kBAAmB,YAAW,iBAAiB;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAW;IAEvC,QAAQ,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAoBxC,KAAK,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYnD,OAAO,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CAqB5D"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { exec, settle, tryExec } from './exec.js';
|
|
2
|
+
const REG = 'reg.exe';
|
|
3
|
+
const KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings';
|
|
4
|
+
/**
|
|
5
|
+
* Windows driver, writing the WinINet settings every browser and most apps read.
|
|
6
|
+
*
|
|
7
|
+
* Uses `reg.exe` directly instead of the `winreg` package — one fewer
|
|
8
|
+
* dependency, and the argument-array form removes v2's quoting problems.
|
|
9
|
+
*/
|
|
10
|
+
export class WindowsSystemProxy {
|
|
11
|
+
platform = 'win32';
|
|
12
|
+
async snapshot() {
|
|
13
|
+
const enabled = (await readValue('ProxyEnable')) === '0x1';
|
|
14
|
+
const server = (await readValue('ProxyServer')) ?? '';
|
|
15
|
+
const [host = '', port = ''] = server.split(':');
|
|
16
|
+
const override = (await readValue('ProxyOverride')) ?? '';
|
|
17
|
+
return {
|
|
18
|
+
platform: 'win32',
|
|
19
|
+
entries: [
|
|
20
|
+
{
|
|
21
|
+
target: KEY,
|
|
22
|
+
enabled,
|
|
23
|
+
host,
|
|
24
|
+
port: Number.parseInt(port, 10) || 0,
|
|
25
|
+
bypass: override.length > 0 ? override.split(';') : [],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async apply(settings) {
|
|
31
|
+
await writeValue('ProxyServer', 'REG_SZ', `${settings.host}:${String(settings.port)}`);
|
|
32
|
+
await writeValue('ProxyOverride', 'REG_SZ', settings.bypass.length > 0 ? settings.bypass.join(';') : '<local>');
|
|
33
|
+
// Enable last: until this flips, the values above are inert.
|
|
34
|
+
await writeValue('ProxyEnable', 'REG_DWORD', '1');
|
|
35
|
+
await refreshWinInet();
|
|
36
|
+
}
|
|
37
|
+
async restore(snapshot) {
|
|
38
|
+
const previous = snapshot.entries[0];
|
|
39
|
+
if (!previous)
|
|
40
|
+
return;
|
|
41
|
+
const on = previous.enabled && previous.host.length > 0;
|
|
42
|
+
await settle([
|
|
43
|
+
// Disable first, so nothing is routed at a half-written config.
|
|
44
|
+
async () => {
|
|
45
|
+
if (!on)
|
|
46
|
+
await writeValue('ProxyEnable', 'REG_DWORD', '0');
|
|
47
|
+
},
|
|
48
|
+
() => writeValue('ProxyServer', 'REG_SZ', on ? `${previous.host}:${String(previous.port)}` : ''),
|
|
49
|
+
() => writeValue('ProxyOverride', 'REG_SZ', (previous.bypass ?? []).join(';')),
|
|
50
|
+
async () => {
|
|
51
|
+
if (on)
|
|
52
|
+
await writeValue('ProxyEnable', 'REG_DWORD', '1');
|
|
53
|
+
},
|
|
54
|
+
// Always tell WinINet to re-read, even if a write above failed.
|
|
55
|
+
refreshWinInet,
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function readValue(name) {
|
|
60
|
+
const output = await tryExec(REG, ['query', KEY, '/v', name]);
|
|
61
|
+
// ` ProxyEnable REG_DWORD 0x1`
|
|
62
|
+
return output
|
|
63
|
+
?.split('\n')
|
|
64
|
+
.find((line) => line.includes(name))
|
|
65
|
+
?.trim()
|
|
66
|
+
.split(/\s{2,}/)[2];
|
|
67
|
+
}
|
|
68
|
+
async function writeValue(name, type, data) {
|
|
69
|
+
await exec(REG, ['add', KEY, '/v', name, '/t', type, '/d', data, '/f']);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Registry writes alone do not take effect until WinINet is told to re-read
|
|
73
|
+
* them. There is no CLI for `InternetSetOption`, so drive it through PowerShell
|
|
74
|
+
* — base64/UTF-16LE encoded to sidestep every layer of quoting.
|
|
75
|
+
*/
|
|
76
|
+
async function refreshWinInet() {
|
|
77
|
+
const script = `
|
|
78
|
+
$signature = @'
|
|
79
|
+
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
|
80
|
+
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
|
|
81
|
+
'@
|
|
82
|
+
$wininet = Add-Type -MemberDefinition $signature -Name WinInet -Namespace Native -PassThru
|
|
83
|
+
# 39 = INTERNET_OPTION_SETTINGS_CHANGED, 37 = INTERNET_OPTION_REFRESH
|
|
84
|
+
$wininet::InternetSetOption([IntPtr]::Zero, 39, [IntPtr]::Zero, 0) | Out-Null
|
|
85
|
+
$wininet::InternetSetOption([IntPtr]::Zero, 37, [IntPtr]::Zero, 0) | Out-Null
|
|
86
|
+
`.trim();
|
|
87
|
+
await tryExec('powershell.exe', [
|
|
88
|
+
'-NoProfile',
|
|
89
|
+
'-NonInteractive',
|
|
90
|
+
'-ExecutionPolicy',
|
|
91
|
+
'Bypass',
|
|
92
|
+
'-EncodedCommand',
|
|
93
|
+
Buffer.from(script, 'utf16le').toString('base64'),
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=windows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.js","sourceRoot":"","sources":["../../../src/core/system-proxy/windows.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FragmentSettings } from '../types.js';
|
|
2
|
+
export declare const TLS_HEADER_SIZE = 5;
|
|
3
|
+
/**
|
|
4
|
+
* True when `data` starts with a TLS handshake record carrying a ClientHello —
|
|
5
|
+
* i.e. the packet that contains the SNI a DPI box is looking for.
|
|
6
|
+
*
|
|
7
|
+
* v2 fragmented whatever the first client packet happened to be. Checking first
|
|
8
|
+
* means non-TLS tunnels (SSH over CONNECT, plain sockets) pass through intact.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isClientHello(data: Buffer): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Split a ClientHello so no single piece contains a complete SNI.
|
|
13
|
+
*
|
|
14
|
+
* Two strategies:
|
|
15
|
+
* - **TCP split** (`tlsRecords: false`) — one TLS record, chopped across
|
|
16
|
+
* several `write()` calls. Beats boxes that inspect single segments.
|
|
17
|
+
* - **Record split** (`tlsRecords: true`) — the handshake payload is re-framed
|
|
18
|
+
* into several individually valid TLS records. Beats boxes that reassemble
|
|
19
|
+
* the TCP stream but inspect record-by-record. Legal per RFC 8446 §5.1: a
|
|
20
|
+
* handshake message may span multiple records.
|
|
21
|
+
*
|
|
22
|
+
* Returns `[data]` unchanged when fragmentation is off or the payload is not a
|
|
23
|
+
* ClientHello.
|
|
24
|
+
*/
|
|
25
|
+
export declare function fragmentClientHello(data: Buffer, settings: FragmentSettings): Buffer[];
|
|
26
|
+
/** Chop a buffer into `size`-byte pieces. */
|
|
27
|
+
export declare function splitBytes(data: Buffer, size: number): Buffer[];
|
|
28
|
+
//# sourceMappingURL=fragment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragment.d.ts","sourceRoot":"","sources":["../../../src/core/tls/fragment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,eAAO,MAAM,eAAe,IAAI,CAAC;AAIjC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMnD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAOtF;AAED,6CAA6C;AAC7C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM/D"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const TLS_HEADER_SIZE = 5;
|
|
2
|
+
const CONTENT_TYPE_HANDSHAKE = 0x16;
|
|
3
|
+
const HANDSHAKE_TYPE_CLIENT_HELLO = 0x01;
|
|
4
|
+
/**
|
|
5
|
+
* True when `data` starts with a TLS handshake record carrying a ClientHello —
|
|
6
|
+
* i.e. the packet that contains the SNI a DPI box is looking for.
|
|
7
|
+
*
|
|
8
|
+
* v2 fragmented whatever the first client packet happened to be. Checking first
|
|
9
|
+
* means non-TLS tunnels (SSH over CONNECT, plain sockets) pass through intact.
|
|
10
|
+
*/
|
|
11
|
+
export function isClientHello(data) {
|
|
12
|
+
return (data.length > TLS_HEADER_SIZE &&
|
|
13
|
+
data[0] === CONTENT_TYPE_HANDSHAKE &&
|
|
14
|
+
data[TLS_HEADER_SIZE] === HANDSHAKE_TYPE_CLIENT_HELLO);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Split a ClientHello so no single piece contains a complete SNI.
|
|
18
|
+
*
|
|
19
|
+
* Two strategies:
|
|
20
|
+
* - **TCP split** (`tlsRecords: false`) — one TLS record, chopped across
|
|
21
|
+
* several `write()` calls. Beats boxes that inspect single segments.
|
|
22
|
+
* - **Record split** (`tlsRecords: true`) — the handshake payload is re-framed
|
|
23
|
+
* into several individually valid TLS records. Beats boxes that reassemble
|
|
24
|
+
* the TCP stream but inspect record-by-record. Legal per RFC 8446 §5.1: a
|
|
25
|
+
* handshake message may span multiple records.
|
|
26
|
+
*
|
|
27
|
+
* Returns `[data]` unchanged when fragmentation is off or the payload is not a
|
|
28
|
+
* ClientHello.
|
|
29
|
+
*/
|
|
30
|
+
export function fragmentClientHello(data, settings) {
|
|
31
|
+
if (!settings.enabled || settings.size < 1 || !isClientHello(data)) {
|
|
32
|
+
return [data];
|
|
33
|
+
}
|
|
34
|
+
return settings.tlsRecords
|
|
35
|
+
? splitIntoRecords(data, settings.size)
|
|
36
|
+
: splitBytes(data, settings.size);
|
|
37
|
+
}
|
|
38
|
+
/** Chop a buffer into `size`-byte pieces. */
|
|
39
|
+
export function splitBytes(data, size) {
|
|
40
|
+
const pieces = [];
|
|
41
|
+
for (let offset = 0; offset < data.length; offset += size) {
|
|
42
|
+
pieces.push(data.subarray(offset, offset + size));
|
|
43
|
+
}
|
|
44
|
+
return pieces;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Re-frame one TLS record's payload into N records of at most `size` bytes,
|
|
48
|
+
* reusing the original content type and legacy version.
|
|
49
|
+
*
|
|
50
|
+
* Anything after the first record (a coalesced second record, which does
|
|
51
|
+
* happen) is forwarded untouched as a final piece — v2 silently corrupted it by
|
|
52
|
+
* treating the whole buffer as one record's payload.
|
|
53
|
+
*/
|
|
54
|
+
function splitIntoRecords(data, size) {
|
|
55
|
+
const contentType = data[0];
|
|
56
|
+
const versionMajor = data[1];
|
|
57
|
+
const versionMinor = data[2];
|
|
58
|
+
if (contentType === undefined || versionMajor === undefined || versionMinor === undefined) {
|
|
59
|
+
return [data];
|
|
60
|
+
}
|
|
61
|
+
const declaredLength = data.readUInt16BE(3);
|
|
62
|
+
const payloadEnd = Math.min(TLS_HEADER_SIZE + declaredLength, data.length);
|
|
63
|
+
const payload = data.subarray(TLS_HEADER_SIZE, payloadEnd);
|
|
64
|
+
const records = [];
|
|
65
|
+
for (let offset = 0; offset < payload.length; offset += size) {
|
|
66
|
+
const slice = payload.subarray(offset, offset + size);
|
|
67
|
+
const record = Buffer.allocUnsafe(TLS_HEADER_SIZE + slice.length);
|
|
68
|
+
record[0] = contentType;
|
|
69
|
+
record[1] = versionMajor;
|
|
70
|
+
record[2] = versionMinor;
|
|
71
|
+
record.writeUInt16BE(slice.length, 3);
|
|
72
|
+
slice.copy(record, TLS_HEADER_SIZE);
|
|
73
|
+
records.push(record);
|
|
74
|
+
}
|
|
75
|
+
const trailing = data.subarray(payloadEnd);
|
|
76
|
+
if (trailing.length > 0) {
|
|
77
|
+
records.push(trailing);
|
|
78
|
+
}
|
|
79
|
+
return records.length > 0 ? records : [data];
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=fragment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragment.js","sourceRoot":"","sources":["../../../src/core/tls/fragment.ts"],"names":[],"mappings":"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"}
|