js2ray
Version:
The v2ray vmess protocol, based on nodejs javascript which you can use on hosts and servers
235 lines (213 loc) • 7.79 kB
JavaScript
;
const jsSha = require("./crypto/sha3");
const crypto = require("crypto");
const consts = require("./consts")
const event = require("./../../core/event")
function uint64ToBuffer(uint64) {
const buf = Buffer.alloc(8);
buf.writeBigUInt64BE(BigInt(uint64), 0);
return buf;
}
function ntb(num, len = 2, byteOrder = 0) {
const buf = Buffer.alloc(len);
if (len > 0 && num >= 0 && num <= Math.pow(256, len) - 1) {
if (byteOrder === 0) {
buf.writeUIntBE(num, 0, len);
} else {
buf.writeUIntLE(num, 0, len);
}
}
return buf;
}
function hash(algorithm, buffer) {
const hs = crypto.createHash(algorithm);
hs.update(buffer);
return hs.digest();
}
function createChacha20Poly1305Key(key) {
const md5Key = hash('md5', key);
return Buffer.concat([md5Key, hash('md5', md5Key)]);
}
function shake128(buffer) {
let buffered = Buffer.alloc(0);
let iter = 0;
return {
nextBytes: function nextBytes(n) {
const end = iter + n
if (end > buffered.length) {
const hash = jsSha.shake128.create(buffered.length * 8 + 512);
hash.update(buffer);
buffered = Buffer.from(hash.arrayBuffer());
}
const bytes = buffered.subarray(iter, end);
iter = end;
return bytes;
}
};
}
function fnv1a(buffer) {
let hash = 0x811c9dc5;
for (let i = 0; i < buffer.length; ++i) {
hash ^= buffer[i];
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
const buf = Buffer.alloc(4);
buf.writeUIntBE(hash >>> 0, 0, 4);
return buf;
}
function encrypt(plaintext, app) {
if (app._dataEncIV) {
const security = app._security;
let tag = null;
const nonce = Buffer.concat([ntb(app._cipherNonce), app._dataEncIV.subarray(2, 12)]);
let ciphertext = null;
if (security === consts.SECURITY_TYPE_AES_128_GCM) {
const cipher = crypto.createCipheriv('aes-128-gcm', app._dataEncKey, nonce);
ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
tag = cipher.getAuthTag();
app._cipherNonce += 1;
if (app._cipherNonce > 65535)
app._cipherNonce = 0
}
else if (security === consts.SECURITY_TYPE_CHACHA20_POLY1305) {
const cipher = crypto.createCipheriv('chacha20-poly1305', app._dataEncKeyForChaCha20, nonce, {
authTagLength: 16
})
ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
tag = cipher.getAuthTag();
app._cipherNonce += 1;
if (app._cipherNonce > 65535)
app._cipherNonce = 0
}
return Buffer.concat([ciphertext, tag]);
}
return Buffer.alloc(0)
}
function decrypt(ciphertext, app) {
if (app._dataDecIV) {
const tag = ciphertext.subarray(-16);
ciphertext = ciphertext.subarray(0, -16)
const security = app._security;
const nonce = Buffer.concat([ntb(app._decipherNonce), app._dataDecIV.subarray(2, 12)]);
if (security === consts.SECURITY_TYPE_AES_128_GCM) {
try {
const decipher = crypto.createDecipheriv('aes-128-gcm', app._dataDecKey, nonce);
decipher.setAuthTag(tag);
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
app._decipherNonce += 1;
if (app._decipherNonce > 65535)
app._decipherNonce = 0
return plaintext;
} catch (err) {
return null;
}
}
else if (security === consts.SECURITY_TYPE_CHACHA20_POLY1305) {
try {
const decipher = crypto.createDecipheriv('chacha20-poly1305', app._dataDecKeyForChaCha20, nonce, {
authTagLength: 16
});
decipher.setAuthTag(tag)
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
app._decipherNonce += 1;
if (app._decipherNonce > 65535)
app._decipherNonce = 0
return plaintext;
} catch (err) {
return null;
}
}
}
}
function getChunks(buffer, maxSize) {
const totalLen = buffer.length;
const bufs = [];
let ptr = 0;
while (ptr < totalLen - 1) {
bufs.push(buffer.subarray(ptr, ptr + maxSize));
ptr += maxSize;
}
if (ptr < totalLen) {
bufs.push(buffer.subarray(ptr));
}
return bufs;
}
// ================================================= utils
function trafficlimit(user) {
if (user.bytesRead + user.bytesWrit >= user.traffic) {
if (!user.maxtraffic) {
event.emit("traffic", user.id.UUID)
user.maxtraffic = true
}
return true;
}
}
function iplimit(ip, user) {
var now = Math.round(new Date() / 1000)
if (user.ipCount && !(ip in user.ipList)) {
if (Object.keys(user.ipList).length >= user.ipCount) {
// ============ if before 10 min
if (user.ipBlock[ip] && user.ipBlock[ip] + user.ipCountDuration > now) {
// ======================================= soft allow for 1 or 2 ip
if (ip in user.ipWarning) {
if (user.ipWarning[ip] + user.ipCountDuration / 20 < now) {
delete user.ipBlock[ip]
user.maxip = false
return false
}
} else if (Object.keys(user.ipWarning).length < Math.max(1, Math.round(user.ipCount / 2.1))) {
user.ipWarning[ip] = now
}
if (!user.maxip) {
event.emit("ip", user.id.UUID)
user.maxip = true
}
return true
// ===================================================
} else {
delete user.ipBlock[ip]
}
//================================================ clear ips
for (const i in user.ipList) {
if (user.ipList[i] + user.ipCountDuration < now) {
delete user.ipList[i]
}
}
for (const i in user.ipWarning) {
if (user.ipWarning[i] + user.ipCountDuration < now) {
delete user.ipWarning[i]
}
}
//===================================================
const keys = Object.keys(user.ipList)
if (keys.length >= user.ipCount) {
var wip = keys.reduce((key, v) => user.ipList[v] < user.ipList[key] ? v : key);
user.ipBlock[wip] = user.ipList[wip]
delete user.ipList[wip];
}
for (const i in user.ipBlock) {
if (user.ipBlock[i] + user.ipCountDuration < now) {
delete user.ipBlock[i]
}
}
}
}
user.ipList[ip] = now
if (user.maxip)
user.maxip = false
return false
}
module.exports = {
ntb,
hash,
createChacha20Poly1305Key,
shake128,
uint64ToBuffer,
fnv1a,
encrypt,
decrypt,
getChunks,
trafficlimit,
update_ip: iplimit,
iplimit
}