green-tunnel 1.8.3 → 2.0.0
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/bin/gt.js +17 -11
- package/package.json +19 -22
- package/src/dns/base.js +6 -5
- package/src/dns/https.js +20 -7
- package/src/dns/tls.js +1 -1
- package/src/dns/unencrypted.js +1 -1
- package/src/handlers/http.js +4 -4
- package/src/handlers/https.js +5 -5
- package/src/handlers/request.js +3 -3
- package/src/http/request.js +1 -1
- package/src/http/response.js +1 -1
- package/src/index.cjs +7 -0
- package/src/index.js +4 -4
- package/src/proxy.js +8 -8
- package/src/utils/analytics.js +57 -26
- package/src/utils/buffer.js +25 -1
- package/src/utils/socket.js +1 -1
- package/src/utils/system-proxy.js +4 -1
- package/src/index.cjs.js +0 -3
package/bin/gt.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const { Proxy, config, getLogger } = require('../src/index.cjs');
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import clear from 'clear';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import debug from 'debug';
|
|
7
|
+
import yargs from 'yargs';
|
|
8
|
+
import { hideBin } from 'yargs/helpers';
|
|
9
|
+
import { Proxy, config, getLogger } from '../src/index.js';
|
|
11
10
|
|
|
12
11
|
const logger = getLogger('cli');
|
|
13
12
|
|
|
14
|
-
const
|
|
13
|
+
const argv = yargs(hideBin(process.argv))
|
|
15
14
|
.usage('Usage: green-tunnel [options]')
|
|
16
15
|
.usage('Usage: gt [options]')
|
|
17
16
|
.alias('help', 'h')
|
|
@@ -76,11 +75,18 @@ const { argv } = yargs
|
|
|
76
75
|
default: true,
|
|
77
76
|
})
|
|
78
77
|
|
|
78
|
+
.option('tls-record-fragmentation', {
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
describe: 'enable TLS record fragmentation',
|
|
81
|
+
default: false
|
|
82
|
+
})
|
|
83
|
+
|
|
79
84
|
.example('$0')
|
|
80
85
|
.example('$0 --ip 127.0.0.1 --port 8000')
|
|
81
86
|
.example('$0 --dns-server https://doh.securedns.eu/dns-query')
|
|
82
87
|
.epilog('ISSUES: https://github.com/SadeghHayeri/GreenTunnel/issues\n' +
|
|
83
|
-
'DONATE: https://github.com/SadeghHayeri/GreenTunnel#donation')
|
|
88
|
+
'DONATE: https://github.com/SadeghHayeri/GreenTunnel#donation')
|
|
89
|
+
.parseSync();
|
|
84
90
|
|
|
85
91
|
const MAIN_COLOR = '84C66F';
|
|
86
92
|
|
|
@@ -128,6 +134,7 @@ async function main() {
|
|
|
128
134
|
port: argv['dns-port']
|
|
129
135
|
},
|
|
130
136
|
source: 'CLI',
|
|
137
|
+
'tlsRecordFragmentation': argv['tls-record-fragmentation']
|
|
131
138
|
});
|
|
132
139
|
|
|
133
140
|
const exitTrap = async () => {
|
|
@@ -155,7 +162,6 @@ async function main() {
|
|
|
155
162
|
if (!argv['silent'] && !argv['verbose']) {
|
|
156
163
|
clear();
|
|
157
164
|
printBanner();
|
|
158
|
-
updateNotifier({ pkg }).notify();
|
|
159
165
|
printAlert(proxy);
|
|
160
166
|
showSpinner();
|
|
161
167
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "green-tunnel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"HTTPS",
|
|
@@ -21,38 +21,35 @@
|
|
|
21
21
|
"bin",
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "src/index.js",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./src/index.js"
|
|
28
|
+
},
|
|
26
29
|
"bin": {
|
|
27
30
|
"green-tunnel": "bin/gt.js",
|
|
28
31
|
"gt": "bin/gt.js"
|
|
29
32
|
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20.0.0"
|
|
35
|
+
},
|
|
30
36
|
"scripts": {
|
|
31
37
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
32
38
|
},
|
|
33
39
|
"dependencies": {
|
|
34
|
-
"
|
|
35
|
-
"chalk": "^2.4.2",
|
|
40
|
+
"chalk": "^5.6.2",
|
|
36
41
|
"clear": "^0.1.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"dns-over-http": "^0.1.2",
|
|
40
|
-
"dns-over-tls": "0.0.6",
|
|
42
|
+
"debug": "^4.4.0",
|
|
43
|
+
"dns-over-tls": "^0.0.9",
|
|
41
44
|
"dns-socket": "^4.2.2",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"std-env": "^2.2.1",
|
|
48
|
-
"universal-analytics": "^0.4.20",
|
|
49
|
-
"update-notifier": "^2.5.0",
|
|
50
|
-
"validator": "^10.11.0",
|
|
51
|
-
"winreg": "^1.2.4",
|
|
52
|
-
"yargs": "^13.2.2"
|
|
45
|
+
"lru-cache": "^11.2.6",
|
|
46
|
+
"ora": "^9.3.0",
|
|
47
|
+
"validator": "^13.15.26",
|
|
48
|
+
"winreg": "^1.2.5",
|
|
49
|
+
"yargs": "^18.0.0"
|
|
53
50
|
},
|
|
54
51
|
"devDependencies": {
|
|
55
|
-
"eslint": "^
|
|
56
|
-
"eslint-config-xo": "^0.
|
|
52
|
+
"eslint": "^9.39.3",
|
|
53
|
+
"eslint-config-xo": "^0.49.0"
|
|
57
54
|
}
|
|
58
55
|
}
|
package/src/dns/base.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import
|
|
1
|
+
import { LRUCache } from 'lru-cache';
|
|
2
|
+
import validator from 'validator';
|
|
3
|
+
const { isIP } = validator;
|
|
4
|
+
import getLogger from '../logger.js';
|
|
5
|
+
import config from '../config.js';
|
|
5
6
|
|
|
6
7
|
const logger = getLogger('dns');
|
|
7
8
|
|
|
@@ -11,7 +12,7 @@ function _isIP(v) {
|
|
|
11
12
|
|
|
12
13
|
export default class BaseDNS {
|
|
13
14
|
constructor() {
|
|
14
|
-
this.cache = new
|
|
15
|
+
this.cache = new LRUCache({max: config.dns.cacheSize});
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
async lookup(hostname) {
|
package/src/dns/https.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import doh from 'dns-over-http';
|
|
3
|
-
import BaseDNS from './base';
|
|
4
|
-
|
|
5
|
-
const dohQueryAsync = promisify(doh.query);
|
|
1
|
+
import BaseDNS from './base.js';
|
|
6
2
|
|
|
7
3
|
export default class DNSOverHTTPS extends BaseDNS {
|
|
8
4
|
constructor(dnsServer) {
|
|
@@ -11,7 +7,24 @@ export default class DNSOverHTTPS extends BaseDNS {
|
|
|
11
7
|
}
|
|
12
8
|
|
|
13
9
|
async _lookup(hostname) {
|
|
14
|
-
const
|
|
15
|
-
|
|
10
|
+
const url = new URL(this.dnsServer);
|
|
11
|
+
url.searchParams.set('name', hostname);
|
|
12
|
+
url.searchParams.set('type', 'A');
|
|
13
|
+
|
|
14
|
+
const response = await fetch(url.toString(), {
|
|
15
|
+
headers: {Accept: 'application/dns-json'},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new Error(`DoH request failed: ${response.status}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await response.json();
|
|
23
|
+
|
|
24
|
+
if (!result.Answer || result.Answer.length === 0) {
|
|
25
|
+
throw new Error(`No DNS answers for ${hostname}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return result.Answer[0].data;
|
|
16
29
|
}
|
|
17
30
|
}
|
package/src/dns/tls.js
CHANGED
package/src/dns/unencrypted.js
CHANGED
package/src/handlers/http.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {URL} from 'url';
|
|
2
|
-
import {isStartOfHTTPRequest} from '../http/utils';
|
|
3
|
-
import {createConnection, closeSocket, tryWrite} from '../utils/socket';
|
|
4
|
-
import HTTPRequest from '../http/request';
|
|
5
|
-
import getLogger from '../logger';
|
|
2
|
+
import {isStartOfHTTPRequest} from '../http/utils.js';
|
|
3
|
+
import {createConnection, closeSocket, tryWrite} from '../utils/socket.js';
|
|
4
|
+
import HTTPRequest from '../http/request.js';
|
|
5
|
+
import getLogger from '../logger.js';
|
|
6
6
|
|
|
7
7
|
const logger = getLogger('https-handler');
|
|
8
8
|
|
package/src/handlers/https.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {URL} from 'url';
|
|
2
|
-
import {bufferToChunks} from '../utils/buffer';
|
|
3
|
-
import {createConnection, closeSocket, tryWrite} from '../utils/socket';
|
|
4
|
-
import HTTPResponse from '../http/response';
|
|
5
|
-
import getLogger from '../logger';
|
|
2
|
+
import {bufferToChunks} from '../utils/buffer.js';
|
|
3
|
+
import {createConnection, closeSocket, tryWrite} from '../utils/socket.js';
|
|
4
|
+
import HTTPResponse from '../http/response.js';
|
|
5
|
+
import getLogger from '../logger.js';
|
|
6
6
|
|
|
7
7
|
const logger = getLogger('https-handler');
|
|
8
8
|
|
|
@@ -43,7 +43,7 @@ export default async function handleHTTPS(clientSocket, firstChunk, proxy) {
|
|
|
43
43
|
// -- clientSocket --
|
|
44
44
|
|
|
45
45
|
clientSocket.once('data', clientHello => {
|
|
46
|
-
const chunks = bufferToChunks(clientHello, proxy.config.clientHelloMTU);
|
|
46
|
+
const chunks = bufferToChunks(clientHello, proxy.config.clientHelloMTU, proxy.config.tlsRecordFragmentation);
|
|
47
47
|
for (const chunk of chunks) {
|
|
48
48
|
logger.debug(`[HTTPS HELLO] ${url.host} (length: ${chunk.length})`);
|
|
49
49
|
tryWrite(serverSocket, chunk, close);
|
package/src/handlers/request.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {isStartOfHTTPRequest, isConnectMethod} from '../http/utils';
|
|
2
|
-
import handleHTTP from './http';
|
|
3
|
-
import handleHTTPS from './https';
|
|
1
|
+
import {isStartOfHTTPRequest, isConnectMethod} from '../http/utils.js';
|
|
2
|
+
import handleHTTP from './http.js';
|
|
3
|
+
import handleHTTPS from './https.js';
|
|
4
4
|
|
|
5
5
|
export default async function handleRequest(clientSocket, proxy) {
|
|
6
6
|
clientSocket.resume();
|
package/src/http/request.js
CHANGED
package/src/http/response.js
CHANGED
package/src/index.cjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// CJS interop shim — green-tunnel is now an ES module.
|
|
3
|
+
// CommonJS callers must use dynamic import:
|
|
4
|
+
// const gt = await import('green-tunnel');
|
|
5
|
+
throw new Error(
|
|
6
|
+
'green-tunnel is an ES module. Use `import` (or dynamic `await import(\'green-tunnel\')`) instead of `require()`.'
|
|
7
|
+
);
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {default as Proxy} from './proxy';
|
|
2
|
-
export {default as config} from './config';
|
|
3
|
-
export {default as handleRequest} from './handlers/request';
|
|
4
|
-
export {default as getLogger} from './logger';
|
|
1
|
+
export {default as Proxy} from './proxy.js';
|
|
2
|
+
export {default as config} from './config.js';
|
|
3
|
+
export {default as handleRequest} from './handlers/request.js';
|
|
4
|
+
export {default as getLogger} from './logger.js';
|
package/src/proxy.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
|
-
import { setProxy, unsetProxy } from './utils/system-proxy';
|
|
3
|
-
import handleRequest from './handlers/request';
|
|
4
|
-
import DNSOverTLS from './dns/tls';
|
|
5
|
-
import DNSOverHTTPS from './dns/https';
|
|
6
|
-
import DNSUnencrypted from './dns/unencrypted';
|
|
7
|
-
import config from './config';
|
|
8
|
-
import getLogger from './logger';
|
|
9
|
-
import { appInit } from './utils/analytics';
|
|
2
|
+
import { setProxy, unsetProxy } from './utils/system-proxy.js';
|
|
3
|
+
import handleRequest from './handlers/request.js';
|
|
4
|
+
import DNSOverTLS from './dns/tls.js';
|
|
5
|
+
import DNSOverHTTPS from './dns/https.js';
|
|
6
|
+
import DNSUnencrypted from './dns/unencrypted.js';
|
|
7
|
+
import config from './config.js';
|
|
8
|
+
import getLogger from './logger.js';
|
|
9
|
+
import { appInit } from './utils/analytics.js';
|
|
10
10
|
|
|
11
11
|
const logger = getLogger('proxy');
|
|
12
12
|
|
package/src/utils/analytics.js
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
import packageJson from '../../package.json' with { type: 'json' };
|
|
6
|
+
|
|
7
|
+
const MEASUREMENT_ID = 'G-D1R7M2YZ8Q';
|
|
8
|
+
|
|
9
|
+
// Get this from: GA4 Admin → Data Streams → select stream → Measurement Protocol API secrets → Create
|
|
10
|
+
const API_SECRET = 'Y_lvWjBxSG2-k5UYBJQQ7Q';
|
|
11
|
+
|
|
12
|
+
const CONFIG_DIR = join(homedir(), '.config', 'greentunnel');
|
|
13
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
14
|
+
|
|
15
|
+
function getClientId() {
|
|
16
|
+
try {
|
|
17
|
+
mkdirSync(CONFIG_DIR, {recursive: true});
|
|
18
|
+
let config = {};
|
|
19
|
+
if (existsSync(CONFIG_FILE)) {
|
|
20
|
+
config = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!config.clientId) {
|
|
24
|
+
config.clientId = randomUUID();
|
|
25
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return config.clientId;
|
|
29
|
+
} catch {
|
|
30
|
+
return randomUUID();
|
|
31
|
+
}
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
async function sendEvent(name, params = {}) {
|
|
35
|
+
if (!API_SECRET) return;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await fetch(
|
|
39
|
+
`https://www.google-analytics.com/mp/collect?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`,
|
|
40
|
+
{
|
|
41
|
+
method: 'POST',
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
client_id: getClientId(),
|
|
44
|
+
events: [{name, params}],
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
} catch {
|
|
49
|
+
// Analytics failures must never crash the app
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function appInit(source = 'OTHER') {
|
|
54
|
+
sendEvent('app_init', {
|
|
55
|
+
source,
|
|
56
|
+
version: packageJson.version,
|
|
57
|
+
platform: process.platform,
|
|
58
|
+
});
|
|
59
|
+
}
|
package/src/utils/buffer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export function bufferToChunks(buffer, chunkSize) {
|
|
1
|
+
export function bufferToChunks(buffer, chunkSize, recordFragmentation) {
|
|
2
|
+
if(recordFragmentation) buffer=tlsRecordFragmentation(buffer, chunkSize)
|
|
2
3
|
const result = [];
|
|
3
4
|
const len = buffer.length;
|
|
4
5
|
let i = 0;
|
|
@@ -9,3 +10,26 @@ export function bufferToChunks(buffer, chunkSize) {
|
|
|
9
10
|
|
|
10
11
|
return result;
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* @param {Buffer} buffer
|
|
15
|
+
* @param {number} chunkSize
|
|
16
|
+
*/
|
|
17
|
+
function tlsRecordFragmentation(buffer, chunkSize) {
|
|
18
|
+
const list = []
|
|
19
|
+
const header = buffer.subarray(0,3)
|
|
20
|
+
const fullRecord = buffer.subarray(5)
|
|
21
|
+
const len = fullRecord.length
|
|
22
|
+
let i = 0
|
|
23
|
+
|
|
24
|
+
while (i < len) {
|
|
25
|
+
const record = fullRecord.subarray(i, i+chunkSize)
|
|
26
|
+
const recordLength = record.length
|
|
27
|
+
const buf = Buffer.alloc(recordLength+5)
|
|
28
|
+
header.copy(buf)
|
|
29
|
+
buf.writeUInt16BE(recordLength,3)
|
|
30
|
+
record.copy(buf,5)
|
|
31
|
+
list.push(buf)
|
|
32
|
+
i+=chunkSize
|
|
33
|
+
}
|
|
34
|
+
return Buffer.concat(list)
|
|
35
|
+
}
|
package/src/utils/socket.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import util from 'util';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
4
5
|
import {exec as _exec, spawn} from 'child_process';
|
|
5
6
|
import Registry from 'winreg';
|
|
6
|
-
import getLogger from '../logger';
|
|
7
|
+
import getLogger from '../logger.js';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
10
|
|
|
8
11
|
const logger = getLogger('system-proxy');
|
|
9
12
|
const exec = util.promisify(_exec);
|
package/src/index.cjs.js
DELETED