block-proxy 0.1.7 → 0.1.8
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/LICENSE +1 -1
- package/build/asset-manifest.json +15 -0
- package/build/favicon.ico +0 -0
- package/build/index.html +1 -0
- package/build/iphone-proxy-setting.jpg +0 -0
- package/build/logo192.png +0 -0
- package/build/logo512.png +0 -0
- package/build/manifest.json +25 -0
- package/build/proxy.jpg +0 -0
- package/build/robots.txt +3 -0
- package/build/static/css/main.8bfa3d5f.css +2 -0
- package/build/static/css/main.8bfa3d5f.css.map +1 -0
- package/build/static/js/835.f7452062.chunk.js +2 -0
- package/build/static/js/835.f7452062.chunk.js.map +1 -0
- package/build/static/js/main.2247fb80.js +3 -0
- package/build/static/js/main.2247fb80.js.LICENSE.txt +49 -0
- package/build/static/js/main.2247fb80.js.map +1 -0
- package/config.json +6 -2
- package/package.json +1 -1
- package/proxy/scan.js +7 -2
- package/socks5/server.js +201 -14
- package/src/App.css +3 -1
- package/src/App.js +8 -9
package/config.json
CHANGED
|
@@ -135,8 +135,8 @@
|
|
|
135
135
|
"web_interface_port": 8003,
|
|
136
136
|
"your_domain": "yui.cool",
|
|
137
137
|
"vpn_proxy": "",
|
|
138
|
-
"auth_username": "",
|
|
139
|
-
"auth_password": "",
|
|
138
|
+
"auth_username": "lijing",
|
|
139
|
+
"auth_password": "lijing",
|
|
140
140
|
"enable_express": "1",
|
|
141
141
|
"enable_socks5": "1",
|
|
142
142
|
"socks5_port": 8002,
|
|
@@ -197,6 +197,10 @@
|
|
|
197
197
|
{
|
|
198
198
|
"ip": "192.168.124.251",
|
|
199
199
|
"mac": "6E:FB:18:4D:9C:3E"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"ip": "192.168.240.207",
|
|
203
|
+
"mac": "32:BC:EC:7C:66:F6"
|
|
200
204
|
}
|
|
201
205
|
]
|
|
202
206
|
}
|
package/package.json
CHANGED
package/proxy/scan.js
CHANGED
|
@@ -41,6 +41,11 @@ function parseArpTable(arpOutput, subnet) {
|
|
|
41
41
|
macMatch = line.match(/(([0-9a-fA-F]{1,2}[:\-]){5}([0-9a-fA-F]{1,2}))/);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
if (macMatch && macMatch[0].startsWith("0:0:")) {
|
|
45
|
+
// 如果 mac 是 "0:0:0:0:4:1" 之类的,说明子网禁止扫描
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
if (macMatch) {
|
|
45
50
|
let mac = macMatch[1].toUpperCase();
|
|
46
51
|
if (process.platform !== 'win32') {
|
|
@@ -77,9 +82,9 @@ async function doScan() {
|
|
|
77
82
|
const subnet = getLocalSubnet();
|
|
78
83
|
// 如果是 30 网段,直接返回空
|
|
79
84
|
if (subnet.startsWith("30.")) {
|
|
80
|
-
return []
|
|
85
|
+
return [];
|
|
81
86
|
}
|
|
82
|
-
console.log(
|
|
87
|
+
console.log(`正在扫描网段: ${subnet}.0/24`);
|
|
83
88
|
|
|
84
89
|
// Ping all IPs to populate ARP cache
|
|
85
90
|
const ips = Array.from({ length: 254 }, (_, i) => `${subnet}.${i + 1}`);
|
package/socks5/server.js
CHANGED
|
@@ -113,6 +113,8 @@ async function init() {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
function handleTcpRequest(clientSocket, targetHost, targetPort) {
|
|
116
|
+
// jayli
|
|
117
|
+
// console.log(targetHost);
|
|
116
118
|
clientSocket.setTimeout(30_000);
|
|
117
119
|
clientSocket.on('timeout', () => clientSocket.destroy());
|
|
118
120
|
|
|
@@ -160,13 +162,199 @@ async function init() {
|
|
|
160
162
|
clientSocket.on('close', () => proxySocket.destroy());
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
// 处理 UDP ASSOCIATE
|
|
165
|
+
// 处理 UDP ASSOCIATE(RFC 1928 合规实现)
|
|
164
166
|
function handleUdpAssociate(clientSocket) {
|
|
165
167
|
const udpRelay = dgram.createSocket('udp4');
|
|
168
|
+
let clientUdpAddr = null; // 客户端的 UDP 地址(用于回包)
|
|
169
|
+
|
|
170
|
+
// 存储 { 'host:port': { rinfo } } 用于回包时知道发给谁
|
|
171
|
+
const targetToClientMap = new Map();
|
|
172
|
+
|
|
173
|
+
// 绑定到任意端口
|
|
174
|
+
udpRelay.bind(0, '127.0.0.1', () => {
|
|
175
|
+
const localAddr = udpRelay.address();
|
|
176
|
+
// 告诉客户端 UDP 中继地址(必须是 127.0.0.1 或公网 IP,不能是 0.0.0.0)
|
|
177
|
+
sendResponse(clientSocket, 0x00, 0x01, '127.0.0.1', localAddr.port);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// 接收来自客户端的 UDP 包(带 SOCKS5 header)
|
|
181
|
+
udpRelay.on('message', (msg, rinfo) => {
|
|
182
|
+
if (!clientUdpAddr) {
|
|
183
|
+
// 第一个包来自客户端,记录其地址(后续回包用)
|
|
184
|
+
clientUdpAddr = rinfo;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (msg.length < 10) {
|
|
188
|
+
// 最小 UDP 请求:VER=0 + RSV=0 + FRAG=0 + ATYP=1 (IPv4) + ADDR(4) + PORT(2) = 10
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const ver = msg[0];
|
|
193
|
+
const frag = msg[2]; // 分片不支持
|
|
194
|
+
if (ver !== 0x00 || frag !== 0x00) {
|
|
195
|
+
return; // 不支持分片或错误版本
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const atyp = msg[3];
|
|
200
|
+
let headerLen = 0;
|
|
201
|
+
let targetHost, targetPort;
|
|
202
|
+
|
|
203
|
+
if (atyp === 0x01) {
|
|
204
|
+
// IPv4
|
|
205
|
+
targetHost = msg.slice(4, 8).join('.');
|
|
206
|
+
targetPort = msg.readUInt16BE(8);
|
|
207
|
+
headerLen = 10;
|
|
208
|
+
} else if (atyp === 0x03) {
|
|
209
|
+
// Domain
|
|
210
|
+
const len = msg[4];
|
|
211
|
+
if (msg.length < 5 + len + 2) return;
|
|
212
|
+
targetHost = msg.slice(5, 5 + len).toString();
|
|
213
|
+
targetPort = msg.readUInt16BE(5 + len);
|
|
214
|
+
headerLen = 5 + len + 2;
|
|
215
|
+
} else if (atyp === 0x04) {
|
|
216
|
+
// IPv6 —— 简化:只取原始字节,Node.js dgram 支持字符串格式
|
|
217
|
+
if (msg.length < 22) return;
|
|
218
|
+
const ipv6Bytes = msg.slice(4, 20);
|
|
219
|
+
targetHost = '[' + ipv6Bytes.reduce((acc, byte, i) => {
|
|
220
|
+
if (i % 2 === 0 && i > 0) acc += ':';
|
|
221
|
+
return acc + byte.toString(16).padStart(2, '0');
|
|
222
|
+
}, '').replace(/(^|:)0+([0-9a-f]+)/g, '$1$2') + ']';
|
|
223
|
+
targetPort = msg.readUInt16BE(20);
|
|
224
|
+
headerLen = 22;
|
|
225
|
+
} else {
|
|
226
|
+
return; // 不支持的地址类型
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const payload = msg.slice(headerLen);
|
|
230
|
+
if (payload.length === 0) return;
|
|
231
|
+
|
|
232
|
+
// 构建目标唯一键(用于回包映射)
|
|
233
|
+
const targetKey = `${targetHost}:${targetPort}`;
|
|
234
|
+
|
|
235
|
+
// 创建临时 socket 发送数据(避免端口复用问题)
|
|
236
|
+
const outSocket = dgram.createSocket('udp4');
|
|
237
|
+
outSocket.send(payload, targetPort, targetHost, (err) => {
|
|
238
|
+
if (err) {
|
|
239
|
+
console.warn(`UDP forward error to ${targetHost}:${targetPort}:`, err.message);
|
|
240
|
+
}
|
|
241
|
+
outSocket.close();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// 记录该目标对应的客户端地址(用于响应包回传)
|
|
245
|
+
targetToClientMap.set(targetKey, rinfo);
|
|
246
|
+
|
|
247
|
+
// 可选:加个超时自动清理(简化起见这里省略,靠 close 清理)
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.warn('UDP parse error:', e.message);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// 接收从目标服务器返回的 UDP 响应,并转发回客户端
|
|
254
|
+
udpRelay.on('listening', () => {
|
|
255
|
+
// Node.js 不会自动监听入站响应,但我们已经在 bind 后处于 listening 状态
|
|
256
|
+
// 所有 inbound UDP 都会触发 'message',包括响应
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// 注意:响应包也会触发 'message',但来源是外部服务器(不是 clientUdpAddr)
|
|
260
|
+
// 所以我们需要在上面的逻辑中区分:如果是来自已知 target 的响应,则回包
|
|
261
|
+
|
|
262
|
+
// 重写 message handler 以同时处理“客户端请求”和“服务器响应”
|
|
263
|
+
// 我们已经做了:所有包都进同一个 handler,通过 targetToClientMap 判断是否是响应
|
|
264
|
+
|
|
265
|
+
// 但我们还需要:当收到外部服务器的响应时,把它封装后发回 clientUdpAddr
|
|
266
|
+
// 所以上面的 handler 已经能处理请求,现在补充响应回包逻辑:
|
|
267
|
+
|
|
268
|
+
// 实际上,上面的 handler 只处理了“客户端 → 代理”的包。
|
|
269
|
+
// “目标服务器 → 代理”的包也会进同一个 handler,但此时 rinfo ≠ clientUdpAddr,
|
|
270
|
+
// 且不在 targetToClientMap 的 key 中(因为 key 是 host:port,而 rinfo 是源地址)。
|
|
271
|
+
|
|
272
|
+
// 所以我们需要换一种方式:**为每个目标创建独立的 socket?**
|
|
273
|
+
// 但那样太重。更高效的做法是:**用单个 relay socket,靠 targetToClientMap 映射**
|
|
274
|
+
|
|
275
|
+
// ✅ 正确做法:在收到外部响应时,根据 (rinfo.address:rinfo.port) 查找是否是我们发出的请求的目标
|
|
276
|
+
// 但注意:我们发的是 targetHost:targetPort,而响应来自 same address:port
|
|
277
|
+
|
|
278
|
+
// 所以我们在发送时,应该用 **rinfo.address:rinfo.port 作为 key 存 clientAddr**
|
|
279
|
+
// 但这样不行,因为多个客户端可能访问同一目标。
|
|
280
|
+
|
|
281
|
+
// 🚨 更健壮的方式:**每个客户端有自己的 udpRelay**(当前就是这么做的!)
|
|
282
|
+
// 所以在这个函数内,所有流量都属于同一个 SOCKS5 TCP 会话的客户端。
|
|
283
|
+
// 因此,我们可以安全地假设:**任何非 clientUdpAddr 的 UDP 包都是目标服务器的响应**
|
|
284
|
+
|
|
285
|
+
// 修改 message handler 如下(替换上面的 handler):
|
|
286
|
+
udpRelay.removeAllListeners('message');
|
|
166
287
|
udpRelay.on('message', (msg, rinfo) => {
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
288
|
+
// 判断是客户端发来的请求,还是目标服务器的响应
|
|
289
|
+
if (clientUdpAddr && rinfo.address === clientUdpAddr.address && rinfo.port === clientUdpAddr.port) {
|
|
290
|
+
// ← 来自客户端的请求(带 header)
|
|
291
|
+
if (msg.length < 10) return;
|
|
292
|
+
const ver = msg[0];
|
|
293
|
+
const frag = msg[2];
|
|
294
|
+
if (ver !== 0x00 || frag !== 0x00) return;
|
|
295
|
+
|
|
296
|
+
const atyp = msg[3];
|
|
297
|
+
let headerLen = 0, targetHost, targetPort;
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
if (atyp === 0x01) {
|
|
301
|
+
targetHost = msg.slice(4, 8).join('.');
|
|
302
|
+
targetPort = msg.readUInt16BE(8);
|
|
303
|
+
headerLen = 10;
|
|
304
|
+
} else if (atyp === 0x03) {
|
|
305
|
+
const len = msg[4];
|
|
306
|
+
if (msg.length < 5 + len + 2) return;
|
|
307
|
+
targetHost = msg.slice(5, 5 + len).toString();
|
|
308
|
+
targetPort = msg.readUInt16BE(5 + len);
|
|
309
|
+
headerLen = 5 + len + 2;
|
|
310
|
+
} else if (atyp === 0x04) {
|
|
311
|
+
if (msg.length < 22) return;
|
|
312
|
+
const ipv6Bytes = msg.slice(4, 20);
|
|
313
|
+
targetHost = '[' + ipv6Bytes.reduce((acc, byte, i) => {
|
|
314
|
+
if (i % 2 === 0 && i > 0) acc += ':';
|
|
315
|
+
return acc + byte.toString(16).padStart(2, '0');
|
|
316
|
+
}, '').replace(/(^|:)0+([0-9a-f]+)/g, '$1$2') + ']';
|
|
317
|
+
targetPort = msg.readUInt16BE(20);
|
|
318
|
+
headerLen = 22;
|
|
319
|
+
} else {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const payload = msg.slice(headerLen);
|
|
324
|
+
if (payload.length === 0) return;
|
|
325
|
+
|
|
326
|
+
// 发送到目标
|
|
327
|
+
udpRelay.send(payload, targetPort, targetHost, (err) => {
|
|
328
|
+
if (err) {
|
|
329
|
+
console.warn(`UDP send error to ${targetHost}:${targetPort}:`, err.message);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.warn('UDP request parse error:', e.message);
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
// ← 来自目标服务器的响应(裸 payload),需要封装后发回客户端
|
|
337
|
+
if (!clientUdpAddr) return; // 还没收到客户端请求
|
|
338
|
+
|
|
339
|
+
// 构建 SOCKS5 UDP response header
|
|
340
|
+
const respHeader = Buffer.alloc(10);
|
|
341
|
+
respHeader[0] = 0x00; // RSV
|
|
342
|
+
respHeader[1] = 0x00; // RSV
|
|
343
|
+
respHeader[2] = 0x00; // FRAG
|
|
344
|
+
respHeader[3] = 0x01; // ATYP = IPv4 (简化:统一返回 IPv4 0.0.0.0)
|
|
345
|
+
respHeader[4] = 0;
|
|
346
|
+
respHeader[5] = 0;
|
|
347
|
+
respHeader[6] = 0;
|
|
348
|
+
respHeader[7] = 0;
|
|
349
|
+
respHeader.writeUInt16BE(rinfo.port, 8); // 源端口作为 DST.PORT(部分客户端依赖)
|
|
350
|
+
|
|
351
|
+
const response = Buffer.concat([respHeader, msg]);
|
|
352
|
+
udpRelay.send(response, clientUdpAddr.port, clientUdpAddr.address, (err) => {
|
|
353
|
+
if (err) {
|
|
354
|
+
console.warn('UDP send back to client error:', err.message);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
170
358
|
});
|
|
171
359
|
|
|
172
360
|
udpRelay.on('error', (err) => {
|
|
@@ -174,17 +362,16 @@ async function init() {
|
|
|
174
362
|
clientSocket.destroy();
|
|
175
363
|
});
|
|
176
364
|
|
|
177
|
-
const localAddr = udpRelay.address();
|
|
178
|
-
// 告诉客户端 UDP 中继地址(返回 127.0.0.1 + 端口)
|
|
179
|
-
sendResponse(clientSocket, 0x00, 0x01, '127.0.0.1', localAddr.port);
|
|
180
|
-
|
|
181
365
|
// 清理
|
|
182
|
-
|
|
183
|
-
|
|
366
|
+
const cleanup = () => {
|
|
367
|
+
if (!udpRelay._closed) {
|
|
368
|
+
udpRelay.close();
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
clientSocket.on('close', cleanup);
|
|
372
|
+
clientSocket.on('error', cleanup);
|
|
184
373
|
}
|
|
185
374
|
|
|
186
|
-
// console.log('ticketKeys length:', ticketKeys.length); // 必须是 48!
|
|
187
|
-
|
|
188
375
|
// TLS 服务器选项
|
|
189
376
|
const tlsOptions = {
|
|
190
377
|
key: TLS_KEY,
|
|
@@ -297,13 +484,13 @@ async function init() {
|
|
|
297
484
|
socket.destroy();
|
|
298
485
|
}
|
|
299
486
|
} catch (err) {
|
|
300
|
-
console.error('SOCKS5 over TLS session error:', err
|
|
487
|
+
console.error('SOCKS5 over TLS session error:', err);
|
|
301
488
|
socket.destroy();
|
|
302
489
|
}
|
|
303
490
|
});
|
|
304
491
|
|
|
305
492
|
server.on('clientError', (err, socket) => {
|
|
306
|
-
console.warn('TLS client error during handshake:', err
|
|
493
|
+
console.warn('TLS client error during handshake:', err);
|
|
307
494
|
socket?.end(); // 安全关闭
|
|
308
495
|
});
|
|
309
496
|
|
package/src/App.css
CHANGED
|
@@ -271,9 +271,11 @@ span.host-text {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
.setting-row label {
|
|
274
|
-
|
|
274
|
+
width: 220px; /* 固定宽度,足够容纳最长的标签 */
|
|
275
|
+
text-align: right;
|
|
275
276
|
font-weight: bold;
|
|
276
277
|
color: #555;
|
|
278
|
+
flex-shrink: 0; /* 防止被压缩 */
|
|
277
279
|
}
|
|
278
280
|
|
|
279
281
|
.setting-row input {
|
package/src/App.js
CHANGED
|
@@ -541,7 +541,7 @@ function App() {
|
|
|
541
541
|
onChange={(e) => updateFilterTime(index, e.target.value, getFilterTimes(host).end)}
|
|
542
542
|
/>
|
|
543
543
|
</label>
|
|
544
|
-
<label
|
|
544
|
+
<label>~</label>
|
|
545
545
|
<label>
|
|
546
546
|
<input
|
|
547
547
|
type="time"
|
|
@@ -564,7 +564,6 @@ function App() {
|
|
|
564
564
|
|
|
565
565
|
<div className="config-section">
|
|
566
566
|
<h2>HTTP/Socks5 端口设置,验证信息,下游 VPN_Proxy 代理</h2>
|
|
567
|
-
{/*<p><span>配置页端口默认 8004</span></p> */}
|
|
568
567
|
<div className="setting-row">
|
|
569
568
|
<label>Anyproxy HTTP 代理端口:</label>
|
|
570
569
|
<input
|
|
@@ -594,38 +593,38 @@ function App() {
|
|
|
594
593
|
<div className="setting-row">
|
|
595
594
|
<label>代理用户名:</label>
|
|
596
595
|
<input
|
|
597
|
-
type="
|
|
596
|
+
type="text"
|
|
598
597
|
value={config.auth_username}
|
|
599
598
|
onChange={(e) => setConfig({...config, auth_username: e.target.value || ""})}
|
|
600
599
|
/>
|
|
601
600
|
</div>
|
|
602
601
|
<div className="setting-row">
|
|
603
|
-
<label
|
|
602
|
+
<label>代理密码:</label>
|
|
604
603
|
<input
|
|
605
|
-
type="
|
|
604
|
+
type="text"
|
|
606
605
|
value={config.auth_password}
|
|
607
606
|
onChange={(e) => setConfig({...config, auth_password: e.target.value || ""})}
|
|
608
607
|
/>
|
|
609
608
|
</div>
|
|
610
609
|
|
|
611
|
-
<div className="setting-row">
|
|
610
|
+
<div className="setting-row full-width">
|
|
612
611
|
<label>公网域名:</label>
|
|
613
|
-
<div>如果要公网可访问,OpenWRT 配置相同的端口转发到 AnyProxy 代理端口,这里写公网域名<br />(仅在浏览器通过公网域名+端口查看系统水位时防止回环)</div>
|
|
614
612
|
<input
|
|
615
613
|
type="text"
|
|
616
614
|
value={config.your_domain}
|
|
617
615
|
onChange={(e) => setConfig({...config, your_domain: e.target.value || ""})}
|
|
618
616
|
/>
|
|
617
|
+
<div className="help-text">如果要公网可访问,OpenWRT 配置相同的端口转发到 AnyProxy 代理端口,这里写公网域名<br />(仅在浏览器通过公网域名+端口查看系统水位时防止回环)</div>
|
|
619
618
|
</div>
|
|
620
619
|
|
|
621
|
-
<div className="setting-row">
|
|
620
|
+
<div className="setting-row full-width">
|
|
622
621
|
<label>VPN_PROXY 设置(留空):</label>
|
|
623
|
-
<div>(格式:“127.0.0.1:1087”,仅调试用)</div>
|
|
624
622
|
<input
|
|
625
623
|
type="text"
|
|
626
624
|
value={config.vpn_proxy}
|
|
627
625
|
onChange={(e) => setConfig({...config, vpn_proxy: e.target.value || ""})}
|
|
628
626
|
/>
|
|
627
|
+
<div className="help-text">(格式:“127.0.0.1:1087”,仅调试用)</div>
|
|
629
628
|
</div>
|
|
630
629
|
<div className="setting-row actions">
|
|
631
630
|
<button
|