Upload 7 files
Browse files- .gitignore +35 -0
- Dockerfile +16 -0
- README.md +5 -0
- app.js +32 -0
- app_edtunnel.js +381 -0
- package-lock.json +72 -0
- package.json +14 -0
.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
2 |
+
|
3 |
+
# dependencies
|
4 |
+
/node_modules
|
5 |
+
/.pnp
|
6 |
+
.pnp.js
|
7 |
+
|
8 |
+
# testing
|
9 |
+
/coverage
|
10 |
+
|
11 |
+
# next.js
|
12 |
+
/.next/
|
13 |
+
/out/
|
14 |
+
|
15 |
+
# production
|
16 |
+
/build
|
17 |
+
|
18 |
+
# misc
|
19 |
+
.DS_Store
|
20 |
+
*.pem
|
21 |
+
|
22 |
+
# debug
|
23 |
+
npm-debug.log*
|
24 |
+
yarn-debug.log*
|
25 |
+
yarn-error.log*
|
26 |
+
|
27 |
+
# local env files
|
28 |
+
.env*.local
|
29 |
+
|
30 |
+
# vercel
|
31 |
+
.vercel
|
32 |
+
|
33 |
+
# typescript
|
34 |
+
*.tsbuildinfo
|
35 |
+
next-env.d.ts
|
Dockerfile
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 使用官方 Node.js 镜像作为基础镜像
|
2 |
+
FROM node:lts-alpine3.18
|
3 |
+
|
4 |
+
# 设置工作目录
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# 将应用程序文件复制到容器中
|
8 |
+
COPY . .
|
9 |
+
|
10 |
+
# EXPOSE 3000
|
11 |
+
|
12 |
+
# 安装应用程序的依赖
|
13 |
+
RUN npm install
|
14 |
+
|
15 |
+
# 设置默认的命令,即启动应用程序
|
16 |
+
CMD ["npm", "start"]
|
README.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
不能说的秘密
|
2 |
+
|
3 |
+
代码全是chatgpt写的 我一个变量都看不懂
|
4 |
+
|
5 |
+
https://www.chunqiujinjing.com/2023/07/28/free-vps-free-domain/
|
app.js
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const net=require('net');
|
2 |
+
const {WebSocket,createWebSocketStream}=require('ws');
|
3 |
+
const { TextDecoder } = require('util');
|
4 |
+
const logcb= (...args)=>console.log.bind(this,...args);
|
5 |
+
const errcb= (...args)=>console.error.bind(this,...args);
|
6 |
+
|
7 |
+
const uuid= (process.env.UUID||'d342d11e-d424-4583-b36e-524ab1f0afa4').replace(/-/g, "");
|
8 |
+
const port= process.env.PORT||3000;
|
9 |
+
|
10 |
+
const wss=new WebSocket.Server({port},logcb('listen:', port));
|
11 |
+
wss.on('connection', ws=>{
|
12 |
+
console.log("on connection")
|
13 |
+
ws.once('message', msg=>{
|
14 |
+
const [VERSION]=msg;
|
15 |
+
const id=msg.slice(1, 17);
|
16 |
+
if(!id.every((v,i)=>v==parseInt(uuid.substr(i*2,2),16))) return;
|
17 |
+
let i = msg.slice(17, 18).readUInt8()+19;
|
18 |
+
const port = msg.slice(i, i+=2).readUInt16BE(0);
|
19 |
+
const ATYP = msg.slice(i, i+=1).readUInt8();
|
20 |
+
const host= ATYP==1? msg.slice(i,i+=4).join('.')://IPV4
|
21 |
+
(ATYP==2? new TextDecoder().decode(msg.slice(i+1, i+=1+msg.slice(i,i+1).readUInt8()))://domain
|
22 |
+
(ATYP==3? msg.slice(i,i+=16).reduce((s,b,i,a)=>(i%2?s.concat(a.slice(i-1,i+1)):s), []).map(b=>b.readUInt16BE(0).toString(16)).join(':'):''));//ipv6
|
23 |
+
|
24 |
+
logcb('conn:', host,port);
|
25 |
+
ws.send(new Uint8Array([VERSION, 0]));
|
26 |
+
const duplex=createWebSocketStream(ws);
|
27 |
+
net.connect({host,port}, function(){
|
28 |
+
this.write(msg.slice(i));
|
29 |
+
duplex.on('error',errcb('E1:')).pipe(this).on('error',errcb('E2:')).pipe(duplex);
|
30 |
+
}).on('error',errcb('Conn-Err:',{host,port}));
|
31 |
+
}).on('error',errcb('EE:'));
|
32 |
+
});
|
app_edtunnel.js
ADDED
@@ -0,0 +1,381 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const WebSocket = require('ws');
|
2 |
+
const { Readable } = require('stream');
|
3 |
+
const net = require('net');
|
4 |
+
|
5 |
+
// 创建 WebSocket 服务器
|
6 |
+
const wss = new WebSocket.Server({ port: 443 });
|
7 |
+
let userID = 'd342d11e-d424-4583-b36e-524ab1f0afa4';
|
8 |
+
let proxyIP = "64.68.192." + Math.floor(Math.random() * 255);
|
9 |
+
|
10 |
+
let address = '';
|
11 |
+
let portWithRandomLog = '';
|
12 |
+
const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => {
|
13 |
+
console.log(`[${address}:${portWithRandomLog}] ${info}`, event || '');
|
14 |
+
};
|
15 |
+
|
16 |
+
// 监听连接事件
|
17 |
+
wss.on('connection', (ws) => {
|
18 |
+
console.log("ws connection")
|
19 |
+
// 在每个连接上设置消息处理逻辑
|
20 |
+
ws.once('message', (chunk) => {
|
21 |
+
console.log("on message")
|
22 |
+
let webSocket = ws;
|
23 |
+
let remoteSocketWapper = {
|
24 |
+
value: null,
|
25 |
+
};
|
26 |
+
let isDns = false;
|
27 |
+
|
28 |
+
const {
|
29 |
+
hasError,
|
30 |
+
message,
|
31 |
+
portRemote = 443,
|
32 |
+
addressRemote = '',
|
33 |
+
rawDataIndex,
|
34 |
+
vlessVersion = new Uint8Array([0, 0]),
|
35 |
+
isUDP,
|
36 |
+
} = processVlessHeader(chunk, userID);
|
37 |
+
|
38 |
+
address = addressRemote;
|
39 |
+
portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp '
|
40 |
+
} `;
|
41 |
+
if (hasError) {
|
42 |
+
// controller.error(message);
|
43 |
+
// throw new Error(message); // cf seems has bug, controller.error will not end stream
|
44 |
+
// webSocket.close(1000, message);
|
45 |
+
console.log('hasError,Connection closed:'+message);
|
46 |
+
ws.close();
|
47 |
+
return;
|
48 |
+
}
|
49 |
+
// if UDP but port not DNS port, close it
|
50 |
+
if (isUDP) {
|
51 |
+
if (portRemote === 53) {
|
52 |
+
isDns = true;
|
53 |
+
} else {
|
54 |
+
// controller.error('UDP proxy only enable for DNS which is port 53');
|
55 |
+
throw new Error('UDP proxy only enable for DNS which is port 53'); // cf seems has bug, controller.error will not end stream
|
56 |
+
return;
|
57 |
+
}
|
58 |
+
}
|
59 |
+
// ["version", "附加信息长度 N"]
|
60 |
+
const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
|
61 |
+
const rawClientData = chunk.slice(rawDataIndex);
|
62 |
+
|
63 |
+
if (isDns) {
|
64 |
+
return handleDNSQuery(rawClientData, webSocket, vlessResponseHeader, log);
|
65 |
+
}
|
66 |
+
handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);
|
67 |
+
});
|
68 |
+
|
69 |
+
// 监听连接断开事件
|
70 |
+
ws.on('close', () => {
|
71 |
+
console.log('Connection closed');
|
72 |
+
ws.close();
|
73 |
+
});
|
74 |
+
});
|
75 |
+
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Handles outbound TCP connections.
|
79 |
+
*
|
80 |
+
* @param {any} remoteSocket
|
81 |
+
* @param {string} addressRemote The remote address to connect to.
|
82 |
+
* @param {number} portRemote The remote port to connect to.
|
83 |
+
* @param {Uint8Array} rawClientData The raw client data to write.
|
84 |
+
* @param {WebSocket} webSocket The WebSocket to pass the remote socket to.
|
85 |
+
* @param {Uint8Array} vlessResponseHeader The VLESS response header.
|
86 |
+
* @param {function} log The logging function.
|
87 |
+
* @returns {Promise<void>} The remote socket.
|
88 |
+
*/
|
89 |
+
async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
|
90 |
+
async function connectAndWrite(address, port) {
|
91 |
+
const options = {
|
92 |
+
host: address, // 服务器主机地址
|
93 |
+
port: port // 服务器监听的端口号
|
94 |
+
};
|
95 |
+
|
96 |
+
const tcpSocket = net.createConnection(options, () => {
|
97 |
+
console.log('已连接到服务器');
|
98 |
+
});
|
99 |
+
|
100 |
+
remoteSocket.value = tcpSocket;
|
101 |
+
log(`handleTCPOutBound connected to ${address}:${port}`);
|
102 |
+
// const writer = tcpSocket.writable.getWriter();
|
103 |
+
console.log("rawClientData:"+rawClientData)
|
104 |
+
tcpSocket.write(rawClientData); // first write, nomal is tls client hello
|
105 |
+
// writer.releaseLock();
|
106 |
+
return tcpSocket;
|
107 |
+
}
|
108 |
+
|
109 |
+
// if the cf connect tcp socket have no incoming data, we retry to redirect ip
|
110 |
+
async function retry() {
|
111 |
+
console.log("retry")
|
112 |
+
const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote)
|
113 |
+
// no matter retry success or not, close websocket
|
114 |
+
tcpSocket.closed.catch(error => {
|
115 |
+
console.log('retry tcpSocket closed error', error);
|
116 |
+
}).finally(() => {
|
117 |
+
safeCloseWebSocket(webSocket);
|
118 |
+
})
|
119 |
+
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log);
|
120 |
+
}
|
121 |
+
|
122 |
+
const tcpSocket = await connectAndWrite(addressRemote, portRemote);
|
123 |
+
|
124 |
+
// when remoteSocket is ready, pass to websocket
|
125 |
+
// remote--> ws
|
126 |
+
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);
|
127 |
+
}
|
128 |
+
|
129 |
+
/**
|
130 |
+
*
|
131 |
+
* @param {net.Socket} remoteSocket
|
132 |
+
* @param {WebSocket} webSocket
|
133 |
+
* @param {ArrayBuffer} vlessResponseHeader
|
134 |
+
* @param {(() => Promise<void>) | null} retry
|
135 |
+
* @param {*} log
|
136 |
+
*/
|
137 |
+
async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) {
|
138 |
+
// remote--> ws
|
139 |
+
let remoteChunkCount = 0;
|
140 |
+
let chunks = [];
|
141 |
+
/** @type {ArrayBuffer | null} */
|
142 |
+
let vlessHeader = vlessResponseHeader;
|
143 |
+
|
144 |
+
|
145 |
+
// 监听 'data' 事件,将数据推送到可读流中
|
146 |
+
remoteSocket.on('data', (data) => {
|
147 |
+
// console.log("接收到data:"+data)
|
148 |
+
if (vlessHeader) {
|
149 |
+
new Blob([vlessHeader, data]).arrayBuffer()
|
150 |
+
.then(arrayBuffer => {
|
151 |
+
webSocket.send(arrayBuffer);
|
152 |
+
})
|
153 |
+
.catch(error => {
|
154 |
+
console.error('处理 ArrayBuffer 出错:', error);
|
155 |
+
});
|
156 |
+
|
157 |
+
vlessHeader = null;
|
158 |
+
} else {
|
159 |
+
webSocket.send(data);
|
160 |
+
}
|
161 |
+
});
|
162 |
+
|
163 |
+
// 监听 'end' 事件,标记流的结束
|
164 |
+
remoteSocket.on('end', () => {
|
165 |
+
console.log("remoteSocket on end end end")
|
166 |
+
webSocket.send(null);
|
167 |
+
});
|
168 |
+
let is_error = false;
|
169 |
+
|
170 |
+
// 监听 'error' 事件
|
171 |
+
remoteSocket.on('error', () => {
|
172 |
+
is_error = true;
|
173 |
+
});
|
174 |
+
|
175 |
+
if (is_error && retry) {
|
176 |
+
log(`retry`)
|
177 |
+
retry();
|
178 |
+
}
|
179 |
+
}
|
180 |
+
|
181 |
+
|
182 |
+
/**
|
183 |
+
* https://xtls.github.io/development/protocols/vless.html
|
184 |
+
* 1 字节 16 字节 1 字节 M 字节 1 字节 2 字节 1 字节 S 字节 X 字节
|
185 |
+
* 协议版本 等价 UUID 附加信息长度 M 附加信息ProtoBuf 指令 端口 地址类型 地址 请求数据
|
186 |
+
* @param { ArrayBuffer} vlessBuffer
|
187 |
+
* @param {string} userID
|
188 |
+
* @returns
|
189 |
+
*/
|
190 |
+
function processVlessHeader(
|
191 |
+
vlessBuffer,
|
192 |
+
userID
|
193 |
+
) {
|
194 |
+
if (vlessBuffer.byteLength < 24) {
|
195 |
+
return {
|
196 |
+
hasError: true,
|
197 |
+
message: 'invalid data',
|
198 |
+
};
|
199 |
+
}
|
200 |
+
const version = new Uint8Array(vlessBuffer.slice(0, 1));
|
201 |
+
let isValidUser = false;
|
202 |
+
let isUDP = false;
|
203 |
+
if (stringify(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) {
|
204 |
+
console.log(stringify(new Uint8Array(vlessBuffer.slice(1, 17))))
|
205 |
+
console.log(userID)
|
206 |
+
isValidUser = true;
|
207 |
+
}
|
208 |
+
if (!isValidUser) {
|
209 |
+
console.log(stringify(new Uint8Array(vlessBuffer.slice(1, 17))))
|
210 |
+
console.log(userID)
|
211 |
+
return {
|
212 |
+
hasError: true,
|
213 |
+
message: 'invalid user',
|
214 |
+
};
|
215 |
+
}
|
216 |
+
|
217 |
+
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
|
218 |
+
//skip opt for now
|
219 |
+
|
220 |
+
const command = new Uint8Array(
|
221 |
+
vlessBuffer.slice(18 + optLength, 18 + optLength + 1)
|
222 |
+
)[0];
|
223 |
+
|
224 |
+
// 0x01 TCP
|
225 |
+
// 0x02 UDP
|
226 |
+
// 0x03 MUX
|
227 |
+
if (command === 1) {
|
228 |
+
} else if (command === 2) {
|
229 |
+
isUDP = true;
|
230 |
+
} else {
|
231 |
+
return {
|
232 |
+
hasError: true,
|
233 |
+
message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`,
|
234 |
+
};
|
235 |
+
}
|
236 |
+
const portIndex = 18 + optLength + 1;
|
237 |
+
const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);
|
238 |
+
// port is big-Endian in raw data etc 80 == 0x005d
|
239 |
+
const portRemote = portBuffer.readUInt16BE();
|
240 |
+
|
241 |
+
let addressIndex = portIndex + 2;
|
242 |
+
const addressBuffer = new Uint8Array(
|
243 |
+
vlessBuffer.slice(addressIndex, addressIndex + 1)
|
244 |
+
);
|
245 |
+
|
246 |
+
// 1--> ipv4 addressLength =4
|
247 |
+
// 2--> domain name addressLength=addressBuffer[1]
|
248 |
+
// 3--> ipv6 addressLength =16
|
249 |
+
const addressType = addressBuffer[0];
|
250 |
+
let addressLength = 0;
|
251 |
+
let addressValueIndex = addressIndex + 1;
|
252 |
+
let addressValue = '';
|
253 |
+
switch (addressType) {
|
254 |
+
case 1:
|
255 |
+
addressLength = 4;
|
256 |
+
addressValue = new Uint8Array(
|
257 |
+
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
|
258 |
+
).join('.');
|
259 |
+
break;
|
260 |
+
case 2:
|
261 |
+
addressLength = new Uint8Array(
|
262 |
+
vlessBuffer.slice(addressValueIndex, addressValueIndex + 1)
|
263 |
+
)[0];
|
264 |
+
addressValueIndex += 1;
|
265 |
+
addressValue = new TextDecoder().decode(
|
266 |
+
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
|
267 |
+
);
|
268 |
+
break;
|
269 |
+
case 3:
|
270 |
+
addressLength = 16;
|
271 |
+
const dataView = new DataView(
|
272 |
+
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
|
273 |
+
);
|
274 |
+
// 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
275 |
+
const ipv6 = [];
|
276 |
+
for (let i = 0; i < 8; i++) {
|
277 |
+
ipv6.push(dataView.getUint16(i * 2).toString(16));
|
278 |
+
}
|
279 |
+
addressValue = ipv6.join(':');
|
280 |
+
// seems no need add [] for ipv6
|
281 |
+
break;
|
282 |
+
default:
|
283 |
+
return {
|
284 |
+
hasError: true,
|
285 |
+
message: `invild addressType is ${addressType}`,
|
286 |
+
};
|
287 |
+
}
|
288 |
+
if (!addressValue) {
|
289 |
+
return {
|
290 |
+
hasError: true,
|
291 |
+
message: `addressValue is empty, addressType is ${addressType}`,
|
292 |
+
};
|
293 |
+
}
|
294 |
+
|
295 |
+
return {
|
296 |
+
hasError: false,
|
297 |
+
addressRemote: addressValue,
|
298 |
+
addressType,
|
299 |
+
portRemote,
|
300 |
+
rawDataIndex: addressValueIndex + addressLength,
|
301 |
+
vlessVersion: version,
|
302 |
+
isUDP,
|
303 |
+
};
|
304 |
+
}
|
305 |
+
|
306 |
+
const byteToHex = [];
|
307 |
+
for (let i = 0; i < 256; ++i) {
|
308 |
+
byteToHex.push((i + 256).toString(16).slice(1));
|
309 |
+
}
|
310 |
+
|
311 |
+
function unsafeStringify(arr, offset = 0) {
|
312 |
+
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
313 |
+
}
|
314 |
+
function stringify(arr, offset = 0) {
|
315 |
+
const uuid = unsafeStringify(arr, offset);
|
316 |
+
return uuid;
|
317 |
+
}
|
318 |
+
|
319 |
+
/**
|
320 |
+
*
|
321 |
+
* @param {ArrayBuffer} udpChunk
|
322 |
+
* @param {WebSocket} webSocket
|
323 |
+
* @param {Uint8Array} vlessResponseHeader
|
324 |
+
* @param {(string)=> void} log
|
325 |
+
*/
|
326 |
+
async function handleDNSQuery(udpChunk, webSocket, vlessResponseHeader, log) {
|
327 |
+
// no matter which DNS server client send, we alwasy use hard code one.
|
328 |
+
// beacsue someof DNS server is not support DNS over TCP
|
329 |
+
try {
|
330 |
+
const dnsServer = '8.8.4.4'; // change to 1.1.1.1 after cf fix connect own ip bug
|
331 |
+
const dnsPort = 53;
|
332 |
+
/** @type {ArrayBuffer | null} */
|
333 |
+
let vlessHeader = vlessResponseHeader;
|
334 |
+
|
335 |
+
const options = {
|
336 |
+
host: dnsServer, // 服务器主机地址
|
337 |
+
port: dnsPort // 服务器监听的端口号
|
338 |
+
};
|
339 |
+
const tcpSocket = net.createConnection(options, () => {
|
340 |
+
console.log('handleDNSQuery 已连接到服务器');
|
341 |
+
});
|
342 |
+
|
343 |
+
log(`connected to ${dnsServer}:${dnsPort}`);
|
344 |
+
tcpSocket.write(udpChunk)
|
345 |
+
|
346 |
+
// 监听 'data' 事件,将数据推送到可读流中
|
347 |
+
tcpSocket.on('data', (data) => {
|
348 |
+
if (webSocket.readyState === WS_READY_STATE_OPEN) {
|
349 |
+
if (vlessHeader) {
|
350 |
+
new Blob([vlessHeader, data]).arrayBuffer()
|
351 |
+
.then(arrayBuffer => {
|
352 |
+
webSocket.send(arrayBuffer);
|
353 |
+
})
|
354 |
+
.catch(error => {
|
355 |
+
console.error('处理 ArrayBuffer 出错:', error);
|
356 |
+
});
|
357 |
+
vlessHeader = null;
|
358 |
+
} else {
|
359 |
+
webSocket.send(data);
|
360 |
+
}
|
361 |
+
}
|
362 |
+
});
|
363 |
+
} catch (error) {
|
364 |
+
console.error(
|
365 |
+
`handleDNSQuery have exception, error: ${error.message}`
|
366 |
+
);
|
367 |
+
}
|
368 |
+
}
|
369 |
+
/**
|
370 |
+
* Normally, WebSocket will not has exceptions when close.
|
371 |
+
* @param {import("@cloudflare/workers-types").WebSocket} socket
|
372 |
+
*/
|
373 |
+
function safeCloseWebSocket(socket) {
|
374 |
+
try {
|
375 |
+
if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) {
|
376 |
+
socket.close();
|
377 |
+
}
|
378 |
+
} catch (error) {
|
379 |
+
console.error('safeCloseWebSocket error', error);
|
380 |
+
}
|
381 |
+
}
|
package-lock.json
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "nodejs-proxy",
|
3 |
+
"version": "1.0.0",
|
4 |
+
"lockfileVersion": 3,
|
5 |
+
"requires": true,
|
6 |
+
"packages": {
|
7 |
+
"": {
|
8 |
+
"name": "nodejs-proxy",
|
9 |
+
"version": "1.0.0",
|
10 |
+
"dependencies": {
|
11 |
+
"http-proxy": "latest",
|
12 |
+
"ws": "^8.13.0"
|
13 |
+
}
|
14 |
+
},
|
15 |
+
"node_modules/eventemitter3": {
|
16 |
+
"version": "4.0.7",
|
17 |
+
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
18 |
+
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
19 |
+
},
|
20 |
+
"node_modules/follow-redirects": {
|
21 |
+
"version": "1.15.2",
|
22 |
+
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
23 |
+
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
24 |
+
"engines": {
|
25 |
+
"node": ">=4.0"
|
26 |
+
},
|
27 |
+
"peerDependenciesMeta": {
|
28 |
+
"debug": {
|
29 |
+
"optional": true
|
30 |
+
}
|
31 |
+
}
|
32 |
+
},
|
33 |
+
"node_modules/http-proxy": {
|
34 |
+
"version": "1.18.1",
|
35 |
+
"resolved": "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz",
|
36 |
+
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
37 |
+
"dependencies": {
|
38 |
+
"eventemitter3": "^4.0.0",
|
39 |
+
"follow-redirects": "^1.0.0",
|
40 |
+
"requires-port": "^1.0.0"
|
41 |
+
},
|
42 |
+
"engines": {
|
43 |
+
"node": ">=8.0.0"
|
44 |
+
}
|
45 |
+
},
|
46 |
+
"node_modules/requires-port": {
|
47 |
+
"version": "1.0.0",
|
48 |
+
"resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
|
49 |
+
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
50 |
+
},
|
51 |
+
"node_modules/ws": {
|
52 |
+
"version": "8.13.0",
|
53 |
+
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.13.0.tgz",
|
54 |
+
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
55 |
+
"engines": {
|
56 |
+
"node": ">=10.0.0"
|
57 |
+
},
|
58 |
+
"peerDependencies": {
|
59 |
+
"bufferutil": "^4.0.1",
|
60 |
+
"utf-8-validate": ">=5.0.2"
|
61 |
+
},
|
62 |
+
"peerDependenciesMeta": {
|
63 |
+
"bufferutil": {
|
64 |
+
"optional": true
|
65 |
+
},
|
66 |
+
"utf-8-validate": {
|
67 |
+
"optional": true
|
68 |
+
}
|
69 |
+
}
|
70 |
+
}
|
71 |
+
}
|
72 |
+
}
|
package.json
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "nodejs-proxy",
|
3 |
+
"version": "1.0.0",
|
4 |
+
"description": "",
|
5 |
+
"main": "app.js",
|
6 |
+
"scripts": {
|
7 |
+
"start": "node app.js",
|
8 |
+
"test": "echo \"Error: no test specified\" && exit 1"
|
9 |
+
},
|
10 |
+
"dependencies": {
|
11 |
+
"http-proxy": "latest",
|
12 |
+
"ws": "^8.13.0"
|
13 |
+
}
|
14 |
+
}
|