js2ray
Version:
The v2ray vmess protocol, based on nodejs javascript which you can use on hosts and servers
156 lines (130 loc) • 4.32 kB
JavaScript
const net = require('net')
const dgram = require('dgram')
const ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/;
// const ipv6Regex = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i; // so slow
function iptoBuffer(ip, buff, offset) {
if (ipv4Regex.test(ip)) {
return ip4toBuffer(ip, buff, offset)
} else if (net.isIPv6(ip)) {
return ip6toBuffer(ip, buff, offset)
}
};
function ip6toBuffer(ip = "", buff, offset) {
offset = ~~offset;
let result;
const sections = ip.split(':', 8);
let i;
for (i = 0; i < sections.length; i++) {
const isv4 = ipv4Regex.test(sections[i]);
let v4Buffer;
if (isv4) {
v4Buffer = this.toBuffer(sections[i]);
sections[i] = v4Buffer.subarray(0, 2).toString('hex');
}
if (v4Buffer && ++i < 8) {
sections.splice(i, 0, v4Buffer.subarray(2, 4).toString('hex'));
}
}
if (sections[0] === '') {
while (sections.length < 8) sections.unshift('0');
} else if (sections[sections.length - 1] === '') {
while (sections.length < 8) sections.push('0');
} else if (sections.length < 8) {
for (i = 0; i < sections.length && sections[i] !== ''; i++);
const argv = [i, 1];
for (i = 9 - sections.length; i > 0; i--) {
argv.push('0');
}
sections.splice(...argv);
}
result = buff || Buffer.alloc(offset + 16);
for (i = 0; i < sections.length; i++) {
const word = parseInt(sections[i], 16);
result[offset++] = (word >> 8) & 0xff;
result[offset++] = word & 0xff;
}
if (!result) {
throw Error(`Invalid ip address: ${ip}`);
}
return result;
};
function ip4toBuffer(ip = "", buff, offset) {
offset = ~~offset;
let result;
result = buff || Buffer.alloc(offset + 4);
ip.split(/\./g).map((byte) => {
result[offset++] = parseInt(byte, 10) & 0xff;
});
if (!result) {
throw Error(`Invalid ip address: ${ip}`);
}
return result;
};
function iptoString(buff, offset, length) {
offset = ~~offset;
length = length || (buff.length - offset);
var result = [];
var i;
if (length === 4) {
// IPv4
for (i = 0; i < length; i++) {
result.push(buff[offset + i]);
}
result = result.join('.');
} else if (length === 16) {
// IPv6
for (i = 0; i < length; i += 2) {
result.push(buff.readUInt16BE(offset + i).toString(16));
}
result = result.join(':');
result = result.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3');
result = result.replace(/:{3,4}/, '::');
}
return result;
};
function int2ip(ipInt) {
return ((ipInt >>> 24) + '.' + (ipInt >> 16 & 255) + '.' + (ipInt >> 8 & 255) + '.' + (ipInt & 255));
}
function ip2int(ip) {
return ip.split('.').reduce(function (ipInt, octet) { return (ipInt << 8) + parseInt(octet, 10) }, 0) >>> 0;
}
function UDPBind(onconnect, onmessage, onclose, v6) {
let socket = dgram.createSocket(v6 ? 'udp6' : 'udp4');
if (typeof onmessage === "object") {
const stream = onmessage;
onmessage = stream.write.bind(stream);
onclose = stream.destroy?.bind(stream);
stream.on("error", close);
stream.on("close", close);
stream.on("data", (buffer) => message(buffer));
}
function close() {
if (!socket) return;
socket.close();
socket = null;
}
function message(buffer, port, host) {
if (!socket || !port || port < 1 || port > 65535) return;
socket.send(buffer, port, host);
}
const timeout = setTimeout(close, 10000);
socket.on('error', close);
socket.on('message', onmessage);
socket.on('close', () => {
clearTimeout(timeout);
onclose?.();
socket = null;
});
socket.once('message', () => clearTimeout(timeout));
socket.bind(onconnect);
return { close, message };
}
module.exports = {
ip4toBuffer,
ip6toBuffer,
iptoString,
iptoBuffer,
int2ip,
ip2int,
UDPBind,
}