Comparing version 1.2.2 to 2.0.0
40
index.js
var protocols = { | ||
vmess: require('./protocols/vmess') | ||
} | ||
const protocols = {} | ||
protocols.vmess = require('./protocols/vmess') | ||
const freedom = require('./protocols/freedom'); | ||
const bridge = require('./protocols/bridge'); | ||
const localNetwork = require('./localNetwork'); | ||
const remoteNetwork = require('./remoteNetwork'); | ||
const event = require('./event'); | ||
const localNetwork = require('./core/localNetwork'); | ||
const remoteNetwork = require('./core/remoteNetwork'); | ||
const event = require('./core/event'); | ||
const storage = require('./core/storage'); | ||
const api = require('./core/api'); | ||
storage.read() | ||
function config(data) { | ||
@@ -18,2 +19,4 @@ global.log = function (...mess) { | ||
} | ||
if (data.api) | ||
api.init(data.api, protocols) | ||
//=========================== | ||
@@ -25,3 +28,3 @@ if (data.bridge == true) { | ||
throw ("do not use outbound protocol in bridge mode") | ||
var remoteProtocol = bridge(data.outbound.network, remoteNetwork(data.outbound.network.type)) | ||
var remoteProtocol = bridge(data.outbound.networks, remoteNetwork(data.outbound.networks)) | ||
for (const i of data.inbounds) { | ||
@@ -36,31 +39,20 @@ if (i.protocol) | ||
throw ("inbound protocol is not defined") | ||
localNetwork(i.networks, protocols[i.protocol].server(i, freedom)) | ||
localNetwork(i.networks, protocols[i.protocol].server(i, freedom, storage)) | ||
} | ||
} else { | ||
var remoteProtocol = protocols[data.outbound.protocol].client(data.outbound, remoteNetwork(data.outbound.network.type)) | ||
var remoteProtocol = protocols[data.outbound.protocol].client(data.outbound, remoteNetwork(data.outbound.networks)) | ||
for (const i of data.inbounds) { | ||
if (!i.protocol || !(i.protocol in protocols)) | ||
throw ("inbound protocol is not defined") | ||
localNetwork(i.networks, protocols[i.protocol].server(i, remoteProtocol)) | ||
localNetwork(i.networks, protocols[i.protocol].server(i, remoteProtocol, storage)) | ||
} | ||
} | ||
//=========================== | ||
return event; | ||
} | ||
function user() { | ||
} | ||
module.exports = { | ||
config, | ||
user, | ||
on: event.on, | ||
protocols | ||
} | ||
// const server = outboundInit(data.outbound, vmess) | ||
// for (const i of data.inbounds) { | ||
// if (i.protocol == "vmess") | ||
// inbound(i, server) | ||
// else | ||
// throw ("inbound protocol '" + i.protocol + "' not supported") |
{ | ||
"name": "js2ray", | ||
"version": "1.2.2", | ||
"version": "2.0.0", | ||
"description": "The v2ray vmess protocol, based on nodejs javascript which you can use on hosts and servers", | ||
@@ -29,7 +29,5 @@ "main": "index.js", | ||
"dependencies": { | ||
"cbor-js": "^0.1.0", | ||
"cuckoo-filter": "^1.1.4", | ||
"libsodium-wrappers": "^0.7.11", | ||
"ws": "^8.12.0" | ||
} | ||
} |
function init(data, remoteNetwork) { | ||
function init(networks, remoteNetwork) { | ||
const data = networks[Math.floor(Math.random() * networks.length)] | ||
return function (localsocket) { | ||
@@ -5,0 +6,0 @@ localsocket.pause(); |
const crypto = require('crypto'); | ||
const fs = require('fs'); | ||
const { setInterval } = require('timers'); | ||
const consts = require("./consts") | ||
const storage = require("../../core/storage") | ||
function New() { | ||
@@ -73,2 +73,7 @@ return crypto.randomBytes(16) | ||
// 2 'auto' | ||
// 3 'aes-128-gcm' | ||
// 4 'chacha20-poly1305' | ||
// 5 'none' | ||
// 6 'zero' | ||
@@ -84,38 +89,46 @@ var securityTypes = { | ||
var users = usesave() | ||
var users = {} | ||
function user(tag, inp, del) { | ||
if (typeof inp == 'object') { | ||
addUser(tag, a) | ||
save() | ||
} else if (typeof inp == "string") { | ||
var sub_user = [] | ||
for (var i in usersp[tag]) { | ||
if (usersp[tag][i].email == inp) { | ||
sub_user.push(usersp[tag][i]) | ||
if (del) | ||
usersp[tag].splice(i, 1) | ||
} | ||
function getUser(tag, id) { | ||
if (typeof id == "string") { | ||
return users[tag][id] | ||
} else if (tag == undefined) { | ||
return users; | ||
} else if (id == undefined) { | ||
return users[tag]; | ||
} | ||
} | ||
function getUserByEmail(tag, email) { | ||
var sub_user = [] | ||
for (var i in users[tag]) { | ||
if (users[tag][i].email == email) { | ||
sub_user.push(users[tag][i]) | ||
if (del) | ||
delete users[tag][i] | ||
// users[tag].splice(i, 1) | ||
} | ||
return sub_user; | ||
} else if (inp == undefined) { | ||
return usersp[tag]; | ||
} | ||
return sub_user; | ||
} | ||
function deleteUser(tag, id) { | ||
users[tag][id].deactive = true | ||
return delete users[tag][id] | ||
} | ||
function AsAccount(tag, data_user) { | ||
if (tag == undefined) | ||
tag = (Math.random() + 1).toString(36).substring(3) | ||
function addUsers(tag, data_user) { | ||
if (users[tag] == undefined) | ||
users[tag] = [] | ||
users[tag] = {} | ||
var users_ids = [] | ||
for (var i in users[tag]) { | ||
users_ids.push(users[tag][i].id.UUID.toString()) | ||
// var users_ids = [] | ||
// for (var i in users[tag]) { | ||
// users_ids.push(users[tag][i].id.UUID.toString()) | ||
// } | ||
// for (var i of data_user) { | ||
// if (!users_ids.includes(ParseString(i.id).toString())) { | ||
// addUser(tag, i) | ||
// } | ||
// } | ||
for (var item of data_user) { | ||
addUser(tag, item) | ||
} | ||
for (var i of data_user) { | ||
if (!users_ids.includes(ParseString(i.id).toString())) { | ||
addUser(tag, i) | ||
} | ||
} | ||
save() | ||
@@ -126,3 +139,27 @@ return users[tag]; | ||
function addUser(tag, a) { | ||
var protoID = NewID(ParseString(a.id)) | ||
const id = ParseString(a.id) | ||
const idstr = id.toString() | ||
if (idstr in users[tag]) { | ||
const item = users[tag][idstr] | ||
var exist = { | ||
bytesRead: item.bytesRead, | ||
bytesWrit: item.bytesWrit, | ||
traffic: a.traffic || item.traffic, | ||
ipCount: a.ipCount || item.ipCount, | ||
ipCountDuration: a.ipCountDuration || item.ipCountDuration, | ||
ipList: item.ipList | ||
} | ||
} else { | ||
var exist = { | ||
bytesRead: 0, | ||
bytesWrit: 0, | ||
traffic: a.traffic || 0, | ||
ipCount: a.ipCount || 0, | ||
ipCountDuration: a.ipCountDuration || 180, | ||
ipList: {} | ||
} | ||
} | ||
var protoID = NewID(id) | ||
if (!a.alterId) | ||
@@ -136,10 +173,5 @@ a.alterId = 0 | ||
security: securityTypes[a.security || 'auto'], | ||
bytesRead: 0, | ||
bytesWrit: 0, | ||
traffic: a.traffic || 0, | ||
ipCount: a.ipCount || 0, | ||
ipCountDuration: a.ipCountDuration || 180, | ||
ipList: {}, | ||
} | ||
users[tag].push(user) | ||
user = { ...exist, ...user } | ||
users[tag][a.id] = user | ||
return user | ||
@@ -149,53 +181,30 @@ } | ||
setInterval(save, 60000); | ||
function usesave() { | ||
return {} | ||
try { | ||
var usage = fs.readFileSync(__dirname + "/data.json"); | ||
if (usage == "") { | ||
usage = {} | ||
} else { | ||
usage = JSON.parse(usage) | ||
} | ||
for (var i in usage) { | ||
usage[i].id.UUID = Buffer.from(usage[i].id.UUID) | ||
usage[i].id.cmdKey = Buffer.from(usage[i].id.cmdKey) | ||
for (var j in usage[i].alterIDs) { | ||
usage[i].alterIDs[j].UUID = Buffer.from(usage[i].alterIDs[j].UUID) | ||
usage[i].alterIDs[j].cmdKey = Buffer.from(usage[i].alterIDs[j].cmdKey) | ||
function getSavedUsers() { | ||
if (!("vmess" in storage.data)) { | ||
storage.data.vmess = {} | ||
} | ||
const users = storage.data.vmess | ||
for (var j in users) { | ||
for (var i in users[j]) { | ||
users[j][i].id.UUID = Buffer.from(users[j][i].id.UUID) | ||
users[j][i].id.cmdKey = Buffer.from(users[j][i].id.cmdKey) | ||
for (var j in users[j][i].alterIDs) { | ||
users[j][i].alterIDs[j].UUID = Buffer.from(users[j][i].alterIDs[j].UUID) | ||
users[j][i].alterIDs[j].cmdKey = Buffer.from(users[j][i].alterIDs[j].cmdKey) | ||
} | ||
} | ||
return usage; | ||
} catch (error) { | ||
console.error(error) | ||
throw Error("read file error: ") | ||
return {} | ||
} | ||
return users; | ||
} | ||
function save(e) { | ||
try { | ||
// fs.writeFileSync(__dirname + "/data.json", JSON.stringify(users)); | ||
} catch (error) { | ||
if (!e === true) | ||
save(true) | ||
console.log("write file error") | ||
} | ||
function save() { | ||
storage.write() | ||
} | ||
//=================================================================== before exit | ||
process.stdin.resume(); | ||
function exitHandler(options, e) { | ||
if (options.error) console.error(e) | ||
if (options.cleanup) save(); | ||
if (options.exit) process.exit(); | ||
} | ||
process.on('exit', exitHandler.bind(null, { cleanup: true })); | ||
process.on('SIGINT', exitHandler.bind(null, { exit: true })); | ||
process.on('SIGUSR1', exitHandler.bind(null, { exit: true })); | ||
process.on('SIGUSR2', exitHandler.bind(null, { exit: true })); | ||
process.on('uncaughtException', exitHandler.bind(null, { exit: true, error: true })); | ||
module.exports = { | ||
AsAccount, | ||
user, | ||
addUsers, | ||
getSavedUsers, | ||
getUser, | ||
getUserByEmail, | ||
deleteUser, | ||
New, | ||
@@ -202,0 +211,0 @@ tostring, |
@@ -6,3 +6,2 @@ | ||
const consts = require("./consts") | ||
const AdvancedBuffer = require("./advanced-buffer") | ||
@@ -28,3 +27,3 @@ const kdf = require("./kdf") | ||
app._atyp = type; | ||
app._host = (type === ATYP_DOMAIN) ? Buffer.from(address) : toBuffer(address); | ||
app._host = (type === ATYP_DOMAIN) ? Buffer.from(address) : common.iptoBuffer(address); | ||
@@ -39,8 +38,9 @@ | ||
app._adBuf.on('data', DecodeResponseBody); | ||
console.log(data) | ||
const network = data.networks[Math.floor(Math.random() * data.networks.length)] | ||
var socket = remoteNetwork( | ||
data.network.address, | ||
data.network.port, | ||
data.network.option, | ||
network.address, | ||
network.port, | ||
network.option, | ||
localConnect, | ||
@@ -89,7 +89,11 @@ DecodeResponseHeader.bind(app), | ||
app.user.bytesRead += buffer.length | ||
const chunks = getChunks(buffer, 0x3fff).map(resolveChunk.bind(app)); | ||
const chunks = common.getChunks(buffer, 0x3fff).map(resolveChunk.bind(app)); | ||
this.message(Buffer.concat([header, ...chunks])) | ||
} else { | ||
if (app.user.deactive) | ||
return | ||
if (app.user.traffic != 0 && common.trafficlimit(app.user)) | ||
return log(`maximum traffic ${app.user.traffic / 1024 / 1024}MB (bytesRead:${app.user.bytesRead},bytesWrit:${app.user.bytesWrit}) used by user ${app.user.id.UUID.toString("hex")}`); | ||
app.user.bytesRead += buffer.length | ||
const chunks = getChunks(buffer, 0x3fff).map(resolveChunk.bind(app)); | ||
const chunks = common.getChunks(buffer, 0x3fff).map(resolveChunk.bind(app)); | ||
this.message(Buffer.concat(chunks)) | ||
@@ -105,2 +109,5 @@ } | ||
if (random_user.traffic != 0 && common.trafficlimit(random_user)) { | ||
return log(`maximum traffic ${random_user.traffic / 1024 / 1024}MB (bytesRead:${random_user.bytesRead},bytesWrit:${random_user.bytesWrit}) used by user ${random_user.id.UUID.toString("hex")}`) | ||
} | ||
// IV and Key for data chunks encryption/decryption | ||
@@ -113,2 +120,6 @@ app._dataEncIV = rands.subarray(0, 16); | ||
app._dataDecKey = common.hash('sha256', app._dataEncKey).subarray(0, 16); | ||
app.lengthEnKey = kdf.KDF16(app._dataDecKey, consts.KDFSaltConstAEADRespHeaderLenKey) | ||
app.lengthEnIV = kdf.KDF(app._dataDecIV, consts.KDFSaltConstAEADRespHeaderLenIV).subarray(0, 12) | ||
app.payloadEnKey = kdf.KDF16(app._dataDecKey, consts.KDFSaltConstAEADRespHeaderPayloadKey) | ||
app.payloadEnIV = kdf.KDF(app._dataDecIV, consts.KDFSaltConstAEADRespHeaderPayloadIV).subarray(0, 12) | ||
} else { | ||
@@ -119,5 +130,2 @@ app._dataDecIV = common.hash('md5', app._dataEncIV); | ||
app._dataEncKeyForChaCha20 = common.createChacha20Poly1305Key(app._dataEncKey); | ||
app._dataDecKeyForChaCha20 = common.createChacha20Poly1305Key(app._dataDecKey); | ||
app._chunkLenEncMaskGenerator = common.shake128(app._dataEncIV); | ||
@@ -135,2 +143,6 @@ app._chunkLenDecMaskGenerator = common.shake128(app._dataDecIV); | ||
if (app._security == consts.SECURITY_TYPE_CHACHA20_POLY1305) { | ||
app._dataEncKeyForChaCha20 = common.createChacha20Poly1305Key(app._dataEncKey); | ||
app._dataDecKeyForChaCha20 = common.createChacha20Poly1305Key(app._dataDecKey); | ||
} | ||
// create encrypted command | ||
@@ -184,7 +196,10 @@ let command = Buffer.from([ | ||
var header = decipher.update(buffer.subarray(0, 4)); | ||
if (app._v !== header[0]) { | ||
return fail(`server response v doesn't match, expect ${app._v} but got ${header[0]}`); | ||
} | ||
app._isHeaderRecv = true; | ||
return app._adBuf.put(buffer.subarray(4 + header[3]), app); | ||
} else { | ||
const aeadResponseHeaderLengthEncryptionKey = kdf.KDF16(app._dataDecKey, consts.KDFSaltConstAEADRespHeaderLenKey) | ||
const aeadResponseHeaderLengthEncryptionIV = kdf.KDF(app._dataDecIV, consts.KDFSaltConstAEADRespHeaderLenIV).subarray(0, 12) | ||
const decipher = crypto.createDecipheriv('aes-128-gcm', aeadResponseHeaderLengthEncryptionKey, aeadResponseHeaderLengthEncryptionIV); | ||
const decipher = crypto.createDecipheriv('aes-128-gcm', app.lengthEnKey, app.lengthEnIV); | ||
@@ -199,8 +214,4 @@ const aeadEncryptedResponseHeaderLength = buffer.subarray(0, 2) | ||
const aeadResponseHeaderPayloadEncryptionKey = kdf.KDF16(app._dataDecKey, consts.KDFSaltConstAEADRespHeaderPayloadKey) | ||
const aeadResponseHeaderPayloadEncryptionIV = kdf.KDF(app._dataDecIV, consts.KDFSaltConstAEADRespHeaderPayloadIV).subarray(0, 12) | ||
const decipher2 = crypto.createDecipheriv('aes-128-gcm', aeadResponseHeaderPayloadEncryptionKey, aeadResponseHeaderPayloadEncryptionIV); | ||
const decipher2 = crypto.createDecipheriv('aes-128-gcm', app.payloadEnKey, app.payloadEnIV); | ||
const encryptedResponseHeaderBuffer = buffer.subarray(18, decryptedResponseHeaderLength) | ||
@@ -212,2 +223,8 @@ const encryptedResponseHeaderBufferTag = buffer.subarray(decryptedResponseHeaderLength, decryptedResponseHeaderLength + 16) | ||
var header = Buffer.concat([decipher2.update(encryptedResponseHeaderBuffer), decipher2.final()]) | ||
if (app._v !== header[0]) { | ||
return fail(`server response v doesn't match, expect ${app._v} but got ${header[0]}`); | ||
} | ||
app._isHeaderRecv = true; | ||
return app._adBuf.put(buffer.subarray(decryptedResponseHeaderLength + 16 + header[3]), app); | ||
} | ||
@@ -217,10 +234,4 @@ } catch (error) { | ||
} | ||
if (app._v !== header[0]) { | ||
return fail(`server response v doesn't match, expect ${app._v} but got ${header[0]}`); | ||
} | ||
app._isHeaderRecv = true; | ||
return app._adBuf.put(buffer.subarray(4 + header[3]), app); | ||
} | ||
app._adBuf.put(buffer, app); | ||
return app._adBuf.put(buffer, app); | ||
} | ||
@@ -242,4 +253,2 @@ function DecodeResponseBody(chunk, app) { | ||
// ================================================= functions | ||
@@ -256,3 +265,2 @@ function resolveChunk(chunk) { | ||
} | ||
console.log(10, this._option, _len) | ||
return Buffer.concat([common.ntb(_len), _chunk]); | ||
@@ -270,80 +278,6 @@ } | ||
} | ||
console.log(20,app._option, len) | ||
return 2 + len; | ||
} | ||
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; | ||
} | ||
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; | ||
function toBuffer(ip, buff, offset) { | ||
offset = ~~offset; | ||
let result; | ||
if (ipv4Regex.test(ip)) { | ||
result = buff || Buffer.alloc(offset + 4); | ||
ip.split(/\./g).map((byte) => { | ||
result[offset++] = parseInt(byte, 10) & 0xff; | ||
}); | ||
} else if (ipv6Regex.test(ip)) { | ||
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 getAddrType(host) { | ||
@@ -350,0 +284,0 @@ if (net.isIPv4(host)) { |
@@ -5,2 +5,3 @@ | ||
const consts = require("./consts") | ||
const event = require("./../../core/event") | ||
@@ -69,4 +70,3 @@ function uint64ToBuffer(uint64) { | ||
const libsodium = require('libsodium-wrappers'); | ||
libsodium.ready | ||
function encrypt(plaintext, app) { | ||
@@ -119,3 +119,3 @@ const security = app._security; | ||
return plaintext; | ||
} catch (err) { | ||
} catch (err) { | ||
return null; | ||
@@ -126,2 +126,132 @@ } | ||
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 (!(ip in user.ipList)) { | ||
if (Object.keys(user.ipList).length >= user.ipCount) { | ||
for (const i in user.ipList) { | ||
if (user.ipList[i] + user.ipCountDuration < now) { | ||
delete user.ipList[i] | ||
} | ||
} | ||
if (Object.keys(user.ipList).length >= user.ipCount) { | ||
if (!user.maxip) { | ||
event.emit("ip", user.id.UUID) | ||
user.maxip = true | ||
} | ||
return true; | ||
} | ||
} | ||
if (user.maxip) | ||
user.maxip = false | ||
} | ||
user.ipList[ip] = now | ||
} | ||
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; | ||
}; | ||
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; | ||
function iptoBuffer(ip, buff, offset) { | ||
offset = ~~offset; | ||
let result; | ||
if (ipv4Regex.test(ip)) { | ||
result = buff || Buffer.alloc(offset + 4); | ||
ip.split(/\./g).map((byte) => { | ||
result[offset++] = parseInt(byte, 10) & 0xff; | ||
}); | ||
} else if (ipv6Regex.test(ip)) { | ||
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; | ||
}; | ||
module.exports = { | ||
@@ -135,5 +265,10 @@ ntb, | ||
encrypt, | ||
decrypt | ||
decrypt, | ||
getChunks, | ||
iptoString, | ||
iptoBuffer, | ||
trafficlimit, | ||
iplimit | ||
} | ||
const account = require("./account") | ||
const validator = require("./validator") | ||
module.exports = { | ||
server: require('./server'), | ||
client: require('./client'), | ||
user: account.user | ||
client: require('./client'), | ||
api: { | ||
getUserByEmail: validator.getUserByEmail, | ||
getUsers: validator.getUser, | ||
addUsers: validator.addUsers, | ||
removeUser: validator.removeUser | ||
} | ||
} |
@@ -6,3 +6,2 @@ | ||
const kdf = require("./kdf") | ||
const event = require("../../event") | ||
const consts = require("./consts") | ||
@@ -41,3 +40,2 @@ const crypto = require("crypto"); | ||
function DecodeRequestHeader(remoteProtocol, onRemoteMessage, onRemoteClose, checkuser, buffer) { | ||
const app = this.app | ||
@@ -82,6 +80,6 @@ if (!app._isHeaderRecv) { | ||
} | ||
if (aeadUser.ipCount != 0 && iplimit(app, aeadUser)) | ||
if (aeadUser.ipCount != 0 && common.iplimit(app.ip, aeadUser)) | ||
return log(`maximum ip used by user ${aeadUser.id.UUID.toString("hex")}`) | ||
if (aeadUser.traffic != 0 && trafficlimit(aeadUser)) { | ||
if (aeadUser.traffic != 0 && common.trafficlimit(aeadUser)) { | ||
return log(`maximum traffic ${aeadUser.traffic / 1024 / 1024}MB (bytesRead:${aeadUser.bytesRead},bytesWrit:${aeadUser.bytesWrit}) used by user ${aeadUser.id.UUID.toString("hex")}`) | ||
@@ -106,2 +104,6 @@ } | ||
app._dataEncKey = common.hash('sha256', app._dataDecKey).subarray(0, 16); | ||
app.lengthEnKey = kdf.KDF16(app._dataEncKey, consts.KDFSaltConstAEADRespHeaderLenKey) | ||
app.lengthEnIV = kdf.KDF(app._dataEncIV, consts.KDFSaltConstAEADRespHeaderLenIV).subarray(0, 12) | ||
app.payloadEnKey = kdf.KDF16(app._dataEncKey, consts.KDFSaltConstAEADRespHeaderPayloadKey) | ||
app.payloadEnIV = kdf.KDF(app._dataEncIV, consts.KDFSaltConstAEADRespHeaderPayloadIV).subarray(0, 12) | ||
} else { | ||
@@ -111,4 +113,7 @@ app._dataEncIV = common.hash('md5', app._dataDecIV); | ||
} | ||
app._chunkLenDecMaskGenerator = common.shake128(app._dataDecIV); | ||
app._chunkLenEncMaskGenerator = common.shake128(app._dataEncIV); | ||
app._responseHeader = reqHeader[33]; | ||
@@ -126,2 +131,6 @@ app._option = reqHeader[34]; | ||
} | ||
if (securityType == consts.SECURITY_TYPE_CHACHA20_POLY1305) { | ||
app._dataEncKeyForChaCha20 = common.createChacha20Poly1305Key(app._dataEncKey); | ||
app._dataDecKeyForChaCha20 = common.createChacha20Poly1305Key(app._dataDecKey); | ||
} | ||
//=========== | ||
@@ -184,3 +193,3 @@ const cmd = reqHeader[37]; | ||
app.remote = remoteProtocol( | ||
addrType === ATYP_DOMAIN ? addr.toString() : iptoString(addr), | ||
addrType === ATYP_DOMAIN ? addr.toString() : common.iptoString(addr), | ||
port, | ||
@@ -200,3 +209,5 @@ cmd, | ||
} else { | ||
if (app.user.traffic != 0 && trafficlimit(app.user)) | ||
if (app.user.deactive) | ||
return | ||
if (app.user.traffic != 0 && common.trafficlimit(app.user)) | ||
return log(`maximum traffic ${app.user.traffic / 1024 / 1024}MB (bytesRead:${app.user.bytesRead},bytesWrit:${app.user.bytesWrit}) used by user ${app.user.id.UUID.toString("hex")}`); | ||
@@ -229,18 +240,10 @@ app._adBuf.put(buffer, app); | ||
} else { | ||
const aeadResponseHeaderLengthEncryptionKey = kdf.KDF16(app._dataEncKey, consts.KDFSaltConstAEADRespHeaderLenKey) | ||
const aeadResponseHeaderLengthEncryptionIV = kdf.KDF(app._dataEncIV, consts.KDFSaltConstAEADRespHeaderLenIV).subarray(0, 12) | ||
const aeadResponseHeaderLengthEncryptionBuffer = Buffer.alloc(2); | ||
aeadResponseHeaderLengthEncryptionBuffer.writeUInt16BE(outBuffer.length) | ||
const cipher = crypto.createCipheriv('aes-128-gcm', aeadResponseHeaderLengthEncryptionKey, aeadResponseHeaderLengthEncryptionIV); | ||
const cipher = crypto.createCipheriv('aes-128-gcm', app.lengthEnKey, app.lengthEnIV); | ||
var AEADEncryptedLength = Buffer.concat([cipher.update(aeadResponseHeaderLengthEncryptionBuffer), cipher.final(), cipher.getAuthTag()]) | ||
const cipher2 = crypto.createCipheriv('aes-128-gcm', app.payloadEnKey, app.payloadEnIV); | ||
const aeadResponseHeaderPayloadEncryptionKey = kdf.KDF16(app._dataEncKey, consts.KDFSaltConstAEADRespHeaderPayloadKey) | ||
const aeadResponseHeaderPayloadEncryptionIV = kdf.KDF(app._dataEncIV, consts.KDFSaltConstAEADRespHeaderPayloadIV).subarray(0, 12) | ||
const cipher2 = crypto.createCipheriv('aes-128-gcm', aeadResponseHeaderPayloadEncryptionKey, aeadResponseHeaderPayloadEncryptionIV); | ||
var aeadEncryptedHeaderPayload = Buffer.concat([cipher2.update(outBuffer), cipher2.final(), cipher2.getAuthTag()]) | ||
@@ -256,10 +259,11 @@ return Buffer.concat([AEADEncryptedLength, aeadEncryptedHeaderPayload]) | ||
const app = this.app | ||
app.user.bytesRead += buffer.length | ||
if (!app._isHeaderSent) { | ||
app._isHeaderSent = true; | ||
const header = EncodeResponseHeader(app) | ||
const chunks = getChunks(buffer, 0x3fff).map(resolveChunk.bind(app)); | ||
app.user.bytesRead += buffer.length | ||
const chunks = common.getChunks(buffer, 0x3fff).map(resolveChunk.bind(app)); | ||
this.localMessage(Buffer.concat([header, ...chunks])) | ||
} else { | ||
const chunks = getChunks(buffer, 0x3fff).map(resolveChunk.bind(app)); | ||
app.user.bytesRead += buffer.length | ||
const chunks = common.getChunks(buffer, 0x3fff).map(resolveChunk.bind(app)); | ||
this.localMessage(Buffer.concat(chunks)) | ||
@@ -279,3 +283,2 @@ } | ||
} | ||
console.log(10, this._option, _len) | ||
return Buffer.concat([common.ntb(_len), _chunk]); | ||
@@ -290,10 +293,8 @@ } | ||
if (app._option >= 0x05) { | ||
var mask = app._chunkLenDecMaskGenerator.nextBytes(2).readUInt16BE(0); | ||
const mask = app._chunkLenDecMaskGenerator.nextBytes(2).readUInt16BE(0); | ||
len = mask ^ len; | ||
} | ||
console.log(20, app._option, len) | ||
return 2 + len; | ||
} | ||
function onclose() { | ||
@@ -322,77 +323,2 @@ if (this.app.remote) | ||
// ================================================= sender | ||
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(app, user) { | ||
var now = Math.round(new Date() / 1000) | ||
if (!(app.ip in user.ipList)) { | ||
if (Object.keys(user.ipList).length >= user.ipCount) { | ||
for (const i in user.ipList) { | ||
if (user.ipList[i] + user.ipCountDuration < now) { | ||
delete user.ipList[i] | ||
} | ||
} | ||
if (Object.keys(user.ipList).length >= user.ipCount) { | ||
if (!user.maxip) { | ||
event.emit("ip", user.id.UUID) | ||
user.maxip = true | ||
} | ||
return true; | ||
} | ||
} | ||
if (user.maxip) | ||
user.maxip = false | ||
} | ||
user.ipList[app.ip] = now | ||
} | ||
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; | ||
}; | ||
module.exports = init |
const crypto = require("crypto"), | ||
common = require("./common"), | ||
_accont = require("./account"), | ||
account = require("./account"), | ||
aead = require('./aead'), | ||
antireplay = require('./replayfilter'); | ||
const tuvs = {} | ||
function init(users_config) { | ||
var users = [] | ||
var AuthIDDecoderHolder = { | ||
decoders: {}, | ||
filter: antireplay.NewReplayFilter(120) | ||
if (users_config.tag == undefined) | ||
users_config.tag = (Math.random() + 1).toString(36).substring(3) | ||
const tuv = { | ||
users: [], | ||
userHash: {}, | ||
baseTime: unix() - cacheDurationSec * 2, | ||
aeadDecoderHolder: { | ||
decoders: {}, | ||
filter: antireplay.NewReplayFilter(120) | ||
}, | ||
} | ||
tuv.task = periodic(updateUserHash.bind(tuv), updateInterval) | ||
tuvs[users_config.tag] = tuv | ||
var nowSec = unix() | ||
addUsers(users_config.tag, users_config.users) | ||
return { | ||
get: getRandomUser.bind(tuv), | ||
check: checkUser.bind(tuv), | ||
} | ||
} | ||
///==================================================== | ||
for (const user of _accont.AsAccount(users_config.tag, users_config.users)) { | ||
function addUsers(tag, users) { | ||
const tuv = tuvs[tag] | ||
for (const user of Object.values(account.addUsers(tag, users))) { | ||
if (user.alterIDs.length == 0) { | ||
aead.AddUser(AuthIDDecoderHolder, user.id.cmdKey, user) | ||
aead.AddUser(tuv.aeadDecoderHolder, user.id.cmdKey, user) | ||
} else { | ||
users.push({ | ||
tuv.users.push({ | ||
user, | ||
lastSec: (nowSec - cacheDurationSec), | ||
lastSec: (unix() - cacheDurationSec), | ||
}) | ||
} | ||
} | ||
return NewTimedUserValidator(AuthIDDecoderHolder, users); | ||
checkUserHash(tuv) | ||
} | ||
function Close() { | ||
return v.task.close() | ||
function removeUser(tag, id) { | ||
const tuv = tuvs[tag] | ||
const user = account.getUser(tag, id) | ||
if (user) { | ||
account.deleteUser(tag, id) | ||
if (user.alterIDs.length == 0) | ||
aead.RemoveUser(tuv.aeadDecoderHolder, user.id.cmdKey) | ||
else { | ||
for (key in v.userHash) { | ||
if (v.userHash[key].user.user.id.UUID == id) { | ||
delete v.userHash[key] | ||
} | ||
} | ||
} | ||
checkUserHash(tuv) | ||
return true; | ||
} | ||
return false | ||
} | ||
///==================================================== | ||
function checkUserHash(tuv) { | ||
if (tuv.users.length != 0) { | ||
tuv.task.start() | ||
} else { | ||
tuv.userHash = {} | ||
tuv.task.close() | ||
} | ||
} | ||
function getRandomUser() { | ||
const keys = Object.keys(this.aeadDecoderHolder.decoders) | ||
if (keys.length != 0) { | ||
const key = keys[Math.floor(Math.random() * keys.length)] | ||
const cacheItem = this.aeadDecoderHolder.decoders[key] | ||
return [true, cacheItem.ticket] | ||
// return [true, cacheItem.ticket, Buffer.from(key, "hex")] | ||
} else { | ||
const keys = Object.keys(this.userHash) | ||
const key = keys[Math.floor(Math.random() * keys.length)] | ||
const cacheItem = this.userHash[key] | ||
return [false, cacheItem.user.user, Buffer.from(key, "hex"), common.uint64ToBuffer(cacheItem.timeInc + this.baseTime)] | ||
} | ||
} | ||
function checkUser(authInfo, isaead) { | ||
if (isaead) | ||
return aead.Match(this.aeadDecoderHolder, authInfo) | ||
else if (authInfo in this.userHash) { | ||
const cacheItem = this.userHash[authInfo] | ||
return [cacheItem.user.user, common.uint64ToBuffer(cacheItem.timeInc + this.baseTime)] | ||
} | ||
} | ||
const updateInterval = 10 | ||
@@ -41,41 +103,2 @@ const cacheDurationSec = 120 | ||
function NewTimedUserValidator(AuthIDDecoderHolder, users) { | ||
const tuv = { | ||
users: users, | ||
userHash: {}, | ||
baseTime: unix() - cacheDurationSec * 2, | ||
aeadDecoderHolder: AuthIDDecoderHolder | ||
} | ||
if (users.length != 0) { | ||
tuv.task = periodic(function () { | ||
updateUserHash(tuv) | ||
}, updateInterval) | ||
tuv.task.start() | ||
} | ||
return { | ||
get: function () { | ||
const keys = Object.keys(tuv.aeadDecoderHolder.decoders) | ||
if (keys.length != 0) { | ||
const key = keys[Math.floor(Math.random() * keys.length)] | ||
const cacheItem = tuv.aeadDecoderHolder.decoders[key] | ||
return [true, cacheItem.ticket] | ||
// return [true, cacheItem.ticket, Buffer.from(key, "hex")] | ||
} else { | ||
const keys = Object.keys(tuv.userHash) | ||
const key = keys[Math.floor(Math.random() * keys.length)] | ||
const cacheItem = tuv.userHash[key] | ||
return [false, cacheItem.user.user, Buffer.from(key, "hex"), common.uint64ToBuffer(cacheItem.timeInc + tuv.baseTime)] | ||
} | ||
}, | ||
check: function (authInfo, isaead) { | ||
if (isaead) | ||
return aead.Match(tuv.aeadDecoderHolder, authInfo) | ||
else if (authInfo in tuv.userHash) { | ||
const cacheItem = tuv.userHash[authInfo] | ||
return [cacheItem.user.user, common.uint64ToBuffer(cacheItem.timeInc + tuv.baseTime)] | ||
} | ||
} | ||
} | ||
} | ||
function generateNewHashes(v, nowSec, user) { | ||
@@ -112,12 +135,12 @@ var genEndSec = nowSec + cacheDurationSec | ||
function updateUserHash(v) { | ||
function updateUserHash() { | ||
var nowSec = (unix()) | ||
for (var user of v.users) { | ||
generateNewHashes(v, nowSec, user) | ||
for (var user of this.users) { | ||
generateNewHashes(this, nowSec, user) | ||
} | ||
var expire = (unix() - cacheDurationSec) | ||
if (expire > v.baseTime) { | ||
removeExpiredHashes(v, (expire - v.baseTime)) | ||
if (expire > this.baseTime) { | ||
removeExpiredHashes(this, (expire - this.baseTime)) | ||
} | ||
@@ -158,5 +181,8 @@ } | ||
init, | ||
Close, | ||
addUsers, | ||
removeUser, | ||
getUser: account.getUser, | ||
getUserByEmail: account.getUserByEmail, | ||
OpenVMessAEADHeader: aead.OpenVMessAEADHeader, | ||
SealVMessAEADHeader: aead.SealVMessAEADHeader | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
118930
2
30
3041
7
- Removedcbor-js@^0.1.0
- Removedlibsodium-wrappers@^0.7.11
- Removedcbor-js@0.1.0(transitive)
- Removedlibsodium@0.7.15(transitive)
- Removedlibsodium-wrappers@0.7.15(transitive)