Files
RootPanel/plugin/shadowsocks/shadowsocks.coffee
2014-11-20 06:06:22 +08:00

261 lines
7.6 KiB
CoffeeScript

{_, child_process, async, fs} = app.libs
{logger, utils, config} = app
{Account, Financials} = app.models
supervisor = require '../supervisor/supervisor'
ShadowsocksPlugin = require './index'
BILLING_BUCKET = config.plugins.shadowsocks?.billing_bucket ? 100 * 1024 * 1024
exports.initSupervisor = (callback) ->
supervisor.programsStatus (program_status) ->
async.each config.plugins.shadowsocks.available_ciphers, (method, callback) ->
program_name = "shadowsocks-#{method}"
if program_name in _.pluck program_status, 'name'
return callback()
exports.writeSupervisorConfigure method, ->
supervisor.updateProgram {}, {program_name: program_name}, ->
callback()
, callback
exports.writeSupervisorConfigure = (method, callback) ->
program_name = "shadowsocks-#{method}"
configure = exports.generateConfigure [],
method: method
filename = "/etc/shadowsocks/#{method}.json"
ShadowsocksPlugin.writeConfigFile filename, configure, {mode: 0o755}, ->
supervisor.writeConfig {username: 'nobody'},
program_name: program_name
command: "ssserver -c #{filename}"
name: program_name
autostart: true
autorestart: true
stdout_logfile: false
, ->
callback()
exports.generateConfigure = (users, options = {}) ->
configure =
server: '0.0.0.0'
local_port: 1080
port_password: {}
timeout: 60
method: options.method ? 'aes-256-cfb'
workers: 2
for user in users
configure.port_password[user.port] = user.password
return JSON.stringify configure
exports.generatePort = (callback) ->
port = 10000 + Math.floor Math.random() * 10000
Account.findOne
'pluggable.shadowsocks.port': port
, (err, result) ->
if result
generatePort callback
else
callback port
exports.queryIptablesInfo = (callback) ->
child_process.exec 'sudo iptables -n -v -L -t filter -x --line-numbers', (err, stdout) ->
lines = stdout.split '\n'
iptables_info = {}
do ->
CHAIN_OUTPUT = 'Chain OUTPUT'
is_chain_output = false
for item in lines
if is_chain_output
if item
try
[num, pkts, bytes, prot, opt, in_, out, source, destination, prot, port] = item.split /\s+/
unless num == 'num'
port = port.match(/spt:(\d+)/)[1]
iptables_info[port.toString()] =
num: parseInt num
pkts: parseInt pkts
bytes: parseInt bytes
port: parseInt port
catch e
continue
if item[ ... CHAIN_OUTPUT.length] == CHAIN_OUTPUT
is_chain_output = true
callback iptables_info
exports.initAccount = (account, callback) ->
exports.generatePort (port) ->
password = utils.randomString 10
Account.findByIdAndUpdate account._id,
$set:
'pluggable.shadowsocks':
port: port
method: _.first config.plugins.shadowsocks.available_ciphers
password: password
pending_traffic: 0
last_traffic_value: 0
, (err, account) ->
logger.error err if err
child_process.exec "sudo iptables -I OUTPUT -p tcp --sport #{port}", ->
child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', ->
exports.updateConfigure ->
callback()
exports.deleteAccount = (account, callback) ->
exports.queryIptablesInfo (iptables_info) ->
{port} = account.pluggable.shadowsocks
billing_traffic = iptables_info[port].bytes - account.pluggable.shadowsocks.last_traffic_value
billing_traffic = iptables_info[port].bytes if billing_traffic < 0
billing_traffic += account.pluggable.shadowsocks.pending_traffic
amount = billing_traffic / BILLING_BUCKET * config.plugins.shadowsocks.price_bucket
account.update
$unset:
'pluggable.shadowsocks': true
$inc:
'billing.balance': -amount
, ->
async.series [
(callback) ->
child_process.exec "sudo iptables -D OUTPUT #{iptables_info[port].num}", callback
(callback) ->
child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', callback
(callback) ->
exports.updateConfigure callback
], ->
if amount > 0
Financials.create
account_id: account._id
type: 'usage_billing'
amount: -amount
payload:
service: 'shadowsocks'
traffic_mb: billing_traffic / (1000 * 1000)
, ->
callback()
else
callback()
exports.accountUsage = (account, callback) ->
Financials.find
account_id: account._id
type: 'usage_billing'
'payload.service': 'shadowsocks'
, (err, financials) ->
time_range =
traffic_24hours: 24 * 3600 * 1000
traffic_7days: 7 * 24 * 3600 * 1000
traffic_30days: 30 * 24 * 3600 * 1000
result = {}
for name, range of time_range
logs = _.filter financials, (i) ->
return i.created_at.getTime() > Date.now() - range
result[name] = _.reduce logs, (memo, i) ->
return memo + i.payload.traffic_mb
, 0
callback result
exports.updateConfigure = (callback) ->
async.eachSeries config.plugins.shadowsocks.available_ciphers, (method, callback) ->
Account.find
'pluggable.shadowsocks.method': method
, (err, accounts) ->
users = _.map accounts, (account) ->
return account.pluggable.shadowsocks
configure = exports.generateConfigure users,
method: method
filename = "/etc/shadowsocks/#{method}.json"
ShadowsocksPlugin.writeConfigFile filename, configure, {mode: 0o755}, ->
supervisor.updateProgram {}, {program_name: "shadowsocks-#{method}"}, ->
supervisor.programControl {}, {program_name: "shadowsocks-#{method}"}, 'restart', ->
callback()
, ->
callback()
exports.monitoring = ->
exports.queryIptablesInfo (iptables_info) ->
async.each _.values(iptables_info), (item, callback) ->
{port, bytes} = item
Account.findOne
'pluggable.shadowsocks.port': port
, (err, account) ->
unless account
return callback()
{pending_traffic, last_traffic_value} = account.pluggable.shadowsocks
new_traffic = bytes - last_traffic_value
if new_traffic < 0
new_traffic = bytes
new_pending_traffic = pending_traffic + new_traffic
billing_bucket = Math.floor pending_traffic / BILLING_BUCKET
new_pending_traffic -= billing_bucket * BILLING_BUCKET
if billing_bucket > 0
amount = billing_bucket * config.plugins.shadowsocks.price_bucket
account.update
$set:
'pluggable.shadowsocks.pending_traffic': new_pending_traffic
'pluggable.shadowsocks.last_traffic_value': bytes
$inc:
'billing.balance': -amount
, (err) ->
logger.error err if err
Financials.create
account_id: account._id
type: 'usage_billing'
amount: -amount
payload:
service: 'shadowsocks'
traffic_mb: (billing_bucket * BILLING_BUCKET) / (1000 * 1000)
, ->
callback()
else if pending_traffic != new_pending_traffic or last_traffic_value != bytes
account.update
$set:
'pluggable.shadowsocks.pending_traffic': new_pending_traffic
'pluggable.shadowsocks.last_traffic_value': bytes
, (err) ->
callback()
else
callback()
, ->