commit a26863c09afdd64d769dbc0df0f1b759af6daf08 Author: Alexander Sedov Date: Mon Apr 17 19:30:08 2017 +0300 Initial import. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9a24d50 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.json] +insert_final_newline = false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0f2b01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/.idea diff --git a/app.sh b/app.sh new file mode 100755 index 0000000..7ae818f --- /dev/null +++ b/app.sh @@ -0,0 +1,41 @@ +#!/bin/bash -e +APP_NAME=xmrig-proxy +ACTION=$1 +. "$NVM_DIR/nvm.sh" + +start() { + NODE_ENV=production pm2 start index.js --interpreter=`nvm which stable` --name ${APP_NAME} --log log/${APP_NAME}.log --output log/${APP_NAME}.out --error log/${APP_NAME}.err +} + +stop() { + pm2 stop ${APP_NAME} +} + +restart() { + pm2 restart ${APP_NAME} +} + +update() { + git pull + stop + start +} + +case "${ACTION}" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + update) + update + ;; + *) + echo $"Usage: $0 {start|stop|restart|update}" + exit 1 +esac +exit 0 \ No newline at end of file diff --git a/app/Proxy.js b/app/Proxy.js new file mode 100644 index 0000000..a4fb065 --- /dev/null +++ b/app/Proxy.js @@ -0,0 +1,88 @@ +'use strict'; + +const Promise = require('bluebird'); +const uuid = require('uuid'); +const net = require('net'); +const readline = require('readline'); +const log = require('./log'); + +const READY = 0; +const WAIT_LOGIN = 1; +const WAIT_CLOSE = 2; + + +class Proxy { + constructor(options, onLogin) { + this.host = options.host; + this.port = options.port; + this.onLogin = onLogin; + + this.server = net.createServer(this.onConnection.bind(this)); + } + + + listen(options) { + return Promise.promisify(this.server.listen, { context: this.server })(options); + } + + + onConnection(socket) { + socket.id = uuid.v4(); + + _setupSocket(socket); + socket.state = WAIT_LOGIN; + + const upstream = _setupSocket(new net.Socket()); + upstream.connect({ host: this.host, port: this.port }); + + const i = readline.createInterface({ input: socket, terminal: false }); + i.on('line', line => { + if (line === '') { + return; + } + + if (socket.state === WAIT_LOGIN) { + try { + upstream.write(this.onLogin(socket, line)); + socket.state = READY; + } + catch (e) { + socket.state = WAIT_CLOSE; + log.error(`[login] "${e.message}"`); + socket.end(); + } + + return; + } + + // console.log('<<<', line); + upstream.write(line + '\n'); + }); + + upstream.on('data', data => { + // console.log('>>>', data.toString()); + socket.write(data) + }); + + socket.on('close', had_error => { + upstream.end(); + }); + + upstream.on('close', had_error => { + socket.end(); + }); + } +} + + +function _setupSocket(socket) { + socket.setNoDelay(true); + socket.setKeepAlive(true, 120); + + socket.on('error', err => {}); + + return socket; +} + + +module.exports = Proxy; diff --git a/app/config.js b/app/config.js new file mode 100644 index 0000000..d968ed9 --- /dev/null +++ b/app/config.js @@ -0,0 +1,24 @@ +'use strict'; + +const Promise = require('bluebird'); +const nconf = require('nconf'); +const path = require('path'); + + +let suffix = '-dev'; + +if (process.env.NODE_ENV === 'production') { + suffix = ''; +} +else if (process.env.NODE_ENV === 'test') { + suffix = '-test'; +} + + +nconf.file(path.join(__dirname, `/../config/app${suffix}.json`)); +nconf.file('default', path.join(__dirname, '/../config/default.json')); + + +module.exports.save = function() { + return Promise.promisify(nconf.save, {context: nconf})(); +}; diff --git a/app/log.js b/app/log.js new file mode 100644 index 0000000..c6b3bac --- /dev/null +++ b/app/log.js @@ -0,0 +1,20 @@ +'use strict'; + +const winston = require('winston'); + + +const logger = new (winston.Logger)({ + transports: [ + new winston.transports.File({ filename: __dirname + '/../log/app.log', json: false }) + ], + exitOnError: true +}); + + +/* istanbul ignore next */ +if (process.env.NODE_ENV !== 'production') { + logger.add(winston.transports.Console, { colorize: true, timestamp: true, level: 'debug' }); +} + + +module.exports = logger; diff --git a/app/login.js b/app/login.js new file mode 100644 index 0000000..6d928b9 --- /dev/null +++ b/app/login.js @@ -0,0 +1,49 @@ +'use strict'; + +const nconf = require('nconf'); +const rigs = require('./rigs'); + +const BYPASS_WORKER_ID = nconf.get('bypass_worker_id'); +const BYPASS_WALLET = nconf.get('bypass_wallet'); +const WALLET_ADDRESS = nconf.get('wallet'); +const PASSWORD = nconf.get('password'); +const USER_AGENT = nconf.get('agent'); + + +function login(socket, line) { + if (line.charAt(0) !== '{') { + throw new Error(line); + } + + const packet = JSON.parse(line); + if (packet.method !== 'login') { + throw new Error('Invalid method'); + } + + rigs.add(socket, packet.params); + packet.params.login = getLogin(packet.params.login); + packet.params.agent = USER_AGENT; + + if (PASSWORD) { + packet.params.pass = PASSWORD; + } + + return JSON.stringify(packet) + '\n'; +} + + +function getLogin(login) { + login = login.split('.'); + if (!BYPASS_WALLET || login[0].length < 95) { + login[0] = WALLET_ADDRESS; + } + + if (!BYPASS_WORKER_ID || login.length < 2) { + return login[0] + } + + return login.join('.'); +} + + +module.exports = login; diff --git a/app/rigs.js b/app/rigs.js new file mode 100644 index 0000000..8d5e603 --- /dev/null +++ b/app/rigs.js @@ -0,0 +1,47 @@ +'use strict'; + +const log = require('./log'); + + +const STORE = new Map(); + + +/** + * Add new rig. + * + * @param {Object} socket + * @param {Object} params + */ +function add(socket, params) { + STORE.set(socket.id, { id: socket.id, ip: socket.remoteAddress, login: params.login, ua: params.agent, datetime: +new Date() }); + + socket.on('close', had_error => { + remove(socket.id, had_error); + }); + + log.info(socket.remoteAddress, `login: "${params.login}", ua: "${params.agent}", count: ${STORE.size}`); +} + + +function remove(socket_id, had_error) { + const info = STORE.get(socket_id); + if (!info) { + return; + } + + STORE.delete(socket_id); + log.info(info.ip, `close: "${info.login}", had_error: ${had_error}, count: ${STORE.size}`); +} + + +function report() { + return { + rigsCount: STORE.size, + rigs: Array.from(STORE.values()) + }; +} + + +module.exports.add = add; +module.exports.remove = remove; +module.exports.report = report; diff --git a/config/default.json b/config/default.json new file mode 100644 index 0000000..9cf92b1 --- /dev/null +++ b/config/default.json @@ -0,0 +1,21 @@ +{ + "wallet": "48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD", + "bypass_worker_id": true, + "bypass_wallet": true, + "password": "", + "agent": "cpuminer-multi/0.1", + "upstream": { + "host": "xmr-eu.dwarfpool.com", + "port": 8005 + }, + "proxy": [ + { + "host": "0.0.0.0", + "port": 80 + }, + { + "host": "0.0.0.0", + "port": 443 + } + ] +} \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..369ba3d --- /dev/null +++ b/index.js @@ -0,0 +1,22 @@ +'use strict'; + +const config = require('./app/config'); +const nconf = require('nconf'); +const path = require('path'); +const fs = require('fs'); +const Proxy = require('./app/Proxy'); +const log = require('./app/log'); +const login = require('./app/login'); +const rigs = require('./app/rigs'); + + +nconf.get('proxy').forEach(listen => { + const proxy = new Proxy(nconf.get('upstream'), login); + proxy.listen(listen) + .then(() => log.info('[app] listen:', listen)); +}); + + +setInterval(() => { + fs.writeFile(path.join(__dirname, `log/report.json`), JSON.stringify(rigs.report(), null, 2), err => {}) +}, 60000); diff --git a/log/.gitignore b/log/.gitignore new file mode 100644 index 0000000..a3a0c8b --- /dev/null +++ b/log/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c83fe4a --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "xmrig-proxy", + "version": "0.1.0", + "description": "XMRig Proxy", + "main": "index.js", + "scripts": { + "start": "bash app.sh start", + "stop": "bash app.sh stop", + "restart": "bash app.sh stop && bash app.sh start", + "update": "bash app.sh update", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/xmrig/xmrig-proxy.git" + }, + "author": "XMRig (https://xmrig.com)", + "license": "MIT", + "bugs": { + "url": "https://github.com/xmrig/xmrig-proxy/issues" + }, + "homepage": "https://github.com/xmrig/xmrig-proxy#readme", + "dependencies": { + "bluebird": "3.5.0", + "nconf": "0.8.4", + "uuid": "3.0.1", + "winston": "2.3.1" + } +}