UNPKG

js2ray

Version:

The v2ray vmess protocol, based on nodejs javascript which you can use on hosts and servers

420 lines (391 loc) 18.7 kB
const { WebSocketServer } = require('ws'); const net = require('net'); const http = require('http'); const https = require('http'); function onerror(e) { } function localNetwork(data, localProtocol) { try { const socketset = new Set(); if (!data.option) { data.option = {} } if (!data.option.path) data.option.path = '/' if (!data.type) data.type = "tcp" if (typeof localProtocol == "object") { localProtocol.on("connection", function (localsocket) { socketset.add(localsocket); localsocket.setTimeout(10000); localsocket.on("error", onerror) localsocket.on("close", function () { socketset.delete(localsocket); }) }) return { stop: function () { localProtocol.close() for (const socket of socketset.values()) { socket.destroy(); } }, start: function () { log("server is running on " + data.address + " port " + data.port, 1) localProtocol.listen(data.port, data.address); } } } else if (data.type == "tcp") { var server = net.createServer(function (localsocket) { socketset.add(localsocket); localsocket.on("close", function () { socketset.delete(localsocket); }) localsocket.localMessage = localsocket.write.bind(localsocket) localsocket.localClose = localsocket.destroy.bind(localsocket) const remoteProtocol = localProtocol(localsocket, localsocket.remoteAddress) localsocket.setTimeout(10000); localsocket.on('data', remoteProtocol.message); localsocket.on("close", remoteProtocol.close) localsocket.on("error", remoteProtocol.close) }); return { stop: function () { server.close() for (const socket of socketset.values()) { socket.destroy(); } }, start: function () { log("tcp server is running on " + data.address + " port " + data.port, 1) server.listen(data.port, data.address); } } } else if (data.type == "ws") { const wss = new WebSocketServer({ noServer: true }); if (data.option.fake) { if (data.option.tls) { var server = https.createServer(data.option.tls, function (req, res) { return res.end(data.option.fake) }) } else { var server = http.createServer(function (req, res) { return res.end(data.option.fake) }) } } else { if (data.option.tls) var server = https.createServer(data.option.tls) else var server = http.createServer() } function connected(ws, req) { ws.localMessage = function (buffer) { ws.send(buffer) } ws.localClose = function () { ws.close() } var ip if (req.headers['x-forwarded-for']) { ip = req.headers['x-forwarded-for'].split(',')[0].trim(); } else { ip = ws._socket.remoteAddress } const remoteProtocol = localProtocol(ws, ip) ws.on("close", function () { remoteProtocol.close() }) ws.on("error", function () { remoteProtocol.close() }) ws.on('message', function (buffer) { remoteProtocol.message(buffer) }); } server.on('upgrade', function (request, socket, head) { if (typeof data.option.path == "string" ? request.url == data.option.path : data.option.path.includes(request.url)) { wss.handleUpgrade(request, socket, head, connected); } else socket.destroy(); }); return { stop: function () { server.close() server.closeAllConnections() for (const socket of wss.clients) { socket.terminate(); } }, start: function () { log("ws server is running on " + data.address + " port " + data.port, 1) server.listen(data.port, data.address); } } } else if (data.type == "http") { const headers = create_header("HTTP/1.1 200 OK", data.option.headers, { "Connection": "keep-alive", "Content-Type": "text/html", "Pragma": "no-cache", "Transfer-Encoding": "chunked", "Date": new Date().toLocaleString('en-GB', { timeZone: 'UTC', hour12: false, weekday: 'short', year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', }).replace(/(?:(\d),)/, '$1') + ' GMT' }) var server = net.createServer(function (localsocket) { socketset.add(localsocket); localsocket.on("close", function () { socketset.delete(localsocket); }) localsocket.localMessage = localsocket.write.bind(localsocket) localsocket.localClose = localsocket.destroy.bind(localsocket) const remoteProtocol = localProtocol(localsocket, localsocket.remoteAddress) localsocket.setTimeout(10000); localsocket.on("error", remoteProtocol.close) localsocket.on("close", remoteProtocol.close) localsocket.on('data', function (buffer) { var indhttp = buffer.indexOf('\r\n\r\n') if (indhttp != -1) { if (buffer.subarray(0, 3) == "GET" || buffer.subarray(0, 4) == "POST") { var path = buffer.subarray(buffer.indexOf(' ') + 1, buffer.indexOf(' HTTP')).toString() if (typeof data.option.path == "string" ? path == data.option.path : data.option.path.includes(path)) { localsocket.rh = true this.write(headers) if (buffer.length != indhttp + 4) { return remoteProtocol.message.call(this, buffer.subarray(indhttp + 4)) } else { return } } else if (data.option.fake) { this.write(headers) this.write(data.option.fake) return this.end() } } } else if (localsocket.rh == true) { remoteProtocol.message.call(this, buffer) } }); }); return { stop: function () { server.close() for (const socket of socketset.values()) { socket.destroy(); } }, start: function () { log("http server is running on " + data.address + " port " + data.port, 1) server.listen(data.port, data.address); } } } else if (data.type == "xhttp") { const sessions = new Map(); // UUID → { queue: {}, conn, expectedSeq } const server = http.createServer((req, res) => { const localsocket = res.socket const urlParts = req.url.split("/"); const pathOK = typeof data.option.path == "string" ? req.url.startsWith(data.option.path) : data.option.path.includes(req.url); if (!pathOK) return res.end("Not found"); // Headers check (x_padding in referer) const referer = req.headers["referer"] || ""; const xPadding = (referer.match(/x_padding=([Xx]*)/) || [])[1] || ""; if (xPadding.length < 100 || xPadding.length > 1000) { res.writeHead(400); return res.end("Bad padding"); } const UUID = urlParts[2]; const SEQ = urlParts[3] ? parseInt(urlParts[3]) : null; const key = UUID; if (req.method === "POST" && UUID && typeof SEQ === "number") { // Uplink (packet-up) // console.log(1) let session = sessions.get(key); if (!session) { session = { queue: {}, expectedSeq: 0, conn: null }; sessions.set(key, session); } let body = Buffer.alloc(0); req.on('data', chunk => body = Buffer.concat([body, chunk])); req.on('end', () => { session.queue[SEQ] = body; while (session.queue[session.expectedSeq]) { const pkt = session.queue[session.expectedSeq]; console.log(session.queue[session.expectedSeq]+"") if (session.conn) session.conn.message(pkt); delete session.queue[session.expectedSeq]; session.expectedSeq++; } res.writeHead(200); res.end(); }); } else if (req.method === "GET" && UUID) { // Downlink (streaming) // console.log(2) res.writeHead(200, { "X-Accel-Buffering": "no", "Cache-Control": "no-store", "Content-Type": "text/event-stream", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST", "Transfer-Encoding": "chunked", "X-Padding": "X".repeat(~~(Math.random() * 900 + 100)), "Date": new Date().toUTCString() }); res.flushHeaders(); res.localMessage = res.write.bind(res); res.localClose = res.destroy.bind(res); const remoteProtocol = localProtocol(res, localsocket.remoteAddress); localsocket.setTimeout(10000); localsocket.on("close", remoteProtocol.close); localsocket.on("error", remoteProtocol.close); let session = sessions.get(key); if (!session) { session = { queue: {}, expectedSeq: 0, conn: remoteProtocol }; sessions.set(key, session); } else { session.conn = remoteProtocol; res.end() } while (session.queue[session.expectedSeq]) { const pkt = session.queue[session.expectedSeq]; remoteProtocol.message(pkt); delete session.queue[session.expectedSeq]; session.expectedSeq++; } } else if (req.method === "POST") { console.log(3) // Fallback to stream-one res.writeHead(200, { "X-Accel-Buffering": "no", "Cache-Control": "no-store", "Content-Type": "text/event-stream", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST", "Transfer-Encoding": "chunked", "X-Padding": "X".repeat(~~(Math.random() * 900 + 100)), "Date": new Date().toUTCString() }); res.flushHeaders(); localsocket.localMessage = res.write.bind(res); localsocket.localClose = res.destroy.bind(res); const remoteProtocol = localProtocol(localsocket, localsocket.remoteAddress); localsocket.setTimeout(10000); localsocket.on("close", remoteProtocol.close); localsocket.on("error", remoteProtocol.close); req.on("data", remoteProtocol.message); } else { res.writeHead(405); res.end("Method not allowed"); } }); return { start: () => { log("xhttp (HTTP chunked) server running on " + data.address + ":" + data.port, 1); server.listen(data.port, data.address); }, stop: () => { server.close(); for (const socket of socketset.values()) socket.destroy(); sessions.clear(); } }; } else if (data.type == "httpAlt") { const headers = create_header("HTTP/1.1 200 OK", data.option.headers, { "Connection": "keep-alive", "Content-Type": "text/html", "Pragma": "no-cache", "Transfer-Encoding": "chunked", "Date": new Date().toLocaleString('en-GB', { timeZone: 'UTC', hour12: false, weekday: 'short', year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', }).replace(/(?:(\d),)/, '$1') + ' GMT' }) var server = http.createServer() server.on('connection', function (localsocket) { socketset.add(localsocket); localsocket.on("close", function () { socketset.delete(localsocket); }) localsocket.localMessage = localsocket.write.bind(localsocket) localsocket.localClose = localsocket.destroy.bind(localsocket) const remoteProtocol = localProtocol(localsocket, localsocket.remoteAddress) localsocket.setTimeout(10000); localsocket.on("error", remoteProtocol.close) localsocket.on("close", remoteProtocol.close) localsocket.on('data', function (buffer) { var indhttp = buffer.indexOf('\r\n\r\n') if (indhttp != -1 && (buffer.subarray(0, 3) == "GET" || buffer.subarray(0, 4) == "POST")) { var path = buffer.subarray(buffer.indexOf(' ') + 1, buffer.indexOf(' HTTP')).toString() if (typeof data.option.path == "string" ? path == data.option.path : data.option.path.includes(path)) { this.write(headers) if (buffer.length != indhttp + 4) { return remoteProtocol.message.call(this, buffer.subarray(indhttp + 4)) } else { return } } else if (data.option.fake) { this.write(headers) this.write(data.option.fake) return this.end() } } else { remoteProtocol.message.call(this, buffer) } }); }) return { stop: function () { server.close() server.closeAllConnections() for (const socket of socketset.values()) { socket.destroy(); } }, start: function () { log("httpAlt server is running on " + data.address + " port " + data.port, 1) server.listen(data.port, data.address); } } } else { throw ("network type '" + data.type + "' not supported") } } catch (error) { log(error) } } function randomPadding(lenRange) { if (typeof lenRange === "string" && lenRange.includes("-")) { const [min, max] = lenRange.split("-").map(Number); const len = Math.floor(Math.random() * (max - min + 1)) + min; return "X".repeat(len); } return ""; } function create_header(pre, obj = {}, defaults) { var out = pre + "\r\n" if (defaults) obj = { ...defaults, ...obj } for (const i in obj) { if (obj[i]) out += pascalCase(i) + ": " + obj[i] + "\r\n" } return out + "\r\n" } function pascalCase(str) { return str.replace(/\w+/g, function (w) { return w[0].toUpperCase() + w.slice(1).toLowerCase(); }) } module.exports = function (data, localProtocol) { const out = [] for (const i of data) { out.push(localNetwork(i, localProtocol)) } return out; }