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.
- package/.eslintignore +3 -0
- package/AD_BLOCK.md +38 -0
- package/Dockerfile +51 -0
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/bin/start.js +45 -0
- package/cert/rootCA.crt +20 -0
- package/cert/rootCA.key +27 -0
- package/config.json +234 -0
- package/craco.config.js +52 -0
- package/hack-of-anyproxy/lib/requestHandler.js +1028 -0
- package/package.json +54 -0
- package/proxy/attacker.js +135 -0
- package/proxy/domain.js +26 -0
- package/proxy/fs.js +46 -0
- package/proxy/http.js +224 -0
- package/proxy/mitm/persistentStore.js +34 -0
- package/proxy/mitm/persistentStore.json +3 -0
- package/proxy/mitm/rule.js +116 -0
- package/proxy/mitm/uaFilter.js +47 -0
- package/proxy/mitm/ydcd/ydcd.js +34 -0
- package/proxy/mitm/youtube/youtube.response.js +39 -0
- package/proxy/monitor.js +283 -0
- package/proxy/proxy.js +1488 -0
- package/proxy/scan.js +120 -0
- package/proxy/start.js +7 -0
- package/proxy/wanip.js +76 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/iphone-proxy-setting.jpg +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/proxy.jpg +0 -0
- package/public/robots.txt +3 -0
- package/server/express.js +232 -0
- package/server/start.js +24 -0
- package/server/util.js +166 -0
- package/socks5/server.js +321 -0
- package/socks5/start.js +7 -0
- package/socks5/test_tls_reuse.js +40 -0
- package/src/App.css +505 -0
- package/src/App.js +759 -0
- package/src/App.test.js +8 -0
- package/src/index.css +13 -0
- package/src/index.js +17 -0
- package/src/logo.svg +1 -0
- package/src/reportWebVitals.js +13 -0
- 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
|
+
};
|
package/socks5/server.js
ADDED
|
@@ -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;
|
package/socks5/start.js
ADDED
|
@@ -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
|
+
});
|