Initial import.

This commit is contained in:
Alexander Sedov
2017-04-17 19:30:08 +03:00
commit a26863c09a
12 changed files with 357 additions and 0 deletions

12
.editorconfig Normal file
View File

@@ -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

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/node_modules
/.idea

41
app.sh Executable file
View File

@@ -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

88
app/Proxy.js Normal file
View File

@@ -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;

24
app/config.js Normal file
View File

@@ -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})();
};

20
app/log.js Normal file
View File

@@ -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;

49
app/login.js Normal file
View File

@@ -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;

47
app/rigs.js Normal file
View File

@@ -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;

21
config/default.json Normal file
View File

@@ -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
}
]
}

22
index.js Normal file
View File

@@ -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);

2
log/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

29
package.json Normal file
View File

@@ -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 <support@xmrig.com> (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"
}
}