UNPKG

shadowsocks-single

Version:

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

190 lines (151 loc) 5.65 kB
'use strict'; exports.__esModule = true; exports.default = createUDPRelay; var _dgram = require('dgram'); var _dgram2 = _interopRequireDefault(_dgram); var _lruCache = require('lru-cache'); var _lruCache2 = _interopRequireDefault(_lruCache); var _ip = require('ip'); var _ip2 = _interopRequireDefault(_ip); var _utils = require('./utils'); var _encryptor = require('./encryptor'); var encryptor = _interopRequireWildcard(_encryptor); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // SOCKS5 UDP Request // +----+------+------+----------+----------+----------+ // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | // +----+------+------+----------+----------+----------+ // | 2 | 1 | 1 | Variable | 2 | Variable | // +----+------+------+----------+----------+----------+ // // SOCKS5 UDP Response // +----+------+------+----------+----------+----------+ // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | // +----+------+------+----------+----------+----------+ // | 2 | 1 | 1 | Variable | 2 | Variable | // +----+------+------+----------+----------+----------+ // // shadowsocks UDP Request (before encrypted) // +------+----------+----------+----------+ // | ATYP | DST.ADDR | DST.PORT | DATA | // +------+----------+----------+----------+ // | 1 | Variable | 2 | Variable | // +------+----------+----------+----------+ // // shadowsocks UDP Response (before encrypted) // +------+----------+----------+----------+ // | ATYP | DST.ADDR | DST.PORT | DATA | // +------+----------+----------+----------+ // | 1 | Variable | 2 | Variable | // +------+----------+----------+----------+ // // shadowsocks UDP Request and Response (after encrypted) // +-------+--------------+ // | IV | PAYLOAD | // +-------+--------------+ // | Fixed | Variable | // +-------+--------------+ var NAME = 'udp_relay'; var LRU_OPTIONS = { max: 1000, maxAge: 10 * 1000, dispose: function dispose(key, socket) { // close socket if it's not closed if (socket) { socket.close(); } } }; var SOCKET_TYPE = ['udp4', 'udp6']; function getIndex(_ref, _ref2) { var address = _ref.address, port = _ref.port; var dstAddrStr = _ref2.dstAddrStr, dstPortNum = _ref2.dstPortNum; return address + ':' + port + '_' + dstAddrStr + ':' + dstPortNum; } function createClient(logger, _ref3, onMsg, onClose) { var atyp = _ref3.atyp, dstAddr = _ref3.dstAddr, dstPort = _ref3.dstPort; var udpType = atyp === 1 ? 'udp4' : 'udp6'; var socket = _dgram2.default.createSocket(udpType); socket.on('message', onMsg); socket.on('error', function (e) { logger.warn(NAME + ' client socket gets error: ' + e.message); }); socket.on('close', onClose); return socket; } function createUDPRelaySocket(udpType, config, isServer, logger) { var localPort = config.localPort, serverPort = config.serverPort, password = config.password, method = config.method; var serverAddr = udpType === 'udp4' ? config.serverAddr : config.serverAddrIPv6; var encrypt = encryptor.encrypt.bind(null, password, method); var decrypt = encryptor.decrypt.bind(null, password, method); var socket = _dgram2.default.createSocket(udpType); var cache = new _lruCache2.default(Object.assign({}, LRU_OPTIONS, { maxAge: config.timeout * 1000 })); var listenPort = isServer ? serverPort : localPort; socket.on('message', function (_msg, rinfo) { var msg = isServer ? decrypt(_msg) : _msg; var frag = msg[2]; if (frag !== 0) { // drop those datagram that using frag return; } var dstInfo = (0, _utils.getDstInfoFromUDPMsg)(msg, isServer); var dstAddrStr = _ip2.default.toString(dstInfo.dstAddr); var dstPortNum = dstInfo.dstPort.readUInt16BE(); var index = getIndex(rinfo, { dstAddrStr: dstAddrStr, dstPortNum: dstPortNum }); logger.debug(NAME + ' receive message: ' + msg.toString('hex')); var client = cache.get(index); if (!client) { client = createClient(logger, dstInfo, function (msgStream) { // socket on message var incomeMsg = isServer ? encrypt(msgStream) : decrypt(msgStream); (0, _utils.sendDgram)(socket, incomeMsg, rinfo.port, rinfo.address); }, function () { // socket on close cache.del(index); }); cache.set(index, client); } if (isServer) { (0, _utils.sendDgram)(client, msg.slice(dstInfo.totalLength), dstPortNum, dstAddrStr); } else { (0, _utils.sendDgram)(client, // skip RSV and FLAG encrypt(msg.slice(3)), serverPort, serverAddr); } }); socket.on('error', function (err) { logger.error(NAME + ' socket err: ' + err.message); socket.close(); }); socket.on('close', function () { cache.reset(); }); socket.bind(listenPort, function () { logger.verbose(NAME + ' is listening on: ' + listenPort); }); return socket; } function close(sockets) { sockets.forEach(function (socket) { (0, _utils.closeSilently)(socket); }); } function createUDPRelay(config, isServer, logger) { var sockets = SOCKET_TYPE.map(function (udpType) { return createUDPRelaySocket(udpType, config, isServer, logger); }); return { sockets: sockets, close: close.bind(null, sockets) }; }