block-proxy 0.1.1 → 0.1.4

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 CHANGED
@@ -18,27 +18,42 @@
18
18
 
19
19
  ### 1)使用方法
20
20
 
21
- #### ① 快速部署
21
+ #### ① 方式一:快速部署
22
22
 
23
23
  安装:
24
24
 
25
- `npm install -g block-proxy`
25
+ ```
26
+ npm install -g block-proxy
27
+ ```
26
28
 
27
29
  启动:
28
30
 
29
- `block-proxy`
31
+ ```
32
+ block-proxy
33
+ ```
34
+
35
+ 或者带配置文件启动
36
+
37
+ ```
38
+ block-proxy -c rule.js
39
+ ```
40
+
41
+ 配置文件参照 [rule.js](example/rule.js),可留空
30
42
 
31
- #### ② Docker 部署(推荐)
43
+ #### ② 方式二,Docker 部署(推荐)
32
44
 
33
45
  1. 下载 Docker 文件
34
46
  - Arm 架构 → <a href="http://yui.cool:7001/public/downloads/block-proxy/arm/block-proxy.tar" target=_blank>block-proxy-arm.tar</a>
35
47
  - X86 架构 → <a href="http://yui.cool:7001/public/downloads/block-proxy/x86/block-proxy-x86.tar" target=_blank>block-proxy-x86.tar</a>
36
48
  2. 导入:`docker load < block-proxy.tar`
37
49
  3. 启动:参照下文 Docker 部署
38
- 4. 服务端配置:配置面板 <http://server-ip:8004>,关闭、启用配置面板:<http://server-ip:8001>
39
- 5. 客户端配置:http 代理直接在 iphone wifi 详情里手动配置,socks5 代理只支持 socks5 over TLS,用小火箭配置。配置信息参照[配置面板](http://localhost:8004)
40
50
 
41
- ### 2)开发和调试
51
+ ### 2)端口配置
52
+
53
+ 1. 服务端配置:配置面板 <http://server-ip:8004>,关闭、启用配置面板:<http://server-ip:8001>
54
+ 2. 客户端配置:http 代理直接在 iphone wifi 详情里手动配置,socks5 代理只支持 socks5 over TLS,用小火箭配置。配置信息参照[配置面板](http://localhost:8004)
55
+
56
+ ### 3)开发和调试
42
57
 
43
58
  代码 clone 下来后执行`pnpm i`,执行 `npm run dev` 运行本地服务。默认开启 5 个端口:
44
59
 
@@ -51,7 +66,7 @@
51
66
  |8004 |后台配置页端口 | 可禁用 |
52
67
 
53
68
 
54
- ### 3)Docker 构建和部署
69
+ ### 4)Docker 构建说明
55
70
 
56
71
  准备工作,构建 docker 包,先启动本地 Docker:
57
72
 
@@ -77,9 +92,12 @@ docker run --init -d --restart=unless-stopped \
77
92
  --log-opt max-file=3 \
78
93
  --cpus="5" \
79
94
  --memory 400m \
95
+ -v "$(pwd)/":/app/config \
80
96
  --name block-proxy block-proxy
81
97
  ```
82
98
 
99
+ 其中挂载目录 `$(pws)/` 下的 `rule.js` 是需要额外挂载的配置文件,可留空。
100
+
83
101
  > block-proxy 可以配置只启动 proxy 不启动后台面板,首次启动后访问 http://代理IP:8001 根据提示操作。
84
102
 
85
103
  网关里为了方便获取子网机器 ip 和 mac 地址,docker 容器需要和宿主机共享同一个网络,同时指定时区。
@@ -88,14 +106,15 @@ docker run --init -d --restart=unless-stopped \
88
106
 
89
107
  ```
90
108
  docker run --init -d --restart=unless-stopped --user=root \
109
+ -v "$(pwd)/":/app/config \
91
110
  -e TZ=Asia/Shanghai -p 8001:8001 -p 8002:8002 -p 8003:8003 \
92
111
  --name block-proxy block-proxy
93
112
  ```
94
113
 
95
114
 
96
- ### 4)配置说明
115
+ ### 5)配置说明
97
116
 
98
- #### 代理端口
117
+ #### 代理端口
99
118
 
100
119
  默认开启两个代理端口:HTTP 8001 和 Socks5(over TLS) 8002。
101
120
 
@@ -103,7 +122,7 @@ docker run --init -d --restart=unless-stopped --user=root \
103
122
 
104
123
  ⚠️ 使用小火箭的 Socks5 over TLS 代理,TLS 选项里勾选“允许不安全”
105
124
 
106
- #### 后台配置
125
+ #### 后台配置
107
126
 
108
127
  访问路径:`http://proxy-ip:8004`
109
128
 
@@ -112,16 +131,16 @@ docker run --init -d --restart=unless-stopped --user=root \
112
131
  <img src="https://github.com/user-attachments/assets/16f47d3f-1ef9-47a2-8640-c7e04ec64e1a" width=300 />
113
132
 
114
133
 
115
- #### 设备配置
134
+ #### 设备配置
116
135
 
117
136
  1. 代理设置:iPhone/iPad 为例:设置 → 无线局域网 → 点击当前网络 → HTTP代理/配置代理,设置服务器和端口。
118
- 2. 证书设置:打开anproxy监控地址(8003端口),扫码安装证书,在手机设置中安装该证书,同时配置完全信任:设置→通用→关于本机→证书信任设置→打开对AnyProxy的完全信任
137
+ 2. 证书设置:打开 anproxy 监控地址(8003端口),扫码安装证书,在手机设置中安装该证书,同时配置完全信任:设置→通用→关于本机→证书信任设置→打开对AnyProxy的完全信任
119
138
 
120
139
  小朋友的设备里把 Mac 固定下来:
121
140
 
122
141
  <img width="350" alt="image" src="https://github.com/user-attachments/assets/f9bfab89-7194-4a72-b1ae-5cca27911bc9" />
123
142
 
124
- #### 禁掉设备直连
143
+ #### 禁掉设备直连
125
144
 
126
145
  防止小朋友修改网Wifi连接,只允许设备通过代理访问,把直连上网权限关掉。网关里配置防火墙规则:
127
146
 
@@ -133,7 +152,7 @@ ip6tables -I forwarding_rule -m mac --mac-source D2:9E:8D:1B:F1:4E -j REJECT
133
152
  然后重启防火墙
134
153
 
135
154
 
136
- ### 5)使用说明
155
+ ### 6)更多信息
137
156
 
138
157
  #### 应用条件:
139
158
 
package/bin/start.js CHANGED
@@ -2,44 +2,124 @@
2
2
 
3
3
  const { spawn } = require('child_process');
4
4
  const path = require('path');
5
+ const { Command } = require('commander');
6
+ const program = new Command();
7
+ const _fs = require('../proxy/fs.js');
5
8
 
6
- // 获取包根目录和 start.js 路径
7
9
  const pkgDir = path.join(__dirname, '..');
8
10
  const startScript = path.resolve(pkgDir, 'server/start.js');
11
+ const MAX_RESTARTS = 10000;
12
+ let restartCount = 0;
13
+ let restartTimer = null;
14
+ let currentChild = null; // 👈 全局引用当前子进程
9
15
 
10
- // 构造命令:先运行 npm run cp,再运行 node server/start.js
11
- const command = `npm run cp && node "${startScript}"`;
16
+ function startApp() {
17
+ const command = `npm run cp && node "${startScript}"`;
18
+ console.error(`[💟] Block-Proxy 启动 (第 ${restartCount + 1} 次): ${command}`);
12
19
 
13
- console.error(`[💟] Block-Proxy 启动: ${command}`);
20
+ currentChild = spawn(command, {
21
+ cwd: pkgDir,
22
+ shell: true,
23
+ stdio: 'pipe'
24
+ });
14
25
 
15
- // 使用 spawn + shell: true 来支持 && 和管道
16
- const child = spawn(command, {
17
- cwd: pkgDir,
18
- shell: true, // ⭐ 必须启用 shell 才能解析 &&、|、> 等
19
- stdio: 'pipe' // 我们要手动处理流
20
- });
26
+ currentChild.stdout.on('data', (data) => {
27
+ process.stdout.write(data);
28
+ });
21
29
 
22
- // 实时输出 stdout(流式)
23
- child.stdout.on('data', (data) => {
24
- process.stdout.write(data); // 直接写到父进程 stdout
25
- });
30
+ currentChild.stderr.on('data', (data) => {
31
+ process.stderr.write(data);
32
+ });
26
33
 
27
- // 实时输出 stderr(流式)
28
- child.stderr.on('data', (data) => {
29
- process.stderr.write(data);
30
- });
34
+ currentChild.on('close', async (code, signal) => {
35
+ currentChild = null; // 清空引用
36
+ if (restartTimer) {
37
+ clearTimeout(restartTimer);
38
+ restartTimer = null;
39
+ }
40
+
41
+ await _fs.clearGlobalConfigFile();
42
+
43
+ if (code === 0) {
44
+ console.error('[block proxy] 正常退出,不重启。');
45
+ process.exit(0);
46
+ return;
47
+ }
48
+
49
+ if (signal === 'SIGINT' || signal === 'SIGTERM') {
50
+ console.error('[block-proxy] 被信号终止,不重启。');
51
+ process.exit(128 + (signal === 'SIGINT' ? 2 : 15));
52
+ return;
53
+ }
54
+
55
+ if (restartCount < MAX_RESTARTS) {
56
+ restartCount++;
57
+ console.error(`[block proxy] 将在 3 秒后自动重启...(已重启 ${restartCount}/${MAX_RESTARTS} 次)`);
58
+ restartTimer = setTimeout(() => {
59
+ restartTimer = null;
60
+ startApp();
61
+ }, 3000);
62
+ } else {
63
+ console.error(`[block proxy] 已达到最大重启次数 (${MAX_RESTARTS}),停止尝试。`);
64
+ process.exit(1);
65
+ }
66
+ });
67
+ }
31
68
 
32
- // 处理子进程退出
33
- child.on('close', (code, signal) => {
34
- console.error(`\n[my_app] Process exited with code ${code}`);
35
- process.exit(code || 0);
69
+ // ✅ 只注册一次 SIGINT 监听器(在 startApp 外部!)
70
+ process.on('SIGINT', async () => {
71
+ console.error('\n[block proxy] 收到 SIGINT,正在关闭子进程...');
72
+
73
+ await _fs.clearGlobalConfigFile();
74
+
75
+ if (restartTimer) {
76
+ clearTimeout(restartTimer);
77
+ restartTimer = null;
78
+ }
79
+
80
+ if (currentChild) {
81
+ currentChild.kill('SIGINT');
82
+ // 注意:不要在这里 exit,等 close 事件处理
83
+ } else {
84
+ // 如果没有子进程,直接退出
85
+ process.exit(0);
86
+ }
36
87
  });
37
88
 
38
- // 转发 Ctrl+C 信号(重要!)
39
- process.on('SIGINT', () => {
40
- console.error('\n[my_app] Received SIGINT, shutting down...');
41
- child.kill('SIGINT'); // 发送中断信号给子进程
42
- setTimeout(() => {
43
- child.kill('SIGTERM'); // 2秒后强制终止
44
- }, 2000);
89
+ process.on('SIGTERM', () => {
90
+ console.error('\n[block proxy] 收到 SIGTERM,正在关闭子进程...');
91
+ if (restartTimer) {
92
+ clearTimeout(restartTimer);
93
+ restartTimer = null;
94
+ }
95
+ if (currentChild) {
96
+ currentChild.kill('SIGTERM');
97
+ } else {
98
+ process.exit(0);
99
+ }
45
100
  });
101
+
102
+ (async function() {
103
+ program
104
+ .name('block-proxy')
105
+ .description('极简的 MITM 代理工具:https://github.com/jayli/block-proxy')
106
+ .version('0.1.3')
107
+ .option('-c, --config <config>', 'MITM 配置文件');
108
+
109
+ program.parse(process.argv);
110
+ const options = program.opts();
111
+
112
+ if (options.config && options.config != "") {
113
+ if (path.isAbsolute(options.config)) {
114
+ await _fs.setGlobalConfigFile(options.config);
115
+ } else {
116
+ var pwd = process.cwd();
117
+ var configFile = path.resolve(pwd, options.config);
118
+ await _fs.setGlobalConfigFile(configFile);
119
+ }
120
+ }
121
+
122
+ // 启动
123
+ startApp();
124
+ })();
125
+
package/config.json CHANGED
@@ -245,6 +245,14 @@
245
245
  {
246
246
  "ip": "192.168.124.128",
247
247
  "mac": "48:F3:F3:CA:1D:E"
248
+ },
249
+ {
250
+ "ip": "192.168.124.66",
251
+ "mac": "E2:4D:6E:1:21:6E"
252
+ },
253
+ {
254
+ "ip": "192.168.124.125",
255
+ "mac": "14:C0:50:14:6E:A5"
248
256
  }
249
257
  ]
250
- }
258
+ }
@@ -0,0 +1,39 @@
1
+ module.exports = {
2
+ AAA: [
3
+ {
4
+ 'type': 'beforeSendResponse',
5
+ 'host': '163.com',
6
+ 'regexp': "/123/v1/(browse|next)",
7
+ 'callback': async function(url, request, response) {
8
+ return {
9
+ response
10
+ }
11
+ } // -- callback
12
+ },
13
+ {
14
+ 'type': 'beforeSendResponse',
15
+ 'host': '163.com',
16
+ 'regexp': "/456",
17
+ 'callback': async function(url, request, response) {
18
+ return {
19
+ response
20
+ }
21
+ } // -- callback
22
+ }
23
+ ],
24
+ BBB: [
25
+ {
26
+ type: "beforeSendRequest",
27
+ host: "163.com",
28
+ regexp: "/hello",
29
+ callback: async function(url, request, response) {
30
+ return {
31
+ response : {
32
+ statusCode: 200,
33
+ body: 'hello world'
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ]
39
+ };
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "block-proxy",
3
- "version": "0.1.1",
4
- "description": "Small-scale network proxy filter",
5
- "bin":{
6
- "block-proxy": "./bin/start.js"
3
+ "version": "0.1.4",
4
+ "description": "Small-scale network mitm proxy filter",
5
+ "bin": {
6
+ "block-proxy": "bin/start.js"
7
7
  },
8
8
  "dependencies": {
9
9
  "react": "^19.2.0",
@@ -33,11 +33,11 @@
33
33
  "@craco/craco": "^7.1.0",
34
34
  "anyproxy": "^4.1.3",
35
35
  "axios": "^1.13.2",
36
+ "commander": "^14.0.2",
36
37
  "express": "^5.1.0",
37
38
  "http-proxy-agent": "^7.0.2",
38
39
  "https-proxy-agent": "^7.0.6",
39
- "ping": "^1.0.0",
40
- "write-file-atomic": "^7.0.0"
40
+ "ping": "^1.0.0"
41
41
  },
42
42
  "browserslist": {
43
43
  "production": [
package/proxy/fs.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // proxy/fs.js
2
2
  const fs = require('fs').promises;
3
3
  const path = require('path');
4
- const writeFileAtomic = require('write-file-atomic'); // 引入 write-file-atomic
5
4
 
6
5
  const configPath = path.join(__dirname, '../config.json');
7
6
  const CONFIG_FILE_PATH = configPath;
@@ -9,9 +8,6 @@ const CONFIG_FILE_PATH = configPath;
9
8
  // 传入的是对象
10
9
  async function writeConfig(newData) {
11
10
  try {
12
- // 使用 write-file-atomic 进行原子写入
13
- // 它会在内部创建一个临时文件,写入成功后再重命名为目标文件
14
- // await writeFileAtomic(CONFIG_FILE_PATH, JSON.stringify(newData, null, 2), 'utf8');
15
11
  await fs.writeFile(CONFIG_FILE_PATH, JSON.stringify(newData, null, 2), 'utf8');
16
12
  // console.log('Config file written successfully');
17
13
  } catch (error) {
@@ -41,7 +37,33 @@ async function readConfig() {
41
37
  }
42
38
  }
43
39
 
40
+ async function setGlobalConfigFile(configFile) {
41
+ var data = await readConfig();
42
+ data.config_file = configFile;
43
+ await writeConfig(data);
44
+ }
45
+
46
+ async function getGlobalConfigFile() {
47
+ var data = await readConfig();
48
+ if (data.hasOwnProperty("config_file")) {
49
+ return data.config_file;
50
+ } else {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ async function clearGlobalConfigFile() {
56
+ var data = await readConfig();
57
+ if (data.hasOwnProperty("config_file")) {
58
+ delete data.config_file
59
+ }
60
+ await writeConfig(data);
61
+ }
62
+
44
63
  module.exports = {
45
64
  writeConfig,
46
- readConfig
65
+ readConfig,
66
+ setGlobalConfigFile,
67
+ getGlobalConfigFile,
68
+ clearGlobalConfigFile
47
69
  };
package/proxy/proxy.js CHANGED
@@ -7,6 +7,7 @@ const path = require('path');
7
7
  const { start } = require('repl');
8
8
  const net = require('net');
9
9
  const scanNetwork = require("./scan").scanNetwork;
10
+ const setScanStatus = require("./scan").setScanStatus;
10
11
  const util = require('util');
11
12
  const zlib = require('zlib');
12
13
  const _util = require('../server/util.js');
@@ -18,12 +19,12 @@ const axios = require('axios');
18
19
  const { HttpProxyAgent } = require('http-proxy-agent');
19
20
  const { HttpsProxyAgent } = require('https-proxy-agent');
20
21
  const _request = require("./http.js").request;
21
- const Rule = require("./mitm/rule.js");
22
22
  const uaFilter = require("./mitm/uaFilter.js");
23
23
  const attacker = require('./attacker.js');
24
24
  const monitor = require('./monitor.js');
25
25
  const domain = require('./domain.js');
26
26
  const wanip = require('./wanip.js');
27
+ var Rule = require("./mitm/rule.js");
27
28
 
28
29
  // 启用全局 keep-alive,使 AnyProxy 内部转发也复用连接
29
30
  http.globalAgent.keepAlive = true;
@@ -80,6 +81,45 @@ function preCompileRuleRegexp() {
80
81
  });
81
82
  }
82
83
 
84
+ async function fileExists(filePath) {
85
+ try {
86
+ await fs.promises.access(filePath);
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ // 引入从命令行传进来的 rule.js,并加载
94
+ async function loadGlobalConfigFile() {
95
+ var configFile = await _fs.getGlobalConfigFile();
96
+ await _fs.clearGlobalConfigFile();
97
+ if (configFile == null) {
98
+ return;
99
+ } else {
100
+ var extraRule = require(configFile);
101
+ Rule = {
102
+ ...Rule,
103
+ ...extraRule
104
+ }
105
+ }
106
+ }
107
+
108
+ // 引入 Docker 挂载目录下的 rule.js,并加载
109
+ async function loadDockerMountedConfigFile() {
110
+ var rulePath = path.join(__dirname, '../config/rule.js');
111
+ var fileOK = await fileExists(rulePath);
112
+ if (fileOK) {
113
+ var extraRule = require(rulePath);
114
+ Rule = {
115
+ ...Rule,
116
+ ...extraRule
117
+ }
118
+ } else {
119
+ return;
120
+ }
121
+ }
122
+
83
123
  function isEmpty(obj) {
84
124
  if (obj === null || obj === undefined) {
85
125
  return true;
@@ -207,7 +247,12 @@ async function loadConfig() {
207
247
 
208
248
  async function updateWanIp() {
209
249
  // var ips = await domain.getDomainIP(your_domain);
210
- var ip = await wanip.getPublicIp();
250
+ var ip = "0.0.0.0";
251
+ try {
252
+ ip = await wanip.getPublicIp();
253
+ } catch(e) {
254
+ ip = "0.0.0.0";
255
+ }
211
256
  if (ip === null) {
212
257
  ip = "0.0.0.0";
213
258
  }
@@ -398,12 +443,10 @@ function startProxyServer() {
398
443
  proxyServerInstance = new AnyProxy.ProxyServer(options);
399
444
 
400
445
  proxyServerInstance.on('ready', () => {
401
- console.log(`✅ Proxy server started on port ${proxyPort}`);
446
+ console.log(`✅ \x1b[32mHTTP 代理服务启动,端口 ${proxyPort}\x1b[0m`);
402
447
  if (enable_webinterface == "1") {
403
- console.log(`✅ Web interface available on port ${webInterfacePort}`);
448
+ console.log(`✅ \x1b[32mAnyProxy 监控面板启动,端口 ${webInterfacePort}\x1b[0m`);
404
449
  }
405
- console.log('Intercepting requests to hosts:', blockHosts.join(', '));
406
- console.log('All other requests will be passed through without HTTPS interception');
407
450
  });
408
451
 
409
452
  proxyServerInstance.on('error', (e) => {
@@ -1359,6 +1402,7 @@ var LocalProxy = {
1359
1402
  newRouterMap = await scanNetwork();
1360
1403
  } catch (e) {
1361
1404
  newRouterMap = [];
1405
+ setScanStatus("0");
1362
1406
  }
1363
1407
 
1364
1408
  var mergedRouterMap = [];
@@ -1445,7 +1489,17 @@ var LocalProxy = {
1445
1489
  return;
1446
1490
  }
1447
1491
 
1448
- console.log('启动代理服务 LocalProxy.init() ');
1492
+ // 加载命令行里携带的配置文件
1493
+ await loadGlobalConfigFile();
1494
+ // 加载 Docker 挂载目录中的配置文件
1495
+ await loadDockerMountedConfigFile();
1496
+ // 预编译 MITM Rule 的正则
1497
+ preCompileRuleRegexp();
1498
+
1499
+ // 启动时重置 Scan 本地扫描
1500
+ setScanStatus("0");
1501
+
1502
+ console.log('启动代理服务');
1449
1503
  console.log('Dev server started, starting LocalProxy...');
1450
1504
  is_running_in_docker = _util.isRunningInDocker();
1451
1505
  if (is_running_in_docker) {
@@ -1481,8 +1535,9 @@ var LocalProxy = {
1481
1535
  };
1482
1536
 
1483
1537
  // 预编译 MITM Rule 的正则
1484
- (function() {
1485
- preCompileRuleRegexp();
1538
+ (async function() {
1539
+ // await loadGlobalConfigFile();
1540
+ // preCompileRuleRegexp();
1486
1541
  })();
1487
1542
 
1488
1543
  module.exports = LocalProxy;
package/proxy/scan.js CHANGED
@@ -106,7 +106,7 @@ var tempDevices = [];
106
106
  async function scanNetwork() {
107
107
  var status = await getScanStatus();
108
108
  if (status == "1") {
109
- await setScanStatus("0");
109
+ // await setScanStatus("0");
110
110
  return tempDevices;
111
111
  } else {
112
112
  await setScanStatus("1");
@@ -117,4 +117,5 @@ async function scanNetwork() {
117
117
  }
118
118
  }
119
119
 
120
+ module.exports.setScanStatus = setScanStatus;
120
121
  module.exports.scanNetwork = scanNetwork;
package/server/express.js CHANGED
@@ -206,7 +206,6 @@ module.exports = {
206
206
  init: function() {
207
207
  // 启动服务器
208
208
  app.listen(PORT, async () => {
209
- console.log(`✅ 静态服务器运行在 http://localhost:${PORT}`);
210
209
  // 如果是开发环境,则启动SSR服务,开启端口3000
211
210
  if (DEV === '1') {
212
211
  const child = exec('npm run craco', { cwd: path.join(__dirname,'../') });
@@ -227,6 +226,7 @@ module.exports = {
227
226
  }
228
227
  // 启动本地代理
229
228
  await LocalProxy.init();
229
+ console.log(`✅ \x1b[32m后台配置面板启动 → http://localhost:${PORT}\x1b[0m`);
230
230
  });
231
231
  }
232
232
  };
package/socks5/server.js CHANGED
@@ -196,6 +196,12 @@ async function init() {
196
196
 
197
197
  // 创建 TLS 封装的 SOCKS5 服务器
198
198
  const server = tls.createServer(tlsOptions, async (socket) => {
199
+ // 👇 关键:捕获 socket 级别的错误(包括 ECONNRESET)
200
+ socket.on('error', (err) => {
201
+ console.warn('Client socket error (ignored):', err.message);
202
+ // 不需要手动 destroy(),Node.js 会自动关闭
203
+ });
204
+
199
205
  try {
200
206
  // Step 1: 协商认证方法
201
207
  const authMethodsBuf = await new Promise((resolve) => {
@@ -295,6 +301,11 @@ async function init() {
295
301
  }
296
302
  });
297
303
 
304
+ server.on('clientError', (err, socket) => {
305
+ console.warn('TLS client error during handshake:', err.message);
306
+ socket?.end(); // 安全关闭
307
+ });
308
+
298
309
  // 错误处理
299
310
  server.on('tlsClientError', (err, tlsSocket) => {
300
311
  console.warn('TLS handshake failed:', err.message);
@@ -307,10 +318,10 @@ async function init() {
307
318
 
308
319
  // 启动监听
309
320
  server.listen(LISTEN_PORT, () => {
310
- console.log(`✅ SOCKS5 over TLS server started on port ${LISTEN_PORT}`);
311
- console.log(`🔒 Credentials and traffic are encrypted via TLS`);
312
- console.log(`➡️ TCP → downstream HTTP proxy at ${DOWNSTREAM_HTTP_PROXY_HOST}:${DOWNSTREAM_HTTP_PROXY_PORT}`);
313
- console.log(`➡️ UDP → direct local relay`);
321
+ console.log(`✅ \x1b[32mSOCKS5 (over TLS) 服务启动,端口 ${LISTEN_PORT}\x1b[0m`);
322
+ console.log(`🔒 传输加密和认证基于 TLS`);
323
+ console.log(`➡️ TCP → 流量转发至 HTTP 代理 ${DOWNSTREAM_HTTP_PROXY_HOST}:${DOWNSTREAM_HTTP_PROXY_PORT}`);
324
+ console.log(`➡️ UDP → 直接发起请求`);
314
325
  });
315
326
  } catch (err) {
316
327
  console.error('Failed to initialize SOCKS5-TLS proxy:', err);
package/AD_BLOCK.md DELETED
@@ -1,38 +0,0 @@
1
- [Rule]
2
- AND,((DOMAIN-SUFFIX,googlevideo.com), (PROTOCOL,UDP)),REJECT
3
- AND,((DOMAIN,youtubei.googleapis.com), (PROTOCOL,UDP)),REJECT
4
-
5
- [URL Rewrite]
6
- (^https?:\/\/[\w-]+\.googlevideo\.com\/(?!dclk_video_ads).+?)&ctier=L(&.+?),ctier,(.+) $1$2$3 302
7
- ^https?:\/\/[\w-]+\.googlevideo\.com\/(?!(dclk_video_ads|videoplayback\?)).+&oad _ reject-200
8
- ^https?:\/\/(www|s)\.youtube\.com\/api\/stats\/ads _ reject-200
9
- ^https?:\/\/(www|s)\.youtube\.com\/(pagead|ptracking) _ reject-200
10
- ^https?:\/\/s\.youtube\.com\/api\/stats\/qoe\?adcontext _ reject-200
11
-
12
- [Script]
13
- youtube.response = type=http-response,pattern=^https:\/\/youtubei\.googleapis\.com\/youtubei\/v1\/(browse|next|player|search|reel\/reel_watch_sequence|guide|account\/get_setting|get_watch),requires-body=1,max-size=-1,binary-body-mode=1,engine={{{脚本执行引擎}}},script-path=https://raw.githubusercontent.com/Maasea/sgmodule/master/Script/Youtube/youtube.response.js,argument="{"lyricLang":"{{{歌词翻译语言}}}","captionLang":"{{{字幕翻译语言}}}","blockUpload":{{{屏蔽上传按钮}}},"blockImmersive":{{{屏蔽选段按钮}}},"debug":{{{启用调试模式}}}}"
14
-
15
- [MITM]
16
- hostname = %APPEND% -redirector*.googlevideo.com,*.googlevideo.com,www.youtube.com,s.youtube.com,youtubei.googleapis.com
17
-
18
-
19
-
20
- --------------------
21
-
22
- youtube.com
23
- `^https?:\/\/(www|s)\.youtube\.com\/api\/stats\/ads`
24
-
25
- youtube.com
26
- `^https?:\/\/(www|s)\.youtube\.com\/(pagead|ptracking)`
27
-
28
- youtube.com
29
- `^https?:\/\/s\.youtube\.com\/api\/stats\/qoe\?adcontext`
30
-
31
- googlevideo.com
32
- `^https?:\/\/[\w-]+\.googlevideo\.com\/(?!(dclk_video_ads|videoplayback\?)).+(&oad|ctier)`
33
-
34
- s.youtube.com
35
- `^https?:\/\/s\.youtube\.com\/api\/stats\/atr.+&is_ad=1`
36
-
37
- youtube.com
38
- `^https?:\/\/(www|s)\.youtube\.com\/pcs\/activeview.+&ad_cpn=`