block-proxy 0.1.8 → 0.1.9
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/config.json +0 -56
- package/package.json +2 -2
- package/proxy/operator.js +114 -0
- package/proxy/proxy.js +91 -165
- package/tools/speed_test.sh +121 -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "block-proxy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Small-scale network mitm proxy filter",
|
|
5
5
|
"bin": {
|
|
6
6
|
"block-proxy": "bin/start.js"
|
|
@@ -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,9 +21,9 @@ 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 内部转发也复用连接
|
|
@@ -81,6 +81,69 @@ function preCompileRuleRegexp() {
|
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// 对于一些流媒体的链接不支持 407 的情况要排除验证
|
|
85
|
+
// host 可能携带端口:a.com:443
|
|
86
|
+
function authPass(protocol, host, url) {
|
|
87
|
+
const passHosts = [
|
|
88
|
+
"googlevideo.com", // Toutube 视频流
|
|
89
|
+
"dns.weixin.qq.com.cn", // 微信的 dns 预解析
|
|
90
|
+
"weixin.qq.com",
|
|
91
|
+
// xiaohongshu.com:443,小红书App和知乎 App 里发起带端口的请求,收到 407 后第二次
|
|
92
|
+
"xiaohongshu.com:443",
|
|
93
|
+
"zhihu.com:443",
|
|
94
|
+
...filtered_mitm_domains
|
|
95
|
+
];
|
|
96
|
+
// 基于 http 传输的流
|
|
97
|
+
const passUrl = [
|
|
98
|
+
/\.(m3u8|mp4|mpd|ts|webm|avi|mkv)$/i
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
var pass = false;
|
|
102
|
+
// 先检查是否完全比配,即带端口的匹配
|
|
103
|
+
passHosts.some(function(item) {
|
|
104
|
+
if (host.toLowerCase().endsWith(item.toLowerCase())) {
|
|
105
|
+
pass = true;
|
|
106
|
+
return true;
|
|
107
|
+
} else {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
if (pass) {
|
|
112
|
+
return pass;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 去掉端口后匹配
|
|
116
|
+
host = trimHost(host);
|
|
117
|
+
|
|
118
|
+
// 检查流媒体域名的排除项
|
|
119
|
+
passHosts.some(function(item) {
|
|
120
|
+
if (host.toLowerCase().endsWith(item.toLowerCase())) {
|
|
121
|
+
pass = true;
|
|
122
|
+
return true;
|
|
123
|
+
} else {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
if (pass) {
|
|
128
|
+
return pass;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 检查流媒体类型的排除项
|
|
132
|
+
if (url != null) {
|
|
133
|
+
passUrl.some(function(item) {
|
|
134
|
+
if (item.test(url)) {
|
|
135
|
+
pass = true;
|
|
136
|
+
return true;
|
|
137
|
+
} else {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return pass;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
84
147
|
async function fileExists(filePath) {
|
|
85
148
|
try {
|
|
86
149
|
await fs.promises.access(filePath);
|
|
@@ -658,6 +721,27 @@ async function MITMHandler(type, url, request, response) {
|
|
|
658
721
|
return responseResult;
|
|
659
722
|
}
|
|
660
723
|
|
|
724
|
+
// 获得需要重写响应的规则列表,符合规则的则提高chunkSizeThreshold阈值到 20M(默认)以上,来强制整包返回
|
|
725
|
+
// 否则就把chunkSizeThreshold阈值调整为 1M,超过阈值就流式返回,提高响应速度
|
|
726
|
+
function getResponseRules() {
|
|
727
|
+
var Ms = [];
|
|
728
|
+
|
|
729
|
+
Object.keys(Rule).forEach(key => {
|
|
730
|
+
Ms = Ms.concat(Rule[key]);
|
|
731
|
+
});
|
|
732
|
+
var res = [];
|
|
733
|
+
for (const item of Ms) {
|
|
734
|
+
if (item['type'] == "beforeSendResponse") {
|
|
735
|
+
res.push({
|
|
736
|
+
type: item['type'],
|
|
737
|
+
host: item['host'],
|
|
738
|
+
regexp: item['regexp']
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return res;
|
|
743
|
+
}
|
|
744
|
+
|
|
661
745
|
// 为 MITM 处理响应结果 body 的解压缩
|
|
662
746
|
// 之前是在 Anyproxy 里做,每个 response 都处理解压缩,目的是为了返回明文,抓包看明文用的,这里没必要
|
|
663
747
|
// 只需对 mitm 做解压就可以,其他的不需要解压缩的就完全透给客户端
|
|
@@ -820,53 +904,6 @@ function getProxyAuthConfig() {
|
|
|
820
904
|
};
|
|
821
905
|
}
|
|
822
906
|
|
|
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
907
|
function passRequestWithHttpAgent(requestDetail, isHttps) {
|
|
871
908
|
return {
|
|
872
909
|
...requestDetail,
|
|
@@ -881,6 +918,7 @@ function getAnyProxyOptions() {
|
|
|
881
918
|
return {
|
|
882
919
|
port: proxyPort,
|
|
883
920
|
rule: {
|
|
921
|
+
responseRules: getResponseRules(),
|
|
884
922
|
// 验证 Proxy-Authorization
|
|
885
923
|
// protocol: http, https
|
|
886
924
|
// req: 原始的 Request
|
|
@@ -922,17 +960,9 @@ function getAnyProxyOptions() {
|
|
|
922
960
|
|
|
923
961
|
const authHeader = headers['proxy-authorization'];
|
|
924
962
|
|
|
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
963
|
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
933
964
|
return this.sendAuthRequired();
|
|
934
965
|
}
|
|
935
|
-
|
|
936
966
|
const credentials = authHeader.substring(6); // 去掉 'Basic ' 前缀
|
|
937
967
|
let decoded;
|
|
938
968
|
try {
|
|
@@ -1042,7 +1072,7 @@ function getAnyProxyOptions() {
|
|
|
1042
1072
|
const isHttps = url.startsWith('https:') ? true : false;
|
|
1043
1073
|
const isHttp = !isHttps;
|
|
1044
1074
|
|
|
1045
|
-
// 如果直接访问当前 IP 的代理端口
|
|
1075
|
+
// 如果直接访问当前 IP 的代理端口 http://127.0.0.1:8001
|
|
1046
1076
|
// 如果请求的目的是自己,防止代理回环
|
|
1047
1077
|
// 这里没办法穷举,只能约定防火墙里绑定的转发端口和 AnyProxy 的代理端口保持一致
|
|
1048
1078
|
// 只要不绑定其他端口,就绝对不会陷入回环问题
|
|
@@ -1051,116 +1081,12 @@ function getAnyProxyOptions() {
|
|
|
1051
1081
|
if ((myIp.includes(requestOptions.hostname) && requestOptions.port == proxyPort.toString()) ||
|
|
1052
1082
|
(requestOptions.hostname == your_domain && requestOptions.port == proxyPort.toString()) ||
|
|
1053
1083
|
(requestOptions.hostname == wan_ip && requestOptions.port == proxyPort.toString()) ||
|
|
1054
|
-
// 如果这里收到了 localhost 或 127.0.0.1
|
|
1084
|
+
// 如果这里收到了 localhost 或 127.0.0.1 的访问,一定是本机访问
|
|
1085
|
+
// 其他机器访问 localhost 是不会走远端代理的
|
|
1055
1086
|
(host == "localhost" || host == "127.0.0.1")
|
|
1056
1087
|
) {
|
|
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
|
-
}
|
|
1088
|
+
// Operator
|
|
1089
|
+
return await operator.getResponseByPathname(pathname, isDocker, proxyPort);
|
|
1164
1090
|
}
|
|
1165
1091
|
|
|
1166
1092
|
// 如果是裸IP请求,全部放行
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
|
|
3
|
+
# --- 参数检查 ---
|
|
4
|
+
if [ $# -eq 0 ]; then
|
|
5
|
+
echo "用法: $0 <URL> [TARGET_IP]"
|
|
6
|
+
echo "说明: 若提供 TARGET_IP,则绕过 DNS,直连该 IP 并使用 URL 中的域名作为 SNI/Host"
|
|
7
|
+
echo "示例:"
|
|
8
|
+
echo " $0 https://www.taobao.com"
|
|
9
|
+
echo " $0 https://www.taobao.com 211.100.8.95"
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
URL="$1"
|
|
14
|
+
TARGET_IP="${2:-}" # 第二个参数可选
|
|
15
|
+
PROXY="socks5://127.0.0.1:1081"
|
|
16
|
+
TUNNEL_PID=""
|
|
17
|
+
SOCAT_LOG="/tmp/socat_error.log"
|
|
18
|
+
|
|
19
|
+
# --- 从 URL 提取主机名(用于 --resolve 和 SNI)---
|
|
20
|
+
# 移除协议头,再截断路径和端口
|
|
21
|
+
HOST=$(echo "$URL" | sed -E 's|^[^:]+://||' | sed -E 's|/.*$||' | sed -E 's/:.*$//')
|
|
22
|
+
PORT="443"
|
|
23
|
+
|
|
24
|
+
cleanup() {
|
|
25
|
+
if [ -n "$TUNNEL_PID" ]; then
|
|
26
|
+
# 检查进程是否存在
|
|
27
|
+
if kill -0 "$TUNNEL_PID" 2>/dev/null; then
|
|
28
|
+
echo "" >&2
|
|
29
|
+
# echo "🛑 正在终止 socat 隧道 (PID: $TUNNEL_PID)..." >&2
|
|
30
|
+
kill "$TUNNEL_PID" 2>/dev/null
|
|
31
|
+
|
|
32
|
+
# 等待最多 1 秒让它优雅退出
|
|
33
|
+
i=0
|
|
34
|
+
while kill -0 "$TUNNEL_PID" 2>/dev/null && [ $i -lt 10 ]; do
|
|
35
|
+
sleep 0.1
|
|
36
|
+
i=$((i + 1))
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
# 如果还在,强制杀死
|
|
40
|
+
if kill -0 "$TUNNEL_PID" 2>/dev/null; then
|
|
41
|
+
echo "⚠️ 强制终止 socat..." >&2
|
|
42
|
+
kill -9 "$TUNNEL_PID" 2>/dev/null
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# 清理僵尸进程
|
|
46
|
+
wait "$TUNNEL_PID" 2>/dev/null
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
rm -f "$SOCAT_LOG"
|
|
50
|
+
exit "${1:-0}"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
trap cleanup EXIT INT TERM
|
|
54
|
+
|
|
55
|
+
# --- 检查本地端口 1081 是否已被占用 ---
|
|
56
|
+
if command -v ss >/dev/null 2>&1; then
|
|
57
|
+
if ss -tuln 2>/dev/null | grep -q ':1081\b'; then
|
|
58
|
+
echo "⚠️ 警告: 本地端口 1081 已被占用,socat 可能启动失败。"
|
|
59
|
+
fi
|
|
60
|
+
elif command -v netstat >/dev/null 2>&1; then
|
|
61
|
+
if netstat -tuln 2>/dev/null | grep -q ':1081\b'; then
|
|
62
|
+
echo "⚠️ 警告: 本地端口 1081 已被占用,socat 可能启动失败。"
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# --- 启动 socat 隧道,并记录错误 ---
|
|
67
|
+
echo "🔌 正在启动隧道: socat → OPENSSL:yui.cool:8002 ..."
|
|
68
|
+
socat TCP-LISTEN:1081,fork,bind=127.0.0.1 OPENSSL:yui.cool:8002,verify=0 >"$SOCAT_LOG" 2>&1 &
|
|
69
|
+
TUNNEL_PID=$!
|
|
70
|
+
sleep 0.5
|
|
71
|
+
|
|
72
|
+
# --- 检查 socat 是否仍在运行 ---
|
|
73
|
+
if ! kill -0 "$TUNNEL_PID" 2>/dev/null; then
|
|
74
|
+
echo "❌ 隧道启动失败!socat 报错如下:"
|
|
75
|
+
if [ -s "$SOCAT_LOG" ]; then
|
|
76
|
+
cat "$SOCAT_LOG"
|
|
77
|
+
else
|
|
78
|
+
echo "(无详细错误,可能进程立即崩溃)"
|
|
79
|
+
fi
|
|
80
|
+
exit 1
|
|
81
|
+
else
|
|
82
|
+
: > "$SOCAT_LOG" # 清空日志
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# --- 构建 curl 命令 ---
|
|
86
|
+
CURL_CMD="curl -k -I --proxy '$PROXY'"
|
|
87
|
+
|
|
88
|
+
if [ -n "$TARGET_IP" ]; then
|
|
89
|
+
CURL_CMD="$CURL_CMD --resolve '$HOST:$PORT:$TARGET_IP'"
|
|
90
|
+
echo "🌐 绕过 DNS: 直连 $TARGET_IP,SNI = $HOST"
|
|
91
|
+
else
|
|
92
|
+
echo "🌐 使用代理解析 DNS(常规模式)"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
echo "📡 请求: $URL via $PROXY"
|
|
96
|
+
|
|
97
|
+
# --- 执行 curl 并计时 ---
|
|
98
|
+
FINAL_CMD="$CURL_CMD $URL"
|
|
99
|
+
|
|
100
|
+
echo "$FINAL_CMD"
|
|
101
|
+
|
|
102
|
+
{ time_output=$( { time eval "$FINAL_CMD"; } 2>&1 1>&3 ); } 3>&1
|
|
103
|
+
exit_code=$?
|
|
104
|
+
|
|
105
|
+
# --- 提取状态行 ---
|
|
106
|
+
status_line=$(printf "%s\n" "$time_output" | head -n 1)
|
|
107
|
+
|
|
108
|
+
if [ "$exit_code" -eq 0 ]; then
|
|
109
|
+
echo "✅ 响应状态: $status_line"
|
|
110
|
+
else
|
|
111
|
+
echo "❌ 请求失败(退出码: $exit_code)"
|
|
112
|
+
[ -n "$status_line" ] && echo "⚠️ 部分响应: $status_line"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# --- 显示耗时 ---
|
|
116
|
+
real_time=$(printf "%s\n" "$time_output" | grep "^real" | awk '{print $2}')
|
|
117
|
+
if [ -n "$real_time" ]; then
|
|
118
|
+
echo "⏱️ 耗时: $real_time"
|
|
119
|
+
else
|
|
120
|
+
echo "⚠️ 无法获取耗时(shell 不支持 time 内置命令的格式)"
|
|
121
|
+
fi
|