UNPKG

shadowsocks-single

Version:

Yet another shadowsocks implementation for nodejs to help you bypass firewalls.

226 lines (189 loc) 5.12 kB
import ip from 'ip'; import os from 'os'; import { writeFileSync, accessSync, mkdirSync } from 'fs'; import { join } from 'path'; const DEFAULT_PATH = join(__dirname, '../logs/debug.log'); const hasOwnProperty = {}.hasOwnProperty; let platform = null; export const BufferFrom = (() => { try { Buffer.from('aa', 'hex'); } catch (err) { return ((...args) => new Buffer(...args)); } return Buffer.from; })(); export function isWindows() { if (!platform) { platform = os.type(); } return platform === 'Windows_NT'; } export function safelyKill(pid, signal) { if (pid === null || pid === undefined) { return; } if (signal && !isWindows()) { process.kill(pid, signal); } else { process.kill(pid); } } export function safelyKillChild(child, signal) { if (!child) { return; } if (signal && !isWindows()) { child.kill(signal); } else { child.kill(); } } export function fileLog(content, path = DEFAULT_PATH) { writeFileSync(path, content); } // NOTE: https://github.com/winstonjs/winston/issues/228 // Winston will log things asynchronously so we have to // make sure it has log the error before exiting this // process. // And this is disappointing. export function createSafeAfterHandler(logger, next) { let numFlushes = 0; let numFlushed = 0; return () => { Object.keys(logger.transports).forEach((k) => { // eslint-disable-next-line const stream = logger.transports[k]._stream; if (stream) { numFlushes += 1; stream.once('finish', () => { numFlushed += 1; if (numFlushes === numFlushed) { next(); } }); stream.end(); } }); if (numFlushes === 0) { next(); } }; } export function closeSilently(server) { if (server) { try { server.close(); } catch (e) { // already closed } } } export function mkdirIfNotExistSync(path) { try { accessSync(path); } catch (e) { mkdirSync(path); } } export function sendDgram(socket, data, ...args) { socket.send(data, 0, data.length, ...args); } export function writeOrPause(fromCon, toCon, data) { const res = toCon.write(data); if (!res) { fromCon.pause(); } return res; } function parseDstInfo(data, offset) { const atyp = data[offset]; let dstAddr; let dstPort; let dstAddrLength; let dstPortIndex; let dstPortEnd; // length of non-data field let totalLength; switch (atyp) { case 0x01: dstAddrLength = 4; dstAddr = data.slice(offset + 1, offset + 5); dstPort = data.slice(offset + 5, offset + 7); totalLength = offset + 7; break; case 0x04: dstAddrLength = 16; dstAddr = data.slice(offset + 1, offset + 17); dstPort = data.slice(offset + 17, offset + 19); totalLength = offset + 19; break; case 0x03: dstAddrLength = data[offset + 1]; dstPortIndex = 2 + offset + dstAddrLength; dstAddr = data.slice(offset + 2, dstPortIndex); dstPortEnd = dstPortIndex + 2; dstPort = data.slice(dstPortIndex, dstPortEnd); totalLength = dstPortEnd; break; default: return null; } if (data.length < totalLength) { return null; } return { atyp, dstAddrLength, dstAddr, dstPort, totalLength, }; } export function getDstInfo(data, isServer) { // +----+-----+-------+------+----------+----------+ // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | // +----+-----+-------+------+----------+----------+ // | 1 | 1 | X'00' | 1 | Variable | 2 | // +----+-----+-------+------+----------+----------+ // Yet shadowsocks begin with ATYP. const offset = isServer ? 0 : 3; return parseDstInfo(data, offset); } export function getDstInfoFromUDPMsg(data, isServer) { // +----+------+------+----------+----------+----------+ // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | // +----+------+------+----------+----------+----------+ // | 2 | 1 | 1 | Variable | 2 | Variable | // +----+------+------+----------+----------+----------+ const offset = isServer ? 0 : 3; return parseDstInfo(data, offset); } const formatKeyValues = { server: 'serverAddr', server_port: 'serverPort', local_addr: 'localAddr', local_port: 'localPort', local_addr_ipv6: 'localAddrIPv6', server_addr_ipv6: 'serverAddrIPv6', }; export function formatConfig(_config) { const formattedConfig = Object.assign({}, _config); Object.keys(formatKeyValues).forEach((key) => { if (hasOwnProperty.call(formattedConfig, key)) { formattedConfig[formatKeyValues[key]] = formattedConfig[key]; delete formattedConfig[key]; } }); return formattedConfig; } export function getDstStr(dstInfo) { if (!dstInfo) { return null; } switch (dstInfo.atyp) { case 1: case 4: return `${ip.toString(dstInfo.dstAddr)}:${dstInfo.dstPort.readUInt16BE()}`; case 3: return `${dstInfo.dstAddr.toString('utf8')}:${dstInfo.dstPort.readUInt16BE()}`; default: return 'WARN: invalid atyp'; } }