forked from eooce/node-ws
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
174 lines (161 loc) · 5.73 KB
/
index.js
File metadata and controls
174 lines (161 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
const http = require('http');
const fs = require('fs');
const axios = require('axios');
const net = require('net');
const path = require('path');
const { Buffer } = require('buffer');
const { WebSocket, createWebSocketStream } = require('ws');
const UUID = process.env.UUID || '2681965f-0eab-450a-af4e-a94bdd1c0446';
const DOMAIN = process.env.DOMAIN || '1234.abc.com'; // 填写项目域名或已反代的域名,不带前缀,建议填已反代的域名
const AUTO_ACCESS = process.env.AUTO_ACCESS || false; // 是否开启自动访问保活,false为关闭,true为开启,需同时填写DOMAIN变量
const WSPATH = process.env.WSPATH || UUID.slice(0, 8); // 节点路径,默认获取uuid前8位
const SUB_PATH = process.env.SUB_PATH || 'sub'; // 获取节点的订阅路径
const NAME = process.env.NAME || ''; // 节点名称
const PORT = process.env.PORT || 3000; // http和ws服务端口
let ISP = '';
const GetISP = async () => {
try {
const res = await axios.get('https://speed.cloudflare.com/meta');
const data = res.data;
ISP = `${data.country}-${data.asOrganization}`.replace(/ /g, '_');
} catch (e) {
ISP = 'Unknown';
}
}
GetISP();
const httpServer = http.createServer((req, res) => {
if (req.url === '/') {
const filePath = path.join(__dirname, 'index.html');
fs.readFile(filePath, 'utf8', (err, content) => {
if (err) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('Hello world!');
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(content);
});
return;
} else if (req.url === `/${SUB_PATH}`) {
const namePart = NAME ? `${NAME}-${ISP}` : ISP;
const vlessURL = `vless://${UUID}@cdns.doon.eu.org:443?encryption=none&security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`;
const subscription = vlessURL;
const base64Content = Buffer.from(subscription).toString('base64');
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(base64Content + '\n');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found\n');
}
});
const wss = new WebSocket.Server({ server: httpServer });
const uuid = UUID.replace(/-/g, "");
const DNS_SERVERS = ['8.8.4.4', '1.1.1.1'];
// Custom DNS
function resolveHost(host) {
return new Promise((resolve, reject) => {
if (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(host)) {
resolve(host);
return;
}
let attempts = 0;
function tryNextDNS() {
if (attempts >= DNS_SERVERS.length) {
reject(new Error(`Failed to resolve ${host} with all DNS servers`));
return;
}
const dnsServer = DNS_SERVERS[attempts];
attempts++;
const dnsQuery = `https://dns.google/resolve?name=${encodeURIComponent(host)}&type=A`;
axios.get(dnsQuery, {
timeout: 5000,
headers: {
'Accept': 'application/dns-json'
}
})
.then(response => {
const data = response.data;
if (data.Status === 0 && data.Answer && data.Answer.length > 0) {
const ip = data.Answer.find(record => record.type === 1);
if (ip) {
resolve(ip.data);
return;
}
}
tryNextDNS();
})
.catch(error => {
tryNextDNS();
});
}
tryNextDNS();
});
}
// VLE-SS处理
function handleVlessConnection(ws, msg) {
const [VERSION] = msg;
const id = msg.slice(1, 17);
if (!id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16))) return false;
let i = msg.slice(17, 18).readUInt8() + 19;
const port = msg.slice(i, i += 2).readUInt16BE(0);
const ATYP = msg.slice(i, i += 1).readUInt8();
const host = ATYP == 1 ? msg.slice(i, i += 4).join('.') :
(ATYP == 2 ? new TextDecoder().decode(msg.slice(i + 1, i += 1 + msg.slice(i, i + 1).readUInt8())) :
(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(':') : ''));
ws.send(new Uint8Array([VERSION, 0]));
const duplex = createWebSocketStream(ws);
resolveHost(host)
.then(resolvedIP => {
net.connect({ host: resolvedIP, port }, function () {
this.write(msg.slice(i));
duplex.on('error', () => { }).pipe(this).on('error', () => { }).pipe(duplex);
}).on('error', () => { });
})
.catch(error => {
net.connect({ host, port }, function () {
this.write(msg.slice(i));
duplex.on('error', () => { }).pipe(this).on('error', () => { }).pipe(duplex);
}).on('error', () => { });
});
return true;
}
// Ws 连接处理
wss.on('connection', (ws, req) => {
const url = req.url || '';
ws.once('message', msg => {
if (msg.length > 17 && msg[0] === 0) {
const id = msg.slice(1, 17);
const isVless = id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16));
if (isVless) {
if (!handleVlessConnection(ws, msg)) {
ws.close();
}
return;
}
}
ws.close();
}).on('error', () => { });
});
async function addAccessTask() {
if (!AUTO_ACCESS) return;
if (!DOMAIN) {
return;
}
const fullURL = `https://${DOMAIN}/${SUB_PATH}`;
try {
const res = await axios.post("https://oooo.serv00.net/add-url", {
url: fullURL
}, {
headers: {
'Content-Type': 'application/json'
}
});
console.log('Automatic Access Task added successfully');
} catch (error) {
// console.error('Error adding Task:', error.message);
}
}
httpServer.listen(PORT, () => {
addAccessTask();
console.log(`Server is running on port ${PORT}`);
});