green-tunnel 1.8.3 → 2.0.1
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/README.md +122 -114
- 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/README.md
CHANGED
|
@@ -1,160 +1,168 @@
|
|
|
1
1
|
# Green Tunnel
|
|
2
|
+
|
|
2
3
|
<p align="center">
|
|
3
4
|
<img src="assets/logo.png" alt="green tunnel logo" width="200">
|
|
4
5
|
</p>
|
|
5
6
|
<p align="center">
|
|
6
|
-
<img src="https://img.shields.io/github/license/SadeghHayeri/GreenTunnel.svg?color=Green&style=for-the-badge">
|
|
7
|
+
<img src="https://img.shields.io/github/license/SadeghHayeri/GreenTunnel.svg?color=Green&style=for-the-badge">
|
|
8
|
+
<img src="https://img.shields.io/npm/v/green-tunnel?color=Green&style=for-the-badge">
|
|
9
|
+
<img src="https://img.shields.io/github/repo-size/SadeghHayeri/GreenTunnel.svg?color=Green&style=for-the-badge">
|
|
7
10
|
</p>
|
|
8
11
|
|
|
9
12
|
GreenTunnel bypasses DPI (Deep Packet Inspection) systems found in many ISPs (Internet Service Providers) which block access to certain websites.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
<img src="assets/demo.gif" alt="green tunnel demo" style="margin-top: 20px;">
|
|
13
|
-
</p>
|
|
14
|
+
> **Note:** GreenTunnel does not hide your IP address. It only bypasses DPI-based censorship.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
### Graphical user interface (GUI)
|
|
17
|
-
You can simply choose the suitable installation for your OS in the [releases](https://github.com/SadeghHayeri/GreenTunnel/releases "releases") section.
|
|
16
|
+
---
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
<table>
|
|
19
|
+
<tr>
|
|
20
|
+
<td width="340"><img src="assets/new-version.png" alt="GreenTunnel v2.0.0 new UI" width="320"></td>
|
|
21
|
+
<td valign="top" style="padding-left: 24px;">
|
|
22
|
+
|
|
23
|
+
**GreenTunnel v2 — with the help of [Claude Code](https://claude.ai/claude-code) — is out now.**
|
|
24
|
+
|
|
25
|
+
After years of silence, this project is alive again. The entire codebase has been rebuilt from the ground up: native ESM, Node.js 20+, a brand new dark UI, and a clean dependency tree with zero known vulnerabilities.
|
|
26
|
+
|
|
27
|
+
Open-source tools that help people reach the free internet shouldn't die — and with AI-assisted development, they don't have to. We'll keep doing our best to help people access the open internet, one packet at a time.
|
|
28
|
+
|
|
29
|
+
</td>
|
|
30
|
+
</tr>
|
|
31
|
+
</table>
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
### Requirements
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
- **Node.js 20+**
|
|
40
|
+
|
|
41
|
+
### npm (recommended)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g green-tunnel
|
|
26
45
|
```
|
|
27
|
-
|
|
46
|
+
|
|
47
|
+
After installation, run with `gt` or `green-tunnel`.
|
|
48
|
+
|
|
49
|
+
### Docker
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
docker run -p 8000:8000 sadeghhayeri/greentunnel
|
|
28
53
|
```
|
|
29
54
|
|
|
55
|
+
---
|
|
30
56
|
|
|
31
|
-
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### CLI
|
|
32
60
|
|
|
33
61
|
```
|
|
34
|
-
$ gt --help
|
|
35
62
|
Usage: green-tunnel [options]
|
|
36
63
|
Usage: gt [options]
|
|
37
64
|
|
|
38
65
|
Options:
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--
|
|
43
|
-
--
|
|
44
|
-
--dns-
|
|
45
|
-
--dns-
|
|
46
|
-
--
|
|
47
|
-
--
|
|
48
|
-
--
|
|
49
|
-
--
|
|
50
|
-
--
|
|
51
|
-
|
|
52
|
-
Examples:
|
|
53
|
-
gt
|
|
54
|
-
gt --ip 127.0.0.1 --port 8000 --https-only
|
|
55
|
-
gt --dns-server https://doh.securedns.eu/dns-query
|
|
56
|
-
gt --verbose 'green-tunnel:proxy*'
|
|
57
|
-
|
|
58
|
-
ISSUES: https://github.com/SadeghHayeri/GreenTunnel/issues
|
|
66
|
+
--ip IP address to bind proxy server [string] [default: "127.0.0.1"]
|
|
67
|
+
--port Port to bind proxy server [number] [default: 8000]
|
|
68
|
+
--https-only Block insecure HTTP requests [boolean] [default: false]
|
|
69
|
+
--dns-type DNS resolver type [string] [choices: "https", "tls", "unencrypted"] [default: "https"]
|
|
70
|
+
--dns-server DNS server URL [string] [default: "https://cloudflare-dns.com/dns-query"]
|
|
71
|
+
--dns-ip IP for unencrypted DNS [string] [default: "127.0.0.1"]
|
|
72
|
+
--dns-port Port for unencrypted DNS [number] [default: 53]
|
|
73
|
+
--tls-record-frag... Enable TLS record fragmentation [boolean] [default: false]
|
|
74
|
+
--silent, -s Run in silent mode [boolean] [default: false]
|
|
75
|
+
--verbose, -v Debug mode (e.g. 'green-tunnel:*') [string]
|
|
76
|
+
--system-proxy Auto-set system proxy [boolean] [default: true]
|
|
77
|
+
--help, -h Show help
|
|
78
|
+
--version, -V Show version number
|
|
59
79
|
```
|
|
60
80
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
81
|
+
**Examples:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Basic usage (auto-sets system proxy)
|
|
85
|
+
gt
|
|
86
|
+
|
|
87
|
+
# Custom port
|
|
88
|
+
gt --port 9000
|
|
89
|
+
|
|
90
|
+
# Use a different DoH server
|
|
91
|
+
gt --dns-server https://doh.securedns.eu/dns-query
|
|
92
|
+
|
|
93
|
+
# Enable TLS record fragmentation (for stricter DPI)
|
|
94
|
+
gt --tls-record-fragmentation
|
|
95
|
+
|
|
96
|
+
# Debug mode
|
|
97
|
+
gt --verbose 'green-tunnel:*'
|
|
64
98
|
```
|
|
65
99
|
|
|
66
100
|
### Docker
|
|
67
|
-
```
|
|
68
|
-
$ docker run -p 8000:8000 sadeghhayeri/green-tunnel
|
|
69
|
-
```
|
|
70
|
-
> **envs**
|
|
71
|
-
* PORT
|
|
72
|
-
* HTTPS-ONLY
|
|
73
|
-
* VERBOSE
|
|
74
|
-
* SILENT
|
|
75
|
-
* DNS_TYPE
|
|
76
|
-
* DNS_SERVER
|
|
77
|
-
|
|
78
|
-
usage:
|
|
79
|
-
```
|
|
80
|
-
$ docker run -e 'PORT=1000' -p 8000:1000 sadeghhayeri/green-tunnel
|
|
81
|
-
```
|
|
82
101
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```
|
|
102
|
+
```bash
|
|
103
|
+
# Basic
|
|
104
|
+
docker run -p 8000:8000 sadeghhayeri/greentunnel
|
|
87
105
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
# Custom port
|
|
107
|
+
docker run -e PORT=9000 -p 9000:9000 sadeghhayeri/greentunnel
|
|
108
|
+
|
|
109
|
+
# Run in background, restart on reboot
|
|
110
|
+
docker run -d --restart unless-stopped -p 8000:8000 sadeghhayeri/greentunnel
|
|
91
111
|
```
|
|
92
112
|
|
|
93
|
-
|
|
113
|
+
**Environment variables:**
|
|
94
114
|
|
|
95
|
-
|
|
115
|
+
| Variable | Description | Default |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `PORT` | Proxy port | `8000` |
|
|
118
|
+
| `HTTPS_ONLY` | Block HTTP traffic | `false` |
|
|
119
|
+
| `DNS_TYPE` | `https`, `tls`, or `unencrypted` | `https` |
|
|
120
|
+
| `DNS_SERVER` | DNS server URL | Cloudflare DoH |
|
|
121
|
+
| `SILENT` | Suppress output | `false` |
|
|
122
|
+
| `VERBOSE` | Debug namespace | — |
|
|
96
123
|
|
|
97
|
-
###
|
|
98
|
-
- MacOS Catalina with node 12
|
|
99
|
-
- Ubuntu 18.04 with node 8
|
|
100
|
-
- Windows 10 with node 8
|
|
124
|
+
### Graphical Interface (GUI)
|
|
101
125
|
|
|
126
|
+
Download the pre-built installer for your OS from the [releases](https://github.com/SadeghHayeri/GreenTunnel/releases) page.
|
|
102
127
|
|
|
103
|
-
|
|
104
|
-
> **How does it work?**
|
|
105
|
-
###### HTTP
|
|
106
|
-
There are gaps in providers in DPI. They happen from what the DPI rules write for ordinary user programs, omitting all possible cases that are permissible by standards. This is done for simplicity and speed.
|
|
107
|
-
Some DPIs cannot recognize the HTTP request if it is divided into TCP segments. For example, a request of the form
|
|
128
|
+
---
|
|
108
129
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
130
|
+
## How It Works
|
|
131
|
+
|
|
132
|
+
### HTTP
|
|
133
|
+
|
|
134
|
+
Some DPI systems fail to detect blocked content when an HTTP request is split across multiple TCP segments. GreenTunnel splits the request so the `Host` header straddles a segment boundary, preventing the DPI from matching the blocked hostname.
|
|
135
|
+
|
|
136
|
+
### HTTPS / SNI Fragmentation
|
|
137
|
+
|
|
138
|
+
TLS's Server Name Indication (SNI) extension sends the target hostname in plaintext during the handshake. DPI systems use this to block HTTPS connections. GreenTunnel splits the initial `ClientHello` TLS record into small fragments so the DPI cannot reassemble and inspect the SNI field.
|
|
139
|
+
|
|
140
|
+
Optionally, `--tls-record-fragmentation` breaks the TLS record at a lower level for stricter DPI environments.
|
|
115
141
|
|
|
116
|
-
|
|
117
|
-
Server Name Indication (SNI) is an extension to TLS (Transport Layer Security) that indicates the actual destination hostname a client is attempting to access over HTTPS. For this Web Filter feature, SNI hostname information is used for blocking access to specific sites over HTTPS. For example, if the administrator chooses to block the hostname **youtube** using this feature, all Website access attempts over HTTPS that contain **youtube** like **www.youtube.com** in the SNI would be blocked. However, access to the same hostname over HTTP would not be blocked by this feature. GreenTunnel tries to split first **CLIENT-HELLO** packet into small chunks and ISPs can't parse packet and found SNI field so bypass traffic!
|
|
142
|
+
### Encrypted DNS
|
|
118
143
|
|
|
119
|
-
|
|
120
|
-
When you enter a URL in a Web browser, the first thing the Web browser does is to ask a DNS (Domain Name System) server, at a known numeric address, to look up the domain name referenced in the URL and supply the corresponding IP address.
|
|
121
|
-
If the DNS server is configured to block access, it consults a blacklist of banned domain names. When a browser requests the IP address for one of these domain names, the DNS server gives a wrong answer or no answer at all.
|
|
122
|
-
GreenTunnel use [DNS over HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS "doh (DNS over HTTPS)") and [DNS over TLS](https://en.wikipedia.org/wiki/DNS_over_TLS "DNS over TLS") to get real IP address and bypass DNS Spoofing.
|
|
144
|
+
Standard DNS lookups can be intercepted or spoofed by ISPs to block domains at the DNS level. GreenTunnel uses **DNS over HTTPS (DoH)** or **DNS over TLS (DoT)** to get the real IP address, bypassing DNS-based blocking.
|
|
123
145
|
|
|
124
|
-
|
|
125
|
-
GreenTunnel is an open-source app and I really appreciate other developers adding new features and/or helping fix bugs. If you want to contribute to GreenTunnel, you can fork this repository, make the changes and create a pull request.
|
|
146
|
+
---
|
|
126
147
|
|
|
127
|
-
|
|
148
|
+
## Contributing
|
|
128
149
|
|
|
129
|
-
|
|
130
|
-
- For bugs and feature requests, please create an issue.
|
|
131
|
-
- Make sure your pull request has an informative title. You should use prefixes like `ADD:`, `FIX:`, etc at the start of the title which describes the changes followed by a one-line description of the changes. Example: ADD: Added a new feature to GreenTunnel
|
|
132
|
-
- Commits in your fork should be informative, as well. Make sure you don't combine too many changes into a single commit.
|
|
150
|
+
Pull requests and issues are always welcome.
|
|
133
151
|
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- [ ] add preferences menu
|
|
140
|
-
- [ ] fix close button
|
|
152
|
+
- Use `FIX:`, `ADD:`, `UPDATE:` prefixes in PR titles.
|
|
153
|
+
- Keep commits focused and descriptive.
|
|
154
|
+
- Make sure `npm install` passes and `node -e "import('./src/index.js')"` works.
|
|
155
|
+
|
|
156
|
+
---
|
|
141
157
|
|
|
142
158
|
## Donation
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
**RIPPLE/XRP:** rt6ZTkKdBVYzBee9CpQsDTsewnTbAoV13<br />
|
|
150
|
-
|
|
151
|
-
[](https://payping.ir/d/TXTS)
|
|
152
|
-
<br />
|
|
153
|
-
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=HJ5TBXVYTHS7N¤cy_code=USD&source=url)
|
|
154
|
-
<br />
|
|
155
|
-
[](https://en.cryptobadges.io/donate/bc1qknjsmsa98lljwxjwl4pmjh48s8su8r8ajkqd8w)
|
|
156
|
-
[](https://en.cryptobadges.io/donate/0x018fbf3fAC7165b2c85f856cC90E2d9410415150)
|
|
157
|
-
[](https://en.cryptobadges.io/donate/rt6ZTkKdBVYzBee9CpQsDTsewnTbAoV13)
|
|
159
|
+
|
|
160
|
+
> Love GreenTunnel? Please consider donating to sustain development.
|
|
161
|
+
|
|
162
|
+
**Ethereum / USDT (ERC-20):** `0xB116a6AE50c38a455944A65f9cEE4D54CEceF080`
|
|
163
|
+
|
|
164
|
+
---
|
|
158
165
|
|
|
159
166
|
## License
|
|
160
|
-
|
|
167
|
+
|
|
168
|
+
Licensed under the [MIT License](https://github.com/SadeghHayeri/GreenTunnel/blob/main/LICENSE).
|
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.1",
|
|
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