block-proxy 0.1.8 → 0.1.10
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/.claude/settings.local.json +11 -0
- package/.claude/skills/commit/skill.md +32 -0
- package/.claude/skills/pcap-analyse/skill.md +100 -0
- package/CLAUDE.md +114 -0
- package/Dockerfile +1 -0
- package/README.md +3 -2
- package/bin/start.js +0 -1
- package/build/index.html +2 -1
- package/build/static/css/main.098e0e65.css +2 -0
- package/build/static/css/main.098e0e65.css.map +1 -0
- package/build/static/js/main.e8a52b27.js +3 -0
- package/build/static/js/main.e8a52b27.js.LICENSE.txt +49 -0
- package/build/static/js/main.e8a52b27.js.map +1 -0
- package/config.json +1 -57
- package/example/rule.js +1 -0
- package/hack-of-anyproxy/lib/requestHandler.js +33 -1
- package/package.json +3 -3
- package/proxy/operator.js +114 -0
- package/proxy/proxy.js +98 -169
- package/proxy/scan.js +49 -3
- package/server/express.js +1 -0
- package/socks5/test_tls_reuse.js +1 -0
- package/src/setupTests.js +1 -0
- package/tools/speed_test.sh +122 -0
- package/tools/tcpdump/output.pcap +0 -0
package/config.json
CHANGED
|
@@ -145,62 +145,6 @@
|
|
|
145
145
|
{
|
|
146
146
|
"ip": "192.168.124.1",
|
|
147
147
|
"mac": "FA:27:3C:E5:31:5F"
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
"ip": "192.168.124.2",
|
|
151
|
-
"mac": "7C:DE:78:A9:83:A0"
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"ip": "192.168.124.3",
|
|
155
|
-
"mac": "0:DD:B6:EA:E6:2A"
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
"ip": "192.168.124.6",
|
|
159
|
-
"mac": "0:DD:B6:EB:26:5C"
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
"ip": "192.168.124.10",
|
|
163
|
-
"mac": "FA:27:3C:E5:31:5F"
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
"ip": "192.168.124.21",
|
|
167
|
-
"mac": "DC:97:58:D:FD:9F"
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
"ip": "192.168.124.37",
|
|
171
|
-
"mac": "DE:1E:87:ED:17:8B"
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
"ip": "192.168.124.107",
|
|
175
|
-
"mac": "6:26:EA:5A:9E:6C"
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
"ip": "192.168.124.111",
|
|
179
|
-
"mac": "74:3F:C2:67:74:98"
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
"ip": "192.168.124.128",
|
|
183
|
-
"mac": "48:F3:F3:CA:1D:E"
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
"ip": "192.168.124.200",
|
|
187
|
-
"mac": "54:52:84:95:DF:5E"
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
"ip": "192.168.124.229",
|
|
191
|
-
"mac": "D6:A0:61:69:67:F6"
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
"ip": "192.168.124.240",
|
|
195
|
-
"mac": "F4:6B:8C:90:29:5"
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
"ip": "192.168.124.251",
|
|
199
|
-
"mac": "6E:FB:18:4D:9C:3E"
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
"ip": "192.168.240.207",
|
|
203
|
-
"mac": "32:BC:EC:7C:66:F6"
|
|
204
148
|
}
|
|
205
149
|
]
|
|
206
|
-
}
|
|
150
|
+
}
|
package/example/rule.js
CHANGED
|
@@ -46,6 +46,28 @@ function normalizeSocketKey(ip, port) {
|
|
|
46
46
|
return ip + ":" + port
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// 判断是否命中 responseRules
|
|
50
|
+
function matchResponseRule(responseRules, userConfig) {
|
|
51
|
+
try {
|
|
52
|
+
var hostname = userConfig.requestOptions.hostname.split(":")[0].toLowerCase();
|
|
53
|
+
var pathname = userConfig.requestOptions.path; // 带query
|
|
54
|
+
var url = `${userConfig.protocol}://${hostname}${pathname}`
|
|
55
|
+
var matched = false;
|
|
56
|
+
for (const item of responseRules) {
|
|
57
|
+
if (hostname.endsWith(item["host"]) && new RegExp(item['regexp']).test(url)) {
|
|
58
|
+
matched = true;
|
|
59
|
+
break;
|
|
60
|
+
} else {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return matched;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.log(e);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
class CommonReadableStream extends Readable {
|
|
50
72
|
constructor(config) {
|
|
51
73
|
super({
|
|
@@ -467,6 +489,15 @@ function getUserReqHandler(userRule, recorder) {
|
|
|
467
489
|
|
|
468
490
|
// route user config
|
|
469
491
|
.then(co.wrap(function *(userConfig) {
|
|
492
|
+
// Modified 2026-1-15
|
|
493
|
+
// 增加了对 responseRules 的判断,如果需要对 BeforeResponse 进行拦截
|
|
494
|
+
// 则让 chunkSizeThreshold 为很大的值(默认20M),以便让 response 一次性返回,方便BeforeResponse处理
|
|
495
|
+
// 否则以 512k 为上线,只要返回的数据达到阈值,就直接返回给调用端,以提高性能
|
|
496
|
+
if (userRule.responseRules && matchResponseRule(userRule.responseRules, userConfig)) {
|
|
497
|
+
var _chunkSizeThreshold = chunkSizeThreshold;
|
|
498
|
+
} else {
|
|
499
|
+
var _chunkSizeThreshold = 0.5 * 1024 * 1024; // 512K
|
|
500
|
+
}
|
|
470
501
|
if (userConfig.response) {
|
|
471
502
|
// user-assigned local response
|
|
472
503
|
userConfig._directlyPassToRespond = true;
|
|
@@ -474,7 +505,7 @@ function getUserReqHandler(userRule, recorder) {
|
|
|
474
505
|
} else if (userConfig.requestOptions) {
|
|
475
506
|
const remoteResponse = yield fetchRemoteResponse(userConfig.protocol, userConfig.requestOptions, userConfig.requestData, {
|
|
476
507
|
dangerouslyIgnoreUnauthorized: reqHandlerCtx.dangerouslyIgnoreUnauthorized,
|
|
477
|
-
chunkSizeThreshold,
|
|
508
|
+
chunkSizeThreshold: _chunkSizeThreshold,
|
|
478
509
|
});
|
|
479
510
|
return {
|
|
480
511
|
response: {
|
|
@@ -1026,3 +1057,4 @@ class RequestHandler {
|
|
|
1026
1057
|
}
|
|
1027
1058
|
|
|
1028
1059
|
module.exports = RequestHandler;
|
|
1060
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "block-proxy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Small-scale network mitm proxy filter",
|
|
5
5
|
"bin": {
|
|
6
6
|
"block-proxy": "bin/start.js"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"url": "git+https://github.com/jayli/block-proxy.git"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"cp": "echo '
|
|
19
|
+
"cp": "echo '----- program start -----'",
|
|
20
20
|
"craco": "craco start",
|
|
21
21
|
"dev": "BLOCK_PROXY_DEV=1 npm run express",
|
|
22
22
|
"start": "npm run express",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"eject": "react-scripts eject"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@bachi/anyproxy": "^0.1.
|
|
33
|
+
"@bachi/anyproxy": "^0.1.3",
|
|
34
34
|
"@craco/craco": "^7.1.0",
|
|
35
35
|
"axios": "^1.13.2",
|
|
36
36
|
"commander": "^14.0.2",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const _fs = require('./fs.js');
|
|
2
|
+
const monitor = require('./monitor.js');
|
|
3
|
+
|
|
4
|
+
async function getResponseByPathname(pathname, isDocker, proxyPort) {
|
|
5
|
+
if (pathname === "/favicon.ico") {
|
|
6
|
+
return {
|
|
7
|
+
response:{
|
|
8
|
+
statusCode:200,
|
|
9
|
+
body: Buffer.alloc(0)
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
} else if (pathname == "/restart_docker") {
|
|
13
|
+
if (isDocker) {
|
|
14
|
+
var msg = "请手动重启 Docker 容器。";
|
|
15
|
+
} else {
|
|
16
|
+
var msg = "当前程序不在 Docker 容器内,请在终端终止程序后再 npm run start 启动。";
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
response: {
|
|
20
|
+
statusCode: 200,
|
|
21
|
+
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
22
|
+
body: msg
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
} else if (pathname == "/enable_express") {
|
|
26
|
+
var configData = await _fs.readConfig();
|
|
27
|
+
_fs.writeConfig({
|
|
28
|
+
...configData,
|
|
29
|
+
enable_express: "1"
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
response: {
|
|
33
|
+
statusCode: 200,
|
|
34
|
+
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
35
|
+
body: "开启 express 后台设置成功,请重启 Docker。"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
} else if (pathname == "/disable_express") {
|
|
39
|
+
var configData = await _fs.readConfig();
|
|
40
|
+
_fs.writeConfig({
|
|
41
|
+
...configData,
|
|
42
|
+
enable_express: "0"
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
response: {
|
|
46
|
+
statusCode: 200,
|
|
47
|
+
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
48
|
+
body: "关闭 express 后台设置成功,请重启 Docker。"
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
} else if (pathname == "/enable_socks5") {
|
|
52
|
+
var configData = await _fs.readConfig();
|
|
53
|
+
_fs.writeConfig({
|
|
54
|
+
...configData,
|
|
55
|
+
enable_socks5: "1"
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
response: {
|
|
59
|
+
statusCode: 200,
|
|
60
|
+
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
61
|
+
body: "开启 socks5 成功,请重启 Docker。"
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
} else if (pathname == "/disable_socks5") {
|
|
65
|
+
var configData = await _fs.readConfig();
|
|
66
|
+
_fs.writeConfig({
|
|
67
|
+
...configData,
|
|
68
|
+
enable_socks5: "0"
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
response: {
|
|
72
|
+
statusCode: 200,
|
|
73
|
+
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
74
|
+
body: "关闭 socks5 成功,请重启 Docker。"
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
} else if (pathname == "/disable_webinterface") {
|
|
78
|
+
var configData = await _fs.readConfig();
|
|
79
|
+
_fs.writeConfig({
|
|
80
|
+
...configData,
|
|
81
|
+
enable_webinterface: "0"
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
response: {
|
|
85
|
+
statusCode: 200,
|
|
86
|
+
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
87
|
+
body: "关闭 webinterface 成功,请重启 Docker。"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
} else if (pathname == "/enable_webinterface") {
|
|
91
|
+
var configData = await _fs.readConfig();
|
|
92
|
+
_fs.writeConfig({
|
|
93
|
+
...configData,
|
|
94
|
+
enable_webinterface: "1"
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
response: {
|
|
98
|
+
statusCode: 200,
|
|
99
|
+
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
100
|
+
body: "启用 webinterface 成功,请重启 Docker。"
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
} else {
|
|
104
|
+
return {
|
|
105
|
+
response: {
|
|
106
|
+
statusCode: 200,
|
|
107
|
+
header: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
108
|
+
body: '<pre>' + await monitor.getSystemMonitorInfo(proxyPort) + '</pre>'
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports.getResponseByPathname = getResponseByPathname;
|
package/proxy/proxy.js
CHANGED
|
@@ -21,19 +21,20 @@ const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
|
21
21
|
const _request = require("./http.js").request;
|
|
22
22
|
const uaFilter = require("./mitm/uaFilter.js");
|
|
23
23
|
const attacker = require('./attacker.js');
|
|
24
|
-
const monitor = require('./monitor.js');
|
|
25
24
|
const domain = require('./domain.js');
|
|
26
25
|
const wanip = require('./wanip.js');
|
|
26
|
+
const operator = require("./operator.js");
|
|
27
27
|
var Rule = require("./mitm/rule.js");
|
|
28
28
|
|
|
29
29
|
// 启用全局 keep-alive,使 AnyProxy 内部转发也复用连接
|
|
30
30
|
http.globalAgent.keepAlive = true;
|
|
31
31
|
https.globalAgent.keepAlive = true;
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// 连接上限 50 应该足够了,设置 100 留足 buffer
|
|
33
|
+
http.globalAgent.maxSockets = 100;
|
|
34
|
+
https.globalAgent.maxSockets = 100;
|
|
34
35
|
|
|
35
|
-
const httpAgent = new http.Agent({ keepAlive: true, maxSockets:
|
|
36
|
-
const httpsAgent = new https.Agent({ keepAlive: true, maxSockets:
|
|
36
|
+
const httpAgent = new http.Agent({ keepAlive: true, maxSockets: 100 });
|
|
37
|
+
const httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 100 });
|
|
37
38
|
|
|
38
39
|
// 全局参数
|
|
39
40
|
const configPath = path.join(__dirname, '../config.json');
|
|
@@ -81,6 +82,71 @@ function preCompileRuleRegexp() {
|
|
|
81
82
|
});
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// 对于一些流媒体的链接不支持 407 的情况要排除验证
|
|
86
|
+
// host 可能携带端口:a.com:443
|
|
87
|
+
function authPass(protocol, host, url) {
|
|
88
|
+
const passHosts = [
|
|
89
|
+
"googlevideo.com", // Toutube 视频流
|
|
90
|
+
"dns.weixin.qq.com.cn", // 微信的 dns 预解析
|
|
91
|
+
"weixin.qq.com",
|
|
92
|
+
// xiaohongshu.com:443,小红书App和知乎 App 里发起带端口的请求,收到 407 后第二次
|
|
93
|
+
"xiaohongshu.com:443",
|
|
94
|
+
"xiaohongshu.com",
|
|
95
|
+
"xhscdn.com",
|
|
96
|
+
"zhihu.com:443",
|
|
97
|
+
...filtered_mitm_domains
|
|
98
|
+
];
|
|
99
|
+
// 基于 http 传输的流
|
|
100
|
+
const passUrl = [
|
|
101
|
+
/\.(m3u8|mp4|mpd|ts|webm|avi|mkv)$/i
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
var pass = false;
|
|
105
|
+
// 先检查是否完全比配,即带端口的匹配
|
|
106
|
+
passHosts.some(function(item) {
|
|
107
|
+
if (host.toLowerCase().endsWith(item.toLowerCase())) {
|
|
108
|
+
pass = true;
|
|
109
|
+
return true;
|
|
110
|
+
} else {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
if (pass) {
|
|
115
|
+
return pass;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 去掉端口后匹配
|
|
119
|
+
host = trimHost(host);
|
|
120
|
+
|
|
121
|
+
// 检查流媒体域名的排除项
|
|
122
|
+
passHosts.some(function(item) {
|
|
123
|
+
if (host.toLowerCase().endsWith(item.toLowerCase())) {
|
|
124
|
+
pass = true;
|
|
125
|
+
return true;
|
|
126
|
+
} else {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (pass) {
|
|
131
|
+
return pass;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 检查流媒体类型的排除项
|
|
135
|
+
if (url != null) {
|
|
136
|
+
passUrl.some(function(item) {
|
|
137
|
+
if (item.test(url)) {
|
|
138
|
+
pass = true;
|
|
139
|
+
return true;
|
|
140
|
+
} else {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return pass;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
84
150
|
async function fileExists(filePath) {
|
|
85
151
|
try {
|
|
86
152
|
await fs.promises.access(filePath);
|
|
@@ -658,6 +724,27 @@ async function MITMHandler(type, url, request, response) {
|
|
|
658
724
|
return responseResult;
|
|
659
725
|
}
|
|
660
726
|
|
|
727
|
+
// 获得需要重写响应的规则列表,符合规则的则提高chunkSizeThreshold阈值到 20M(默认)以上,来强制整包返回
|
|
728
|
+
// 否则就把chunkSizeThreshold阈值调整为 1M,超过阈值就流式返回,提高响应速度
|
|
729
|
+
function getResponseRules() {
|
|
730
|
+
var Ms = [];
|
|
731
|
+
|
|
732
|
+
Object.keys(Rule).forEach(key => {
|
|
733
|
+
Ms = Ms.concat(Rule[key]);
|
|
734
|
+
});
|
|
735
|
+
var res = [];
|
|
736
|
+
for (const item of Ms) {
|
|
737
|
+
if (item['type'] == "beforeSendResponse") {
|
|
738
|
+
res.push({
|
|
739
|
+
type: item['type'],
|
|
740
|
+
host: item['host'],
|
|
741
|
+
regexp: item['regexp']
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return res;
|
|
746
|
+
}
|
|
747
|
+
|
|
661
748
|
// 为 MITM 处理响应结果 body 的解压缩
|
|
662
749
|
// 之前是在 Anyproxy 里做,每个 response 都处理解压缩,目的是为了返回明文,抓包看明文用的,这里没必要
|
|
663
750
|
// 只需对 mitm 做解压就可以,其他的不需要解压缩的就完全透给客户端
|
|
@@ -820,53 +907,6 @@ function getProxyAuthConfig() {
|
|
|
820
907
|
};
|
|
821
908
|
}
|
|
822
909
|
|
|
823
|
-
// 对于一些流媒体的链接不支持 407 的情况要排除验证
|
|
824
|
-
// host 可能携带端口:a.com:443
|
|
825
|
-
function authPass(protocol, host, url) {
|
|
826
|
-
const passHosts = [
|
|
827
|
-
"googlevideo.com", // Toutube 视频流
|
|
828
|
-
"dns.weixin.qq.com.cn", // 微信的 dns 预解析
|
|
829
|
-
"weixin.qq.com",
|
|
830
|
-
...filtered_mitm_domains
|
|
831
|
-
];
|
|
832
|
-
// 基于 http 传输的流
|
|
833
|
-
const passUrl = [
|
|
834
|
-
/\.(m3u8|mp4|mpd|ts|webm|avi|mkv)$/i
|
|
835
|
-
];
|
|
836
|
-
|
|
837
|
-
host = trimHost(host);
|
|
838
|
-
|
|
839
|
-
var pass = false;
|
|
840
|
-
|
|
841
|
-
// 检查流媒体域名的排除项
|
|
842
|
-
passHosts.some(function(item) {
|
|
843
|
-
if (host.toLowerCase().endsWith(item.toLowerCase())) {
|
|
844
|
-
pass = true;
|
|
845
|
-
return true;
|
|
846
|
-
} else {
|
|
847
|
-
return false;
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
if (pass) {
|
|
852
|
-
return pass;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// 检查流媒体类型的排除项
|
|
856
|
-
if (url != null) {
|
|
857
|
-
passUrl.some(function(item) {
|
|
858
|
-
if (item.test(url)) {
|
|
859
|
-
pass = true;
|
|
860
|
-
return true;
|
|
861
|
-
} else {
|
|
862
|
-
return false;
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
return pass;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
910
|
function passRequestWithHttpAgent(requestDetail, isHttps) {
|
|
871
911
|
return {
|
|
872
912
|
...requestDetail,
|
|
@@ -881,6 +921,7 @@ function getAnyProxyOptions() {
|
|
|
881
921
|
return {
|
|
882
922
|
port: proxyPort,
|
|
883
923
|
rule: {
|
|
924
|
+
responseRules: getResponseRules(),
|
|
884
925
|
// 验证 Proxy-Authorization
|
|
885
926
|
// protocol: http, https
|
|
886
927
|
// req: 原始的 Request
|
|
@@ -922,17 +963,9 @@ function getAnyProxyOptions() {
|
|
|
922
963
|
|
|
923
964
|
const authHeader = headers['proxy-authorization'];
|
|
924
965
|
|
|
925
|
-
// Hack:
|
|
926
|
-
// xiaohongshu.com:443,小红书App和知乎 App 里发起带端口的请求,收到 407 后第二次
|
|
927
|
-
// 请求不会带上authentication,这是 App 的 bug,为了避免功能不可用,这里统一 Hack 掉。
|
|
928
|
-
if (/:\d+$/ig.test(headers['host'])) {
|
|
929
|
-
return true;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
966
|
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
933
967
|
return this.sendAuthRequired();
|
|
934
968
|
}
|
|
935
|
-
|
|
936
969
|
const credentials = authHeader.substring(6); // 去掉 'Basic ' 前缀
|
|
937
970
|
let decoded;
|
|
938
971
|
try {
|
|
@@ -1042,7 +1075,7 @@ function getAnyProxyOptions() {
|
|
|
1042
1075
|
const isHttps = url.startsWith('https:') ? true : false;
|
|
1043
1076
|
const isHttp = !isHttps;
|
|
1044
1077
|
|
|
1045
|
-
// 如果直接访问当前 IP 的代理端口
|
|
1078
|
+
// 如果直接访问当前 IP 的代理端口 http://127.0.0.1:8001
|
|
1046
1079
|
// 如果请求的目的是自己,防止代理回环
|
|
1047
1080
|
// 这里没办法穷举,只能约定防火墙里绑定的转发端口和 AnyProxy 的代理端口保持一致
|
|
1048
1081
|
// 只要不绑定其他端口,就绝对不会陷入回环问题
|
|
@@ -1051,116 +1084,12 @@ function getAnyProxyOptions() {
|
|
|
1051
1084
|
if ((myIp.includes(requestOptions.hostname) && requestOptions.port == proxyPort.toString()) ||
|
|
1052
1085
|
(requestOptions.hostname == your_domain && requestOptions.port == proxyPort.toString()) ||
|
|
1053
1086
|
(requestOptions.hostname == wan_ip && requestOptions.port == proxyPort.toString()) ||
|
|
1054
|
-
// 如果这里收到了 localhost 或 127.0.0.1
|
|
1087
|
+
// 如果这里收到了 localhost 或 127.0.0.1 的访问,一定是本机访问
|
|
1088
|
+
// 其他机器访问 localhost 是不会走远端代理的
|
|
1055
1089
|
(host == "localhost" || host == "127.0.0.1")
|
|
1056
1090
|
) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
response:{
|
|
1060
|
-
statusCode:200,
|
|
1061
|
-
body: Buffer.alloc(0)
|
|
1062
|
-
}
|
|
1063
|
-
};
|
|
1064
|
-
} else if (pathname == "/restart_docker") {
|
|
1065
|
-
if (isDocker) {
|
|
1066
|
-
var msg = "请手动重启 Docker 容器。";
|
|
1067
|
-
} else {
|
|
1068
|
-
var msg = "当前程序不在 Docker 容器内,请在终端终止程序后再 npm run start 启动。";
|
|
1069
|
-
}
|
|
1070
|
-
return {
|
|
1071
|
-
response: {
|
|
1072
|
-
statusCode: 200,
|
|
1073
|
-
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
1074
|
-
body: msg
|
|
1075
|
-
}
|
|
1076
|
-
};
|
|
1077
|
-
} else if (pathname == "/enable_express") {
|
|
1078
|
-
var configData = await _fs.readConfig();
|
|
1079
|
-
_fs.writeConfig({
|
|
1080
|
-
...configData,
|
|
1081
|
-
enable_express: "1"
|
|
1082
|
-
});
|
|
1083
|
-
return {
|
|
1084
|
-
response: {
|
|
1085
|
-
statusCode: 200,
|
|
1086
|
-
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
1087
|
-
body: "开启 express 后台设置成功,请重启 Docker。"
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1090
|
-
} else if (pathname == "/disable_express") {
|
|
1091
|
-
var configData = await _fs.readConfig();
|
|
1092
|
-
_fs.writeConfig({
|
|
1093
|
-
...configData,
|
|
1094
|
-
enable_express: "0"
|
|
1095
|
-
});
|
|
1096
|
-
return {
|
|
1097
|
-
response: {
|
|
1098
|
-
statusCode: 200,
|
|
1099
|
-
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
1100
|
-
body: "关闭 express 后台设置成功,请重启 Docker。"
|
|
1101
|
-
}
|
|
1102
|
-
};
|
|
1103
|
-
} else if (pathname == "/enable_socks5") {
|
|
1104
|
-
var configData = await _fs.readConfig();
|
|
1105
|
-
_fs.writeConfig({
|
|
1106
|
-
...configData,
|
|
1107
|
-
enable_socks5: "1"
|
|
1108
|
-
});
|
|
1109
|
-
return {
|
|
1110
|
-
response: {
|
|
1111
|
-
statusCode: 200,
|
|
1112
|
-
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
1113
|
-
body: "开启 socks5 成功,请重启 Docker。"
|
|
1114
|
-
}
|
|
1115
|
-
};
|
|
1116
|
-
} else if (pathname == "/disable_socks5") {
|
|
1117
|
-
var configData = await _fs.readConfig();
|
|
1118
|
-
_fs.writeConfig({
|
|
1119
|
-
...configData,
|
|
1120
|
-
enable_socks5: "0"
|
|
1121
|
-
});
|
|
1122
|
-
return {
|
|
1123
|
-
response: {
|
|
1124
|
-
statusCode: 200,
|
|
1125
|
-
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
1126
|
-
body: "关闭 socks5 成功,请重启 Docker。"
|
|
1127
|
-
}
|
|
1128
|
-
};
|
|
1129
|
-
} else if (pathname == "/disable_webinterface") {
|
|
1130
|
-
var configData = await _fs.readConfig();
|
|
1131
|
-
_fs.writeConfig({
|
|
1132
|
-
...configData,
|
|
1133
|
-
enable_webinterface: "0"
|
|
1134
|
-
});
|
|
1135
|
-
return {
|
|
1136
|
-
response: {
|
|
1137
|
-
statusCode: 200,
|
|
1138
|
-
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
1139
|
-
body: "关闭 webinterface 成功,请重启 Docker。"
|
|
1140
|
-
}
|
|
1141
|
-
};
|
|
1142
|
-
} else if (pathname == "/enable_webinterface") {
|
|
1143
|
-
var configData = await _fs.readConfig();
|
|
1144
|
-
_fs.writeConfig({
|
|
1145
|
-
...configData,
|
|
1146
|
-
enable_webinterface: "1"
|
|
1147
|
-
});
|
|
1148
|
-
return {
|
|
1149
|
-
response: {
|
|
1150
|
-
statusCode: 200,
|
|
1151
|
-
header: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
1152
|
-
body: "启用 webinterface 成功,请重启 Docker。"
|
|
1153
|
-
}
|
|
1154
|
-
};
|
|
1155
|
-
} else {
|
|
1156
|
-
return {
|
|
1157
|
-
response: {
|
|
1158
|
-
statusCode: 200,
|
|
1159
|
-
header: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
1160
|
-
body: '<pre>' + await monitor.getSystemMonitorInfo(proxyPort) + '</pre>'
|
|
1161
|
-
}
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1091
|
+
// Operator
|
|
1092
|
+
return await operator.getResponseByPathname(pathname, isDocker, proxyPort);
|
|
1164
1093
|
}
|
|
1165
1094
|
|
|
1166
1095
|
// 如果是裸IP请求,全部放行
|
package/proxy/scan.js
CHANGED
|
@@ -78,6 +78,46 @@ async function getScanStatus() {
|
|
|
78
78
|
return loadedConfig.network_scanning_status;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// 并发控制器,限制同时进行的 ping 数量
|
|
82
|
+
class ConcurrentPingController {
|
|
83
|
+
constructor(maxConcurrent = 32, batchDelay = 50) {
|
|
84
|
+
this.maxConcurrent = maxConcurrent;
|
|
85
|
+
this.batchDelay = batchDelay; // 批次之间的延迟(毫秒)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 批量执行 ping,控制并发数(真正的批次并发)
|
|
89
|
+
async batchPing(ips, timeout = 1) {
|
|
90
|
+
const results = [];
|
|
91
|
+
|
|
92
|
+
// 将IP列表分成多个批次
|
|
93
|
+
for (let i = 0; i < ips.length; i += this.maxConcurrent) {
|
|
94
|
+
const batch = ips.slice(i, i + this.maxConcurrent);
|
|
95
|
+
|
|
96
|
+
// 并发执行当前批次的所有ping
|
|
97
|
+
const batchPromises = batch.map(ip =>
|
|
98
|
+
ping.promise.probe(ip, { timeout })
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const batchResults = await Promise.allSettled(batchPromises);
|
|
102
|
+
|
|
103
|
+
// 将批次结果添加到总结果中
|
|
104
|
+
batch.forEach((ip, index) => {
|
|
105
|
+
results.push({
|
|
106
|
+
ip,
|
|
107
|
+
result: batchResults[index]
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// 如果不是最后一个批次,则添加延迟
|
|
112
|
+
if (i + this.maxConcurrent < ips.length) {
|
|
113
|
+
await new Promise(resolve => setTimeout(resolve, this.batchDelay));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
81
121
|
async function doScan() {
|
|
82
122
|
const subnet = getLocalSubnet();
|
|
83
123
|
// 如果是 30 网段,直接返回空
|
|
@@ -86,11 +126,17 @@ async function doScan() {
|
|
|
86
126
|
}
|
|
87
127
|
console.log(`正在扫描网段: ${subnet}.0/24`);
|
|
88
128
|
|
|
89
|
-
//
|
|
129
|
+
// 创建并发控制器,限制同时进行32个ping,批次间延迟50ms
|
|
130
|
+
const controller = new ConcurrentPingController(32, 50);
|
|
131
|
+
|
|
132
|
+
// 生成所有IP地址
|
|
90
133
|
const ips = Array.from({ length: 254 }, (_, i) => `${subnet}.${i + 1}`);
|
|
91
|
-
await Promise.allSettled(ips.map(ip => ping.promise.probe(ip, { timeout: 1 })));
|
|
92
134
|
|
|
93
|
-
//
|
|
135
|
+
// 使用并发控制器分批ping所有IP
|
|
136
|
+
console.log(`开始并发ping扫描,最大并发数: 32,批次延迟: 50ms`);
|
|
137
|
+
await controller.batchPing(ips, 1); // 使用1秒超时,ping结果仅用于填充ARP缓存
|
|
138
|
+
|
|
139
|
+
// 读取ARP表以获取MAC地址
|
|
94
140
|
const cmd = process.platform === 'win32' ? 'arp -a' : 'arp -a';
|
|
95
141
|
const arpOutput = await new Promise((resolve, reject) => {
|
|
96
142
|
exec(cmd, async (error, stdout) => {
|
package/server/express.js
CHANGED
package/socks5/test_tls_reuse.js
CHANGED
package/src/setupTests.js
CHANGED