block-proxy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.eslintignore +3 -0
  2. package/AD_BLOCK.md +38 -0
  3. package/Dockerfile +51 -0
  4. package/LICENSE +21 -0
  5. package/README.md +182 -0
  6. package/bin/start.js +45 -0
  7. package/cert/rootCA.crt +20 -0
  8. package/cert/rootCA.key +27 -0
  9. package/config.json +234 -0
  10. package/craco.config.js +52 -0
  11. package/hack-of-anyproxy/lib/requestHandler.js +1028 -0
  12. package/package.json +54 -0
  13. package/proxy/attacker.js +135 -0
  14. package/proxy/domain.js +26 -0
  15. package/proxy/fs.js +46 -0
  16. package/proxy/http.js +224 -0
  17. package/proxy/mitm/persistentStore.js +34 -0
  18. package/proxy/mitm/persistentStore.json +3 -0
  19. package/proxy/mitm/rule.js +116 -0
  20. package/proxy/mitm/uaFilter.js +47 -0
  21. package/proxy/mitm/ydcd/ydcd.js +34 -0
  22. package/proxy/mitm/youtube/youtube.response.js +39 -0
  23. package/proxy/monitor.js +283 -0
  24. package/proxy/proxy.js +1488 -0
  25. package/proxy/scan.js +120 -0
  26. package/proxy/start.js +7 -0
  27. package/proxy/wanip.js +76 -0
  28. package/public/favicon.ico +0 -0
  29. package/public/index.html +43 -0
  30. package/public/iphone-proxy-setting.jpg +0 -0
  31. package/public/logo192.png +0 -0
  32. package/public/logo512.png +0 -0
  33. package/public/manifest.json +25 -0
  34. package/public/proxy.jpg +0 -0
  35. package/public/robots.txt +3 -0
  36. package/server/express.js +232 -0
  37. package/server/start.js +24 -0
  38. package/server/util.js +166 -0
  39. package/socks5/server.js +321 -0
  40. package/socks5/start.js +7 -0
  41. package/socks5/test_tls_reuse.js +40 -0
  42. package/src/App.css +505 -0
  43. package/src/App.js +759 -0
  44. package/src/App.test.js +8 -0
  45. package/src/index.css +13 -0
  46. package/src/index.js +17 -0
  47. package/src/logo.svg +1 -0
  48. package/src/reportWebVitals.js +13 -0
  49. package/src/setupTests.js +5 -0
package/server/util.js ADDED
@@ -0,0 +1,166 @@
1
+ // server/util.js
2
+ const http = require('http');
3
+ const https = require('https');
4
+ const { URL } = require('url');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ // const LocalProxy = require('../proxy/proxy');
9
+
10
+ // 将十六进制IP地址转换为点分十进制格式
11
+ function hexToIP(hex) {
12
+ const ip = [];
13
+ for (let i = 0; i < 4; i++) {
14
+ ip.push(parseInt(hex.substr(i * 2, 2), 16));
15
+ }
16
+ return ip.reverse().join('.');
17
+ }
18
+
19
+
20
+ function isPrivateIPv4(ip) {
21
+ if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(ip)) return false;
22
+ const [a, b, c, d] = ip.split('.').map(Number);
23
+ if (a === 192 && b === 168) return true; // 192.168.0.0/16
24
+ if (a === 10) return true; // 10.0.0.0/8
25
+ if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12
26
+ return false;
27
+ }
28
+
29
+ // 可能返回多个局域网IP,逗号分隔
30
+ function getHostLanIP() {
31
+ const interfaces = os.networkInterfaces();
32
+ const lanIPs = new Set(); // 自动去重
33
+
34
+ for (const name of Object.keys(interfaces)) {
35
+ // 跳过回环接口(lo)
36
+ if (name === 'lo') continue;
37
+
38
+ const nets = interfaces[name];
39
+ for (const net of nets) {
40
+ if (net.family === 'IPv4' && !net.internal) {
41
+ const addr = net.address;
42
+ if (isPrivateIPv4(addr)) {
43
+ lanIPs.add(addr);
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ // 如果没找到私有 IP,fallback 到 127.0.0.1
50
+ if (lanIPs.size === 0) {
51
+ return '127.0.0.1';
52
+ }
53
+
54
+ return Array.from(lanIPs).sort().join(',');
55
+ }
56
+
57
+
58
+ // 使用
59
+ // const hostIP = getHostLanIP();
60
+ // console.log('宿主机局域网 IP:', hostIP);
61
+
62
+ module.exports = {
63
+ // 检测是否在 Docker 环境中运行
64
+ isRunningInDocker: () => {
65
+ // 方法1: 检查 /.dockerenv 文件是否存在
66
+ try {
67
+ fs.accessSync('/.dockerenv', fs.constants.F_OK);
68
+ return true;
69
+ } catch (err) {
70
+ // 文件不存在,继续检查其他方法
71
+ }
72
+
73
+ // 方法2: 检查 /proc/1/cgroup 文件中是否包含 docker 相关信息
74
+ try {
75
+ const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
76
+ if (cgroup.includes('docker') || cgroup.includes('containerd')) {
77
+ return true;
78
+ }
79
+ } catch (err) {
80
+ // 文件不存在或无法读取
81
+ }
82
+
83
+ // 方法3: 检查环境变量
84
+ if (process.env.DOCKER === 'true' || process.env.CONTAINER === 'docker') {
85
+ return true;
86
+ }
87
+
88
+ // 方法4: 检查挂载信息
89
+ try {
90
+ const mounts = fs.readFileSync('/proc/mounts', 'utf8');
91
+ if (mounts.includes('docker')) {
92
+ return true;
93
+ }
94
+ } catch (err) {
95
+ // 文件不存在或无法读取
96
+ }
97
+
98
+ return false;
99
+ },
100
+
101
+ // 获取Docker宿主机IP地址
102
+ getDockerHostIP: () => {
103
+ return getHostLanIP();
104
+ },
105
+
106
+ // 获取默认网关IP
107
+ getDefaultGateway: () => {
108
+ try {
109
+ // 读取路由表信息
110
+ const routes = fs.readFileSync('/proc/net/route', 'utf8');
111
+ const lines = routes.split('\n');
112
+
113
+ for (let i = 1; i < lines.length; i++) {
114
+ const fields = lines[i].trim().split(/\s+/);
115
+ if (fields.length >= 3) {
116
+ const destination = fields[1];
117
+ const gateway = fields[2];
118
+
119
+ // 检查默认路由 (目标为00000000)
120
+ if (destination === '00000000' && gateway !== '00000000') {
121
+ // 将十六进制网关地址转换为点分十进制
122
+ const gatewayIP = hexToIP(gateway);
123
+ return gatewayIP;
124
+ }
125
+ }
126
+ }
127
+ } catch (err) {
128
+ // 文件不存在或无法读取
129
+ }
130
+ return null;
131
+ },
132
+
133
+
134
+ // 检查当前时间是否在拦截时间段内
135
+ isWithinFilterTime: (filterItem) => {
136
+ // 如果没有设置时间段,则始终拦截
137
+ if (!filterItem.filter_start_time || !filterItem.filter_end_time) {
138
+ return true;
139
+ }
140
+
141
+ const now = new Date();
142
+ const currentTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
143
+
144
+ const startTime = filterItem.filter_start_time;
145
+ const endTime = filterItem.filter_end_time;
146
+
147
+ // 处理跨天的情况(例如 22:00 到 06:00)
148
+ if (startTime > endTime) {
149
+ return currentTime >= startTime || currentTime <= endTime;
150
+ } else {
151
+ return currentTime >= startTime && currentTime <= endTime;
152
+ }
153
+ },
154
+
155
+ extractTrailingHttpUrl: (str) => {
156
+ const regex = /\/proxy\/(http|https):\/\/.+$/;
157
+ const match = str.match(regex);
158
+ if (!match) {
159
+ return null;
160
+ } else {
161
+ const mixed_url = match[0];
162
+ const url = mixed_url.replace(/^\/proxy\//,'');
163
+ return url;
164
+ }
165
+ }
166
+ };
@@ -0,0 +1,321 @@
1
+ // socks5-proxy.js
2
+ const path = require('path');
3
+ const net = require('net');
4
+ const dgram = require('dgram');
5
+ const tls = require('tls');
6
+ const crypto = require('crypto');
7
+ const fs = require('fs');
8
+ const _fs = require('../proxy/fs.js');
9
+ const { pipeline } = require('stream');
10
+
11
+ // 固定下游 HTTP 代理地址(可改为配置项)
12
+ const DOWNSTREAM_HTTP_PROXY_HOST = '127.0.0.1';
13
+ const keyFile = path.join(__dirname, '../cert/rootCA.key');
14
+ const crtFile = path.join(__dirname, '../cert/rootCA.crt');
15
+ const ticketKeyPath = path.join(__dirname, './ticket-keys.bin');
16
+
17
+ function initTicketKeyFile() {
18
+ if (!fs.existsSync(ticketKeyPath)) {
19
+ fs.writeFileSync(ticketKeyPath, crypto.randomBytes(48));
20
+ }
21
+ }
22
+
23
+ function getTicketKeys() {
24
+ initTicketKeyFile();
25
+ return fs.readFileSync(ticketKeyPath) || crypto.randomBytes(48);
26
+ }
27
+
28
+ async function init() {
29
+ initTicketKeyFile();
30
+ const ticketKeys = getTicketKeys();
31
+
32
+ try {
33
+ const loadedConfig = await _fs.readConfig();
34
+
35
+ const DOWNSTREAM_HTTP_PROXY_PORT = loadedConfig.proxy_port;
36
+ const LISTEN_PORT = loadedConfig.socks5_port;
37
+
38
+ // 从配置加载 TLS 证书和密钥路径
39
+ const certPath = crtFile;
40
+ const keyPath = keyFile;
41
+
42
+ if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
43
+ console.error(`❌ TLS 证书或私钥文件不存在: cert=${certPath}, key=${keyPath}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ const TLS_CERT = fs.readFileSync(certPath);
48
+ const TLS_KEY = fs.readFileSync(keyPath);
49
+
50
+ const AUTH_CREDENTIALS = {
51
+ username: loadedConfig.auth_username,
52
+ password: loadedConfig.auth_password,
53
+ };
54
+
55
+ // 工具函数:解析目标地址(IPv4 / 域名 / IPv6)
56
+ function parseAddress(buf, offset) {
57
+ const atyp = buf[offset];
58
+ let host, port, nextOffset;
59
+
60
+ if (atyp === 0x01) {
61
+ // IPv4
62
+ host = buf.slice(offset + 1, offset + 5).join('.');
63
+ port = buf.readUInt16BE(offset + 5);
64
+ nextOffset = offset + 7;
65
+ } else if (atyp === 0x03) {
66
+ // Domain name
67
+ const len = buf[offset + 1];
68
+ host = buf.slice(offset + 2, offset + 2 + len).toString();
69
+ port = buf.readUInt16BE(offset + 2 + len);
70
+ nextOffset = offset + 2 + len + 2;
71
+ } else if (atyp === 0x04) {
72
+ // IPv6 (简化表示)
73
+ const ipv6Bytes = buf.slice(offset + 1, offset + 17);
74
+ host = '[' + ipv6Bytes.reduce((acc, byte, i) => {
75
+ if (i % 2 === 0 && i > 0) acc += ':';
76
+ return acc + byte.toString(16).padStart(2, '0');
77
+ }, '').replace(/00/g, '0').replace(/(^|:)0+([0-9a-f]+)/g, '$1$2') + ']';
78
+ port = buf.readUInt16BE(offset + 17);
79
+ nextOffset = offset + 19;
80
+ } else {
81
+ throw new Error('Unsupported address type: ' + atyp);
82
+ }
83
+
84
+ return { host, port, nextOffset };
85
+ }
86
+
87
+ // 发送 SOCKS5 响应包
88
+ function sendResponse(socket, status, atyp = 0x01, bindAddr = '0.0.0.0', bindPort = 0) {
89
+ const resp = Buffer.alloc(10);
90
+ resp[0] = 0x05; // VER
91
+ resp[1] = status; // REP
92
+ resp[2] = 0x00; // RSV
93
+ resp[3] = atyp; // ATYP
94
+
95
+ if (atyp === 0x01) {
96
+ // IPv4: 0.0.0.0
97
+ resp[4] = 0;
98
+ resp[5] = 0;
99
+ resp[6] = 0;
100
+ resp[7] = 0;
101
+ } else if (atyp === 0x03) {
102
+ // 域名(此处不使用)
103
+ resp[4] = 0;
104
+ } else if (atyp === 0x04) {
105
+ // IPv6: :: (16 bytes of 0)
106
+ resp.fill(0, 4, 20);
107
+ }
108
+
109
+ resp.writeUInt16BE(bindPort, atyp === 0x01 ? 8 : (atyp === 0x03 ? 5 : 20));
110
+ const len = atyp === 0x01 ? 10 : (atyp === 0x03 ? 5 + resp[4] + 2 : 22);
111
+ socket.write(resp.slice(0, len));
112
+ }
113
+
114
+ function handleTcpRequest(clientSocket, targetHost, targetPort) {
115
+ clientSocket.setTimeout(30_000);
116
+ clientSocket.on('timeout', () => clientSocket.destroy());
117
+
118
+ const proxySocket = net.connect(DOWNSTREAM_HTTP_PROXY_PORT, DOWNSTREAM_HTTP_PROXY_HOST, () => {
119
+ const connectReq = `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n\r\n`;
120
+ proxySocket.write(connectReq);
121
+
122
+ const chunks = [];
123
+ let totalLen = 0;
124
+ const onProxyData = (chunk) => {
125
+ if (clientSocket.destroyed || proxySocket.destroyed) return;
126
+
127
+ chunks.push(chunk);
128
+ totalLen += chunk.length;
129
+ const buf = Buffer.concat(chunks, totalLen);
130
+ if (buf.indexOf('\r\n\r\n') !== -1) {
131
+ proxySocket.removeListener('data', onProxyData);
132
+ const str = buf.toString();
133
+ if (!str.match(/^HTTP\/1\.[01] 200/)) {
134
+ sendResponse(clientSocket, 0x05);
135
+ clientSocket.destroy();
136
+ proxySocket.destroy();
137
+ return;
138
+ }
139
+ sendResponse(clientSocket, 0x00);
140
+
141
+ // 👇 高效双向转发
142
+ clientSocket.pipe(proxySocket);
143
+ proxySocket.pipe(clientSocket);
144
+ }
145
+ };
146
+ proxySocket.on('data', onProxyData);
147
+ });
148
+
149
+ proxySocket.setTimeout(30_000);
150
+ proxySocket.on('timeout', () => proxySocket.destroy());
151
+ proxySocket.on('error', (err) => {
152
+ console.warn(`Proxy error: ${err.message}`);
153
+ if (!clientSocket.destroyed) {
154
+ sendResponse(clientSocket, 0x05);
155
+ clientSocket.destroy();
156
+ }
157
+ });
158
+ clientSocket.on('error', () => proxySocket.destroy());
159
+ clientSocket.on('close', () => proxySocket.destroy());
160
+ }
161
+
162
+ // 处理 UDP ASSOCIATE(本地 UDP 中继)
163
+ function handleUdpAssociate(clientSocket) {
164
+ const udpRelay = dgram.createSocket('udp4');
165
+ udpRelay.on('message', (msg, rinfo) => {
166
+ // 注意:标准 SOCKS5 UDP 包含 header,但此处简化直接回传(适用于 DNS 等)
167
+ // 生产环境建议按 RFC 1928 封装/解封装
168
+ clientSocket.write(msg);
169
+ });
170
+
171
+ udpRelay.on('error', (err) => {
172
+ console.error('UDP relay error:', err);
173
+ clientSocket.destroy();
174
+ });
175
+
176
+ const localAddr = udpRelay.address();
177
+ // 告诉客户端 UDP 中继地址(返回 127.0.0.1 + 端口)
178
+ sendResponse(clientSocket, 0x00, 0x01, '127.0.0.1', localAddr.port);
179
+
180
+ // 清理
181
+ clientSocket.on('close', () => udpRelay.close());
182
+ clientSocket.on('error', () => udpRelay.close());
183
+ }
184
+
185
+ // console.log('ticketKeys length:', ticketKeys.length); // 必须是 48!
186
+
187
+ // TLS 服务器选项
188
+ const tlsOptions = {
189
+ key: TLS_KEY,
190
+ cert: TLS_CERT,
191
+ minVersion: 'TLSv1.2',
192
+ // 👇 启用会话缓存(Session ID + Session Tickets)
193
+ sessionTimeout: 300, // 会话有效期(秒),默认 300
194
+ ticketKeys: ticketKeys
195
+ };
196
+
197
+ // 创建 TLS 封装的 SOCKS5 服务器
198
+ const server = tls.createServer(tlsOptions, async (socket) => {
199
+ try {
200
+ // Step 1: 协商认证方法
201
+ const authMethodsBuf = await new Promise((resolve) => {
202
+ socket.once('data', resolve);
203
+ });
204
+
205
+ if (authMethodsBuf.length < 2) {
206
+ socket.destroy();
207
+ return;
208
+ }
209
+
210
+ const nmethods = authMethodsBuf[1];
211
+ if (authMethodsBuf.length !== 2 + nmethods) {
212
+ socket.destroy();
213
+ return;
214
+ }
215
+
216
+ let method = 0xff; // 不支持任何方法
217
+ for (let i = 0; i < nmethods; i++) {
218
+ const m = authMethodsBuf[2 + i];
219
+ if (m === 0x02) method = 0x02; // 用户名/密码
220
+ if (m === 0x00 && method === 0xff) method = 0x00; // 匿名
221
+ }
222
+
223
+ socket.write(Buffer.from([0x05, method]));
224
+
225
+ // Step 2: 执行认证
226
+ if (method === 0x02) {
227
+ const authData = await new Promise((resolve) => {
228
+ socket.once('data', resolve);
229
+ });
230
+
231
+ if (authData.length < 2) {
232
+ socket.write(Buffer.from([0x01, 0xff]));
233
+ socket.destroy();
234
+ return;
235
+ }
236
+
237
+ const ulen = authData[1];
238
+ if (authData.length < 2 + ulen + 1) {
239
+ socket.write(Buffer.from([0x01, 0xff]));
240
+ socket.destroy();
241
+ return;
242
+ }
243
+
244
+ const username = authData.slice(2, 2 + ulen).toString();
245
+ const plen = authData[2 + ulen];
246
+ if (authData.length < 2 + ulen + 1 + plen) {
247
+ socket.write(Buffer.from([0x01, 0xff]));
248
+ socket.destroy();
249
+ return;
250
+ }
251
+
252
+ const password = authData.slice(2 + ulen + 1, 2 + ulen + 1 + plen).toString();
253
+
254
+ if (username !== AUTH_CREDENTIALS.username || password !== AUTH_CREDENTIALS.password) {
255
+ socket.write(Buffer.from([0x01, 0xff])); // 认证失败
256
+ socket.destroy();
257
+ return;
258
+ }
259
+ socket.write(Buffer.from([0x01, 0x00])); // 成功
260
+ }
261
+
262
+ // Step 3: 处理请求
263
+ const requestBuf = await new Promise((resolve) => {
264
+ socket.once('data', resolve);
265
+ });
266
+
267
+ if (requestBuf.length < 4) {
268
+ socket.destroy();
269
+ return;
270
+ }
271
+
272
+ const cmd = requestBuf[1];
273
+ let target;
274
+ try {
275
+ target = parseAddress(requestBuf, 3);
276
+ } catch (e) {
277
+ sendResponse(socket, 0x08); // Address type not supported
278
+ socket.destroy();
279
+ return;
280
+ }
281
+
282
+ if (cmd === 0x01) {
283
+ // CONNECT
284
+ handleTcpRequest(socket, target.host, target.port);
285
+ } else if (cmd === 0x03) {
286
+ // UDP ASSOCIATE
287
+ handleUdpAssociate(socket);
288
+ } else {
289
+ sendResponse(socket, 0x07); // Command not supported
290
+ socket.destroy();
291
+ }
292
+ } catch (err) {
293
+ console.error('SOCKS5 over TLS session error:', err.message);
294
+ socket.destroy();
295
+ }
296
+ });
297
+
298
+ // 错误处理
299
+ server.on('tlsClientError', (err, tlsSocket) => {
300
+ console.warn('TLS handshake failed:', err.message);
301
+ tlsSocket?.destroy();
302
+ });
303
+
304
+ server.on('error', (err) => {
305
+ console.error('SOCKS5 TLS server error:', err);
306
+ });
307
+
308
+ // 启动监听
309
+ 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`);
314
+ });
315
+ } catch (err) {
316
+ console.error('Failed to initialize SOCKS5-TLS proxy:', err);
317
+ process.exit(1);
318
+ }
319
+ }
320
+
321
+ module.exports.init = init;
@@ -0,0 +1,7 @@
1
+ // 启动 Socks5 代理
2
+ // /socks5/start.js
3
+ const Socks5 = require('./server');
4
+
5
+ (async () => {
6
+ await Socks5.init();
7
+ })();
@@ -0,0 +1,40 @@
1
+ const tls = require('tls');
2
+
3
+ const options = {
4
+ host: '127.0.0.1',
5
+ port: 8002,
6
+ rejectUnauthorized: false,
7
+ };
8
+
9
+ let firstSession = null;
10
+ let sessionReceived = false;
11
+
12
+ const sock1 = tls.connect(options);
13
+
14
+ sock1.on('session', (session) => {
15
+ // 👈 关键!只有收到 session ticket 后才保存
16
+ firstSession = session;
17
+ sessionReceived = true;
18
+ console.log('🔑 Received session ticket');
19
+ });
20
+
21
+ sock1.on('secureConnect', () => {
22
+ console.log('✅ Conn 1: reused?', sock1.isSessionReused());
23
+ sock1.end();
24
+
25
+ // 等待 session 到达(加一点延迟保险)
26
+ const trySecond = () => {
27
+ if (sessionReceived && firstSession) {
28
+ const sock2 = tls.connect({
29
+ ...options,
30
+ session: firstSession,
31
+ }, () => {
32
+ console.log('✅ Conn 2: reused?', sock2.isSessionReused());
33
+ sock2.end();
34
+ });
35
+ } else {
36
+ setTimeout(trySecond, 10); // 最多等 100ms
37
+ }
38
+ };
39
+ setTimeout(trySecond, 5);
40
+ });