lightsword
Version:
LightSword Secure SOCKS5 Proxy / iOS VPN Server
199 lines (153 loc) • 5.79 kB
text/typescript
//-----------------------------------
// Copyright(c) 2015 Neko
//-----------------------------------
import * as net from 'net';
import { EventEmitter } from 'events';
import * as crypto from 'crypto';
import * as cryptoEx from '../common/cipher';
import { VPN_TYPE, HandshakeOptions, defaultCipherAlgorithm } from '../common/constant'
import { handleSocks5 } from './socks5/index';
import { handleOSXSocks5 } from './osxcl5/index';
import { handleAppleVPN } from './aplvpn/index';
import * as kinq from 'kinq';
export type ServerOptions = {
cipherAlgorithm: string,
password: string,
port: number,
timeout?: number,
expireDate?: string,
disableSelfProtection?: boolean,
speed?: number
}
export type UpdateServerOptions = {
expireDate?: string,
disableSelfProtection?: boolean,
speed?: number
}
export class LsServer extends EventEmitter {
disableSelfProtection = false;
cipherAlgorithm: string;
password: string;
port: number;
timeout: number;
expireDate: string;
speed: number;
blackIPs = new Set<string>();
private static expireRefreshInterval = 60 * 60 * 1000;
private remainingTime: number; // Unit: ms
private remainingTimer: NodeJS.Timer;
private blacklistIntervalTimer: NodeJS.Timer;
private blacklist = new Map<string, Set<number>>();
private server: net.Server;
private requestHandlers = new Map<VPN_TYPE, (client: net.Socket, data: Buffer, options: HandshakeOptions) => boolean>();
constructor(options: ServerOptions) {
super()
let me = this;
Object.getOwnPropertyNames(options).forEach(n => me[n] = options[n]);
this.requestHandlers.set(VPN_TYPE.APLVPN, handleAppleVPN);
this.requestHandlers.set(VPN_TYPE.SOCKS5, handleSocks5);
this.requestHandlers.set(VPN_TYPE.OSXCL5, handleOSXSocks5);
}
start() {
let me = this;
let server = net.createServer(async (client) => {
if (me.blacklist.has(client.remoteAddress) && me.blacklist.get(client.remoteAddress).size > 20) return client.dispose();
let data = await client.readAsync();
if (!data) return client.dispose();
let meta = cryptoEx.SupportedCiphers[me.cipherAlgorithm];
if (!meta) meta = cryptoEx.SupportedCiphers[defaultCipherAlgorithm];
let ivLength = meta[1];
if (data.length < ivLength) {
console.warn(client.remoteAddress, 'Malicious Access');
return me.addToBlacklist(client);
}
let iv = data.slice(0, ivLength);
let decipher = cryptoEx.createDecipher(me.cipherAlgorithm, me.password, iv);
let et = data.slice(ivLength, data.length);
let dt = Buffer.concat([decipher.update(et), decipher.final()]);
if (dt.length < 2) {
console.warn(client.remoteAddress, 'Malicious Access')
return me.addToBlacklist(client);
}
let vpnType = dt[0];
let paddingSize = dt[1];
let options = {
iv,
password: me.password,
cipherAlgorithm: me.cipherAlgorithm,
timeout: me.timeout,
xorNum: paddingSize,
speed: me.speed,
ivLength,
};
let request = dt.slice(2 + paddingSize, data.length);
let handler = me.requestHandlers.get(vpnType);
if (!handler) return me.addToBlacklist(client);
let handled = handler(client, request, options);
if (handled) return;
me.addToBlacklist(client);
});
this.server = server;
server.listen(this.port);
server.on('error', (err) => {
console.error(err.message);
me.stop();
});
this.blacklistIntervalTimer = setInterval(() => me.blacklist.clear(), 10 * 60 * 1000);
this.blacklistIntervalTimer.unref();
setInterval(() => me.blackIPs.clear(), 24 * 60 * 60 * 1000).unref();
this.startRemainingTimer();
}
stop() {
if (!this.server) return;
this.server.removeAllListeners();
this.server.close();
this.server = undefined;
this.stopRemainingTimer();
this.emit('close');
this.blacklist.clear();
if (this.blacklistIntervalTimer) clearInterval(this.blacklistIntervalTimer);
this.blacklistIntervalTimer = undefined;
}
updateConfiguration(options: UpdateServerOptions) {
this.disableSelfProtection = options.disableSelfProtection;
this.expireDate = options.expireDate;
this.startRemainingTimer();
}
private addToBlacklist(client: net.Socket) {
if (this.disableSelfProtection) return;
let ports = this.blacklist.get(client.remoteAddress);
if (!ports) {
ports = new Set<number>();
this.blacklist.set(client.remoteAddress, ports);
}
ports.add(client.remotePort);
client.dispose();
this.blackIPs.add(client.remoteAddress);
}
private startRemainingTimer() {
let me = this;
this.remainingTime = this.expireDate ? (<any>(new Date(this.expireDate)) - <any>new Date()) : undefined;
if (!this.remainingTime) return me.stopRemainingTimer();
if (this.remainingTime <= 0) {
return process.nextTick(() => {
console.info(`${me.port} expired. ${me.expireDate} ${me.remainingTime}`);
me.stop();
});
}
this.stopRemainingTimer();
this.remainingTimer = setInterval(() => {
me.remainingTime -= LsServer.expireRefreshInterval;
if (me.remainingTime > 0) return;
console.info(`${me.port} expired. ${me.expireDate} ${me.remainingTime}`);
me.stop();
}, LsServer.expireRefreshInterval);
this.remainingTimer.unref();
}
private stopRemainingTimer() {
if (!this.remainingTimer) return;
clearInterval(this.remainingTimer);
this.remainingTimer = undefined;
}
}