diff options
author | Richard Yu <yurichard3839@gmail.com> | 2019-11-04 10:02:03 +0800 |
---|---|---|
committer | Yousong Zhou <yszhou4tech@gmail.com> | 2019-11-04 10:14:15 +0800 |
commit | b83374b3401d33f1f1bc40bbb367991cc34cc918 (patch) | |
tree | 0207830ab0118912fe8d2fa86c35641f4384c925 | |
parent | 4e9f2d3f1ef21262e8c009579d235d355fbd467a (diff) |
luci-app-shadowsocks-libev: port to client side
Signed-off-by: Richard Yu <yurichard3839@gmail.com>
13 files changed, 584 insertions, 620 deletions
diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js new file mode 100644 index 000000000..3aaaa5012 --- /dev/null +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js @@ -0,0 +1,242 @@ +'use strict'; +'require uci'; +'require form'; +'require network'; + +var names_options_server = [ + 'server', + 'server_port', + 'method', + 'key', + 'password', + 'plugin', + 'plugin_opts', +]; + +var names_options_client = [ + 'server', + 'local_address', + 'local_port', +]; + +var names_options_common = [ + 'verbose', + 'ipv6_first', + 'fast_open', + 'no_delay', + 'reuse_port', + 'mode', + 'mtu', + 'timeout', + 'user', +]; + +var modes = [ + 'tcp_only', + 'tcp_and_udp', + 'udp_only', +]; + +var methods = [ + // aead + 'aes-128-gcm', + 'aes-192-gcm', + 'aes-256-gcm', + 'chacha20-ietf-poly1305', + 'xchacha20-ietf-poly1305', + // stream + 'table', + 'rc4', + 'rc4-md5', + 'aes-128-cfb', + 'aes-192-cfb', + 'aes-256-cfb', + 'aes-128-ctr', + 'aes-192-ctr', + 'aes-256-ctr', + 'bf-cfb', + 'camellia-128-cfb', + 'camellia-192-cfb', + 'camellia-256-cfb', + 'salsa20', + 'chacha20', + 'chacha20-ietf', +]; + +function ucival_to_bool(val) { + return val === 'true' || val === '1' || val === 'yes' || val === 'on'; +} + +return L.Class.extend({ + values_actions: function(o) { + o.value('bypass'); + o.value('forward'); + if (o.option !== 'dst_default') { + o.value('checkdst'); + } + }, + values_redir: function(o, xmode) { + uci.sections('shadowsocks-libev', 'ss_redir', function(sdata) { + var disabled = ucival_to_bool(sdata['disabled']), + sname = sdata['.name'], + mode = sdata['mode'] || 'tcp_only'; + if (!disabled && mode.indexOf(xmode) !== -1) { + o.value(sname, sname + ' - ' + mode); + } + }); + o.value('', '<unset>'); + o.default = ''; + }, + values_serverlist: function(o) { + uci.sections('shadowsocks-libev', 'server', function(sdata) { + var sname = sdata['.name'], + server = sdata['server'], + server_port = sdata['server_port']; + if (server && server_port) { + var disabled = ucival_to_bool(sdata['.disabled']) ? ' - disabled' : '', + desc = '%s - %s:%s%s'.format(sname, server, server_port, disabled); + o.value(sname, desc); + } + }); + }, + values_ipaddr: function(o, netDevs) { + netDevs.forEach(function(v) { + v.getIPAddrs().forEach(function(a) { + var host = a.split('/')[0]; + o.value(host, '%s (%s)'.format(host, v.getShortName())); + }); + }); + }, + options_client: function(s, tab, netDevs) { + var o = s.taboption(tab, form.ListValue, 'server', _('Remote server')); + this.values_serverlist(o); + o = s.taboption(tab, form.Value, 'local_address', _('Local address')); + o.datatype = 'ipaddr'; + o.placeholder = '0.0.0.0'; + this.values_ipaddr(o, netDevs); + o = s.taboption(tab, form.Value, 'local_port', _('Local port')); + o.datatype = 'port'; + }, + options_server: function(s, opts) { + var o, optfunc, + tab = opts && opts.tab || null; + + if (!tab) { + optfunc = function(/* ... */) { + var o = s.option.apply(s, arguments); + o.editable = true; + return o; + }; + } else { + optfunc = function(/* ... */) { + var o = s.taboption.apply(s, L.varargs(arguments, 0, tab)); + o.editable = true; + return o; + }; + } + + o = optfunc(form.Value, 'server', _('Server')); + o.datatype = 'host'; + o.size = 16; + + o = optfunc(form.Value, 'server_port', _('Server port')); + o.datatype = 'port'; + o.size = 5; + + o = optfunc(form.ListValue, 'method', _('Method')); + methods.forEach(function(m) { + o.value(m); + }); + + o = optfunc(form.Value, 'password', _('Password')); + o.password = true; + o.size = 12; + + o = optfunc(form.Value, 'key', _('Key (base64)')); + o.datatype = 'base64'; + o.password = true; + o.size = 12; + o.modalonly = true;; + + optfunc(form.Value, 'plugin', _('Plugin')).modalonly = true; + + optfunc(form.Value, 'plugin_opts', _('Plugin Options')).modalonly = true; + }, + options_common: function(s, tab) { + var o = s.taboption(tab, form.ListValue, 'mode', _('Mode of operation')); + modes.forEach(function(m) { + o.value(m); + }); + o.default = 'tcp_and_udp'; + o = s.taboption(tab, form.Value, 'mtu', _('MTU')); + o.datatype = 'uinteger'; + o = s.taboption(tab, form.Value, 'timeout', _('Timeout (sec)')); + o.datatype = 'uinteger'; + s.taboption(tab, form.Value, 'user', _('Run as')); + + s.taboption(tab, form.Flag, 'verbose', _('Verbose')); + s.taboption(tab, form.Flag, 'ipv6_first', _('IPv6 First'), _('Prefer IPv6 addresses when resolving names')); + s.taboption(tab, form.Flag, 'fast_open', _('Enable TCP Fast Open')); + s.taboption(tab, form.Flag, 'no_delay', _('Enable TCP_NODELAY')); + s.taboption(tab, form.Flag, 'reuse_port', _('Enable SO_REUSEPORT')); + }, + ucival_to_bool: function(val) { + return ucival_to_bool(val); + }, + cfgvalue_overview: function(sdata) { + var stype = sdata['.type'], + lines = []; + + if (stype === 'ss_server') { + this.cfgvalue_overview_(sdata, lines, names_options_server); + this.cfgvalue_overview_(sdata, lines, names_options_common); + this.cfgvalue_overview_(sdata, lines, ['bind_address']); + } else if (stype === 'ss_local' || stype === 'ss_redir' || stype === 'ss_tunnel') { + this.cfgvalue_overview_(sdata, lines, names_options_client); + if (stype === 'ss_tunnel') { + this.cfgvalue_overview_(sdata, lines, ['tunnel_address']); + } + this.cfgvalue_overview_(sdata, lines, names_options_common); + } else { + return []; + } + + return lines; + }, + cfgvalue_overview_: function(sdata, lines, names) { + names.forEach(function(n) { + var v = sdata[n]; + if (v) { + if (n === 'key' || n === 'password') { + v = _('<hidden>'); + } + var fv = E('var', [v]); + if (sdata['.type'] !== 'ss_server' && n === 'server') { + fv = E('a', { + class: 'label', + href: L.url('admin/services/shadowsocks-libev/servers') + '#edit=' + v, + target: '_blank', + rel: 'noopener' + }, fv); + } + lines.push(n + ': ', fv, E('br')); + } + }); + }, + option_install_package: function(s, tab) { + var bin = s.sectiontype.replace('_', '-'), + opkg_package = 'shadowsocks-libev-' + bin, o; + if (tab) { + o = s.taboption(tab, form.Button, '_install'); + } else { + o = s.option(form.Button, '_install'); + } + o.title = _('Package is not installed'); + o.inputtitle = _('Install package ' + opkg_package); + o.inputstyle = 'apply'; + o.onclick = function() { + window.open(L.url('admin/system/opkg') + + '?query=' + opkg_package, '_blank', 'noopener'); + }; + } +}); diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/instances.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/instances.js new file mode 100644 index 000000000..27a2b950c --- /dev/null +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/instances.js @@ -0,0 +1,162 @@ +'use strict'; +'require form'; +'require uci'; +'require fs'; +'require network'; +'require rpc'; +'require shadowsocks-libev as ss'; + +var conf = 'shadowsocks-libev'; +var cfgtypes = ['ss_local', 'ss_redir', 'ss_server', 'ss_tunnel']; + +var callServiceList = rpc.declare({ + object: 'service', + method: 'list', + params: [ 'name' ], + expect: { '': {} } +}); + +return L.view.extend({ + render: function(stats) { + var m, s, o; + + m = new form.Map(conf, + _('Local Instances'), + _('Instances of shadowsocks-libev components, e.g. ss-local, \ + ss-redir, ss-tunnel, ss-server, etc. To enable an instance it \ + is required to enable both the instance itself and the remote \ + server it refers to.')); + + s = m.section(form.GridSection); + s.addremove = true; + s.cfgsections = function() { + return this.map.data.sections(this.map.config) + .filter(function(s) { return cfgtypes.indexOf(s['.type']) !== -1; }) + .map(function(s) { return s['.name']; }); + }; + s.sectiontitle = function(section_id) { + var s = uci.get(conf, section_id); + return (s ? s['.type'] + '.' : '') + section_id; + }; + s.renderSectionAdd = function(extra_class) { + var el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments), + optionEl = [E('option', { value: '_dummy' }, [_('-- instance type --')])]; + cfgtypes.forEach(function(t) { + optionEl.push(E('option', { value: t }, [t.replace('_', '-')])); + }); + var selectEl = E('select', { + class: 'cbi-input-select', + change: function(ev) { + ev.target.parentElement.nextElementSibling.nextElementSibling + .toggleAttribute('disabled', ev.target.value === '_dummy'); + } + }, optionEl); + el.lastElementChild.setAttribute('disabled', ''); + el.prepend(E('div', {}, selectEl)); + return el; + }; + s.handleAdd = function(ev, name) { + var selectEl = ev.target.parentElement.firstElementChild.firstElementChild, + type = selectEl.value; + this.sectiontype = type; + var promise = form.GridSection.prototype.handleAdd.apply(this, arguments); + this.sectiontype = undefined; + return promise; + }; + s.addModalOptions = function(s, section_id, ev) { + var sdata = uci.get(conf, section_id), + stype = sdata ? sdata['.type'] : null; + if (stype) { + s.sectiontype = stype; + return Promise.all([ + L.resolveDefault(fs.stat('/usr/bin/' + stype.replace('_', '-')), null), + network.getDevices() + ]).then(L.bind(function(res) { + s.tab('general', _('General Settings')); + s.tab('advanced', _('Advanced Settings')); + s.taboption('general', form.Flag, 'disabled', _('Disable')); + if (!res[0]) { + ss.option_install_package(s, 'general'); + } + ss.options_common(s, 'advanced'); + + if (stype === 'ss_server') { + ss.options_server(s, { tab: 'general' }); + o = s.taboption('general', form.Value, 'bind_address', + _('Bind address'), + _('The address ss-server will initiate connection from')); + o.datatype = 'ipaddr'; + o.placeholder = '0.0.0.0'; + ss.values_ipaddr(o, res[1]); + } else { + ss.options_client(s, 'general', res[1]); + if (stype === 'ss_tunnel') { + o = s.taboption('general', form.Value, 'tunnel_address', + _('Tunnel address'), + _('The address ss-tunnel will forward traffic to')); + o.datatype = 'hostport'; + } + } + }, this)); + } + }; + + o = s.option(form.DummyValue, 'overview', _('Overview')); + o.modalonly = false; + o.editable = true; + o.rawhtml = true; + o.renderWidget = function(section_id, option_index, cfgvalue) { + var sdata = uci.get(conf, section_id); + if (sdata) { + return form.DummyValue.prototype.renderWidget.call(this, section_id, option_index, ss.cfgvalue_overview(sdata)); + } + return null; + }; + + o = s.option(form.DummyValue, 'running', _('Running')); + o.modalonly = false; + o.editable = true; + o.default = ''; + + o = s.option(form.Button, 'disabled', _('Enable/Disable')); + o.modalonly = false; + o.editable = true; + o.inputtitle = function(section_id) { + var s = uci.get(conf, section_id); + if (ss.ucival_to_bool(s['disabled'])) { + this.inputstyle = 'reset'; + return _('Disabled'); + } + this.inputstyle = 'save'; + return _('Enabled'); + } + o.onclick = function(ev) { + var inputEl = ev.target.parentElement.nextElementSibling; + inputEl.value = ss.ucival_to_bool(inputEl.value) ? '0' : '1'; + return this.map.save(); + } + + return m.render().finally(function() { + L.Poll.add(function() { + return L.resolveDefault(callServiceList(conf), {}) + .then(function(res) { + var instances = null; + try { + instances = res[conf]['instances']; + } catch (e) {} + if (!instances) return; + uci.sections(conf) + .filter(function(s) { return cfgtypes.indexOf(s['.type']) !== -1; }) + .forEach(function(s) { + var el = document.getElementById('cbi-shadowsocks-libev-' + s['.name'] + '-running'); + if (el) { + var name = s['.type'] + '.' + s['.name'], + running = instances.hasOwnProperty(name)? instances[name].running : false; + el.innerText = running ? 'yes' : 'no'; + } + }); + }); + }); + }); + }, +}); diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/rules.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/rules.js new file mode 100644 index 000000000..798237adb --- /dev/null +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/rules.js @@ -0,0 +1,123 @@ +'use strict'; +'require uci'; +'require fs'; +'require form'; +'require tools.widgets as widgets'; +'require shadowsocks-libev as ss'; + +var conf = 'shadowsocks-libev'; + +function src_dst_option(s /*, ... */) { + var o = s.taboption.apply(s, L.varargs(arguments, 1)); + o.datatype = 'or(ipaddr,cidr)'; +} + +return L.view.extend({ + load: function() { + return Promise.all([ + L.resolveDefault(fs.stat('/usr/lib/iptables/libxt_recent.so'), {}), + L.resolveDefault(fs.stat('/usr/bin/ss-rules'), null), + uci.load(conf).then(function() { + if (!uci.get_first(conf, 'ss_rules')) { + uci.set(conf, uci.add(conf, 'ss_rules', 'ss_rules'), 'disabled', '1'); + } + }) + ]); + }, + render: function(stats) { + var m, s, o; + + m = new form.Map(conf, _('Redir Rules'), + _('On this page you can configure how traffics are to be \ + forwarded to ss-redir instances. \ + If enabled, packets will first have their src ip addresses checked \ + against <em>Src ip/net bypass</em>, <em>Src ip/net forward</em>, \ + <em>Src ip/net checkdst</em> and if none matches <em>Src default</em> \ + will give the default action to be taken. \ + If the prior check results in action <em>checkdst</em>, packets will continue \ + to have their dst addresses checked.')); + + s = m.section(form.NamedSection, 'ss_rules', 'ss_rules'); + s.tab('general', _('General Settings')); + s.tab('src', _('Source Settings')); + s.tab('dst', _('Destination Settings')); + + s.taboption('general', form.Flag, 'disabled', _('Disable')); + if (!stats[1]) { + ss.option_install_package(s, 'general'); + } + + o = s.taboption('general', form.ListValue, 'redir_tcp', + _('ss-redir for TCP')); + ss.values_redir(o, 'tcp'); + o = s.taboption('general', form.ListValue, 'redir_udp', + _('ss-redir for UDP')); + ss.values_redir(o, 'udp'); + + o = s.taboption('general', form.ListValue, 'local_default', + _('Local-out default'), + _('Default action for locally generated TCP packets')); + ss.values_actions(o); + o = s.taboption('general', widgets.DeviceSelect, 'ifnames', + _('Ingress interfaces'), + _('Only apply rules on packets from these network interfaces')); + o.multiple = true; + o.noaliases = true; + o.noinactive = true; + s.taboption('general', form.Value, 'ipt_args', + _('Extra arguments'), + _('Passes additional arguments to iptables. Use with care!')); + + src_dst_option(s, 'src', form.DynamicList, 'src_ips_bypass', + _('Src ip/net bypass'), + _('Bypass ss-redir for packets with src address in this list')); + src_dst_option(s, 'src', form.DynamicList, 'src_ips_forward', + _('Src ip/net forward'), + _('Forward through ss-redir for packets with src address in this list')); + src_dst_option(s, 'src', form.DynamicList, 'src_ips_checkdst', + _('Src ip/net checkdst'), + _('Continue to have dst address checked for packets with src address in this list')); + o = s.taboption('src', form.ListValue, 'src_default', + _('Src default'), + _('Default action for packets whose src address do not match any of the src ip/net list')); + ss.values_actions(o); + + src_dst_option(s, 'dst', form.DynamicList, 'dst_ips_bypass', + _('Dst ip/net bypass'), + _('Bypass ss-redir for packets with dst address in this list')); + src_dst_option(s, 'dst', form.DynamicList, 'dst_ips_forward', + _('Dst ip/net forward'), + _('Forward through ss-redir for packets with dst address in this list')); + + var dir = '/etc/shadowsocks-libev'; + o = s.taboption('dst', form.FileUpload, 'dst_ips_bypass_file', + _('Dst ip/net bypass file'), + _('File containing ip/net for the purposes as with <em>Dst ip/net bypass</em>')); + o.root_directory = dir; + o = s.taboption('dst', form.FileUpload, 'dst_ips_forward_file', + _('Dst ip/net forward file'), + _('File containing ip/net for the purposes as with <em>Dst ip/net forward</em>')); + o.root_directory = dir; + o = s.taboption('dst', form.ListValue, 'dst_default', + _('Dst default'), + _('Default action for packets whose dst address do not match any of the dst ip list')); + ss.values_actions(o); + + if (stats[0].type === 'file') { + o = s.taboption('dst', form.Flag, 'dst_forward_recentrst'); + } else { + uci.set(conf, 'ss_rules', 'dst_forward_recentrst', '0'); + o = s.taboption('dst', form.Button, '_install'); + o.inputtitle = _('Install package iptables-mod-conntrack-extra'); + o.inputstyle = 'apply'; + o.onclick = function() { + window.open(L.url('admin/system/opkg') + + '?query=iptables-mod-conntrack-extra', '_blank', 'noopener'); + } + } + o.title = _('Forward recentrst'); + o.description = _('Forward those packets whose dst have recently sent to us multiple tcp-rst'); + + return m.render(); + }, +}); diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js new file mode 100644 index 000000000..d46bfb0aa --- /dev/null +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js @@ -0,0 +1,37 @@ +'use strict'; +'require form'; +'require shadowsocks-libev as ss'; + +function startsWith(str, search) { + return str.substring(0, search.length) === search; +} + +return L.view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('shadowsocks-libev', _('Remote Servers'), + _('Definition of remote shadowsocks servers. \ + Disable any of them will also disable instances referring to it.')); + + s = m.section(form.GridSection, 'server'); + s.addremove = true; + + o = s.option(form.Flag, 'disabled', _('Disable')); + o.editable = true; + + ss.options_server(s); + + return m.render(); + }, + addFooter: function() { + var p = '#edit='; + if (startsWith(location.hash, p)) { + var section_id = location.hash.substring(p.length); + var editBtn = document.querySelector('#cbi-shadowsocks-libev-' + section_id + ' button.cbi-button-edit'); + if (editBtn) + editBtn.click(); + } + return this.super('addFooter', arguments); + } +}); diff --git a/applications/luci-app-shadowsocks-libev/luasrc/controller/shadowsocks-libev.lua b/applications/luci-app-shadowsocks-libev/luasrc/controller/shadowsocks-libev.lua index 05d12e38b..c8a9d6636 100644 --- a/applications/luci-app-shadowsocks-libev/luasrc/controller/shadowsocks-libev.lua +++ b/applications/luci-app-shadowsocks-libev/luasrc/controller/shadowsocks-libev.lua @@ -9,25 +9,14 @@ function index() _("Shadowsocks-libev"), 59) entry({"admin", "services", "shadowsocks-libev", "instances"}, - arcombine(cbi("shadowsocks-libev/instances"), cbi("shadowsocks-libev/instance-details")), + view("shadowsocks-libev/instances"), _("Local Instances"), 10).leaf = true entry({"admin", "services", "shadowsocks-libev", "servers"}, - cbi("shadowsocks-libev/servers"), + view("shadowsocks-libev/servers"), _("Remote Servers"), 20).leaf = true entry({"admin", "services", "shadowsocks-libev", "rules"}, - cbi("shadowsocks-libev/rules"), + view("shadowsocks-libev/rules"), _("Redir Rules"), 30).leaf = true - - entry({"admin", "services", "shadowsocks-libev", "status"}, call("ss_status"), nil).leaf = true - -end - -function ss_status() - local ut = require "luci.util" - local rv = ut.ubus("service", "list", {name = "shadowsocks-libev"})["shadowsocks-libev"] or {_=0} - - luci.http.prepare_content("application/json") - luci.http.write_json(rv) end diff --git a/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instance-details.lua b/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instance-details.lua deleted file mode 100644 index c6323b4df..000000000 --- a/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instance-details.lua +++ /dev/null @@ -1,47 +0,0 @@ --- Copyright 2017 Yousong Zhou <yszhou4tech@gmail.com> --- Licensed to the public under the Apache License 2.0. - -local ds = require "luci.dispatcher" -local ss = require "luci.model.shadowsocks-libev" - -local sname = arg[1] -local redirect_url = ds.build_url("admin/services/shadowsocks-libev/instances") -local s, o - -local m = Map("shadowsocks-libev") -local sdata = m:get(sname) -if not sdata then - luci.http.redirect(redirect_url) - return -end -local stype = sdata[".type"] -m.redirect = redirect_url -m.title = "shadowsocks-libev - %s - %s" % {stype, sname} - - -s = m:section(NamedSection, sname, stype) -s:tab("general", translate("General Settings")) -s:tab("advanced", translate("Advanced Settings")) -s:taboption("general", Flag, "disabled", translate("Disable")) -ss.option_install_package(s, "general") -ss.options_common(s, "advanced") - -if stype == "ss_server" then - ss.options_server(s, {tab="general"}) - o = s:taboption("general", Value, "bind_address", - translate("Bind address"), - translate("The address ss-server will initiate connection from")) - o.datatype = "ipaddr" - o.placeholder = "0.0.0.0" - ss.values_ipaddr(o) -else - ss.options_client(s, "general") - if stype == "ss_tunnel" then - o = s:taboption("general", Value, "tunnel_address", - translate("Tunnel address"), - translate("The address ss-tunnel will forward traffic to")) - o.datatype = "hostport" - end -end - -return m diff --git a/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instances.lua b/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instances.lua deleted file mode 100644 index 62a90fb41..000000000 --- a/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instances.lua +++ /dev/null @@ -1,104 +0,0 @@ --- Copyright 2017 Yousong Zhou <yszhou4tech@gmail.com> --- Licensed to the public under the Apache License 2.0. - -local ds = require "luci.dispatcher" -local ss = require "luci.model.shadowsocks-libev" -local ut = require "luci.util" -local m, s, o - -m = Map("shadowsocks-libev", - translate("Local Instances"), - translate("Instances of shadowsocks-libev components, e.g. ss-local, \ - ss-redir, ss-tunnel, ss-server, etc. To enable an instance it \ - is required to enable both the instance itself and the remote \ - server it refers to.")) - -local instances = {} -local cfgtypes = { "ss_local", "ss_redir", "ss_server", "ss_tunnel" } - -for sname, sdata in pairs(m:get()) do - local key, value = ss.cfgvalue_overview(sdata) - if key ~= nil then - instances[key] = value - end -end - -s = m:section(Table, instances) -s.addremove = true -s.template_addremove = "shadowsocks-libev/add_instance" -s.extedit = function(self, section) - local value = instances[section] - if type(value) == "table" then - return ds.build_url(unpack(ds.context.requestpath), - "services/shadowsocks-libev/instances", - value[".name"]) - end -end -s.parse = function(self, ...) - Table.parse(self, ...) - - local crval = REMOVE_PREFIX .. self.config - local name = self.map:formvaluetable(crval) - for k,v in pairs(name) do - local value = instances[k] - local sname = value[".name"] - if type(value) == "table" then - m:del(sname) - instances[k] = nil - for _, oname in ipairs({"redir_tcp", "redir_udp"}) do - local ovalue = m:get("ss_rules", oname) - if ovalue == sname then - m:del("ss_rules", oname) - end - end - end - end - - local stype = m:formvalue("_newinst.type") - local sname = m:formvalue("_newinst.name") - if ut.contains(cfgtypes, stype) then - local created - if sname and #sname > 0 then - created = m:set(sname, nil, stype) - else - created = m:add(stype) - sname = created - end - if created then - m.uci:save("shadowsocks-libev") - luci.http.redirect(ds.build_url( - "admin/services/shadowsocks-libev/instances", sname - )) - end - end -end - -o = s:option(DummyValue, "name", translate("Name")) -o.rawhtml = true -o = s:option(DummyValue, "overview", translate("Overview")) -o.rawhtml = true - -s:option(DummyValue, "running", translate("Running")) - -o = s:option(Button, "disabled", translate("Enable/Disable")) -o.render = function(self, section, scope) - if instances[section].disabled then - self.title = translate("Disabled") - self.inputstyle = "reset" - else - self.title = translate("Enabled") - self.inputstyle = "save" - end - Button.render(self, section, scope) -end -o.write = function(self, section) - local sdata = instances[section] - if type(sdata) == "table" then - local sname = sdata[".name"] - local disabled = not sdata["disabled"] - sdata["disabled"] = disabled - m:set(sname, "disabled", tostring(disabled)) - end -end - -return m diff --git a/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/rules.lua b/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/rules.lua deleted file mode 100644 index e4d1a2745..000000000 --- a/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/rules.lua +++ /dev/null @@ -1,109 +0,0 @@ --- Copyright 2017 Yousong Zhou <yszhou4tech@gmail.com> --- Licensed to the public under the Apache License 2.0. - -local ss = require("luci.model.shadowsocks-libev") - -local m, s, o - -m = Map("shadowsocks-libev", - translate("Redir Rules"), - translate("On this page you can configure how traffics are to be \ - forwarded to ss-redir instances. \ - If enabled, packets will first have their src ip addresses checked \ - against <em>Src ip/net bypass</em>, <em>Src ip/net forward</em>, \ - <em>Src ip/net checkdst</em> and if none matches <em>Src default</em> \ - will give the default action to be taken. \ - If the prior check results in action <em>checkdst</em>, packets will continue \ - to have their dst addresses checked.")) - -local sdata = m:get('ss_rules') -if not sdata then - m:set('ss_rules', nil, 'ss_rules') - m:set('ss_rules', 'disabled', "1") -end - -function src_dst_option(s, ...) - local o = s:taboption(...) - o.datatype = "or(ipaddr,cidr)" -end - -s = m:section(NamedSection, "ss_rules", "ss_rules") -s:tab("general", translate("General Settings")) -s:tab("src", translate("Source Settings")) -s:tab("dst", translate("Destination Settings")) - -s:taboption('general', Flag, "disabled", translate("Disable")) -ss.option_install_package(s, 'general') - -o = s:taboption('general', ListValue, "redir_tcp", - translate("ss-redir for TCP")) -ss.values_redir(o, 'tcp') -o = s:taboption('general', ListValue, "redir_udp", - translate("ss-redir for UDP")) -ss.values_redir(o, 'udp') - -o = s:taboption('general', ListValue, "local_default", - translate("Local-out default"), - translate("Default action for locally generated TCP packets")) -ss.values_actions(o) -o = s:taboption('general', DynamicList, "ifnames", - translate("Ingress interfaces"), - translate("Only apply rules on packets from these network interfaces")) -ss.values_ifnames(o) -s:taboption('general', Value, "ipt_args", - translate("Extra arguments"), - translate("Passes additional arguments to iptables. Use with care!")) - -src_dst_option(s, 'src', DynamicList, "src_ips_bypass", - translate("Src ip/net bypass"), - translate("Bypass ss-redir for packets with src address in this list")) -src_dst_option(s, 'src', DynamicList, "src_ips_forward", - translate("Src ip/net forward"), - translate("Forward through ss-redir for packets with src address in this list")) -src_dst_option(s, 'src', DynamicList, "src_ips_checkdst", - translate("Src ip/net checkdst"), - translate("Continue to have dst address checked for packets with src address in this list")) -o = s:taboption('src', ListValue, "src_default", - translate("Src default"), - translate("Default action for packets whose src address do not match any of the src ip/net list")) -ss.values_actions(o) - -src_dst_option(s, 'dst', DynamicList, "dst_ips_bypass", - translate("Dst ip/net bypass"), - translate("Bypass ss-redir for packets with dst address in this list")) -src_dst_option(s, 'dst', DynamicList, "dst_ips_forward", - translate("Dst ip/net forward"), - translate("Forward through ss-redir for packets with dst address in this list")) - -o = s:taboption('dst', FileBrowser, "dst_ips_bypass_file", - translate("Dst ip/net bypass file"), - translate("File containing ip/net for the purposes as with <em>Dst ip/net bypass</em>")) -o.datatype = "file" -s:taboption('dst', FileBrowser, "dst_ips_forward_file", - translate("Dst ip/net forward file"), - translate("File containing ip/net for the purposes as with <em>Dst ip/net forward</em>")) -o.datatype = "file" -o = s:taboption('dst', ListValue, "dst_default", - translate("Dst default"), - translate("Default action for packets whose dst address do not match any of the dst ip list")) -ss.values_actions(o) - -local installed = os.execute("iptables -m recent -h &>/dev/null") == 0 -if installed then - o = s:taboption('dst', Flag, "dst_forward_recentrst") -else - m:set('ss_rules', 'dst_forward_recentrst', "0") - o = s:taboption("dst", Button, "_install") - o.inputtitle = translate("Install package iptables-mod-conntrack-extra") - o.inputstyle = "apply" - o.write = function() - return luci.http.redirect( - luci.dispatcher.build_url("admin/system/opkg") .. - "?query=iptables-mod-conntrack-extra" - ) - end -end -o.title = translate("Forward recentrst") -o.description = translate("Forward those packets whose dst have recently sent to us multiple tcp-rst") - -return m diff --git a/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/servers.lua b/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/servers.lua deleted file mode 100644 index ec601c4fe..000000000 --- a/applications/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/servers.lua +++ /dev/null @@ -1,36 +0,0 @@ --- Copyright 2017 Yousong Zhou <yszhou4tech@gmail.com> --- Licensed to the public under the Apache License 2.0. - -local ds = require "luci.dispatcher" -local ss = require("luci.model.shadowsocks-libev") - -local m, s - -m = Map("shadowsocks-libev", - translate("Remote Servers"), - translate("Definition of remote shadowsocks servers. \ - Disable any of them will also disable instances referring to it.")) - -local sname = arg[1] -if sname then - if not m:get(sname) then - luci.http.redirect(ds.build_url("admin/services/shadowsocks-libev/servers")) - return - end - s = m:section(NamedSection, sname, "server") - m.title = m.title .. ' - ' .. sname - opts = {} -else - s = m:section(TypedSection, "server") - s.template = 'cbi/tblsection' - s.addremove = true - s.extedit = function(self, section) - return 'servers/' .. section - end - opts = {row=true} -end - -s:option(Flag, "disabled", translate("Disable")) -ss.options_server(s, opts) - -return m diff --git a/applications/luci-app-shadowsocks-libev/luasrc/model/shadowsocks-libev.lua b/applications/luci-app-shadowsocks-libev/luasrc/model/shadowsocks-libev.lua deleted file mode 100644 index abb6ce8d4..000000000 --- a/applications/luci-app-shadowsocks-libev/luasrc/model/shadowsocks-libev.lua +++ /dev/null @@ -1,272 +0,0 @@ --- Copyright 2017 Yousong Zhou <yszhou4tech@gmail.com> --- Licensed to the public under the Apache License 2.0. - -local _up = getfenv(3) -local ut = require("luci.util") -local sys = require("luci.sys") -local ds = require("luci.dispatcher") -local nw = require("luci.model.network") -nw.init() -module("luci.model.shadowsocks-libev", function(m) - setmetatable(m, {__index=function (self, k) - local tb = _up - return rawget(self, k) or _up[k] - end}) -end) - -function values_actions(o) - o:value("bypass") - o:value("forward") - if o.option ~= "dst_default" then - o:value("checkdst") - end -end - -function values_redir(o, xmode) - o.map.uci:foreach("shadowsocks-libev", "ss_redir", function(sdata) - local disabled = ucival_to_bool(sdata["disabled"]) - local sname = sdata[".name"] - local mode = sdata["mode"] or "tcp_only" - if not disabled and mode:find(xmode) then - local desc = "%s - %s" % {sname, mode} - o:value(sname, desc) - end - end) - o:value("", "<unset>") - o.default = "" -end - -function values_serverlist(o) - o.map.uci:foreach("shadowsocks-libev", "server", function(sdata) - local sname = sdata[".name"] - local server = sdata["server"] - local server_port = sdata["server_port"] - if server and server_port then - local disabled = ucival_to_bool(sdata[".disabled"]) and " - disabled" or "" - local desc = "%s - %s:%s%s" % {sname, server, server_port, disabled} - o:value(sname, desc) - end - end) -end - -function values_ipaddr(o) - for _, v in ipairs(nw:get_interfaces()) do - for _, a in ipairs(v:ipaddrs()) do - o:value(a:host():string(), '%s (%s)' %{ a:host(), v:shortname() }) - end - end -end - -function values_ifnames(o) - for _, v in ipairs(sys.net.devices()) do - o:value(v) - end -end - -function options_client(s, tab) - local o - - o = s:taboption(tab, ListValue, "server", translate("Remote server")) - values_serverlist(o) - o = s:taboption(tab, Value, "local_address", translate("Local address")) - o.datatype = "ipaddr" - o.placeholder = "0.0.0.0" - values_ipaddr(o) - o = s:taboption(tab, Value, "local_port", translate("Local port")) - o.datatype = "port" -end - -function options_server(s, opts) - local o - local optfunc - local tab = opts and opts.tab or nil - local row = opts and opts.row or false - - if tab == nil then - optfunc = function(...) return s:option(...) end - else - optfunc = function(...) return s:taboption(tab, ...) end - end - - o = optfunc(Value, "server", translate("Server")) - o.datatype = "host" - o.size = 16 - o = optfunc(Value, "server_port", translate("Server port")) - o.datatype = "port" - o.size = 5 - o = optfunc(ListValue, "method", translate("Method")) - for _, m in ipairs(methods) do - o:value(m) - end - o = optfunc(Value, "password", translate("Password")) - o.password = true - o.size = 12 - if not row then - o = optfunc(Value, "key", translate("Key (base64)")) - o.datatype = "base64" - o.password = true - o.size = 12 - optfunc(Value, "plugin", translate("Plugin")) - optfunc(Value, "plugin_opts", translate("Plugin Options")) - end -end - -function options_common(s, tab) - local o - - o = s:taboption(tab, ListValue, "mode", translate("Mode of operation")) - for _, m in ipairs(modes) do - o:value(m) - end - o.default = "tcp_and_udp" - o = s:taboption(tab, Value, "mtu", translate("MTU")) - o.datatype = "uinteger" - o = s:taboption(tab, Value, "timeout", translate("Timeout (sec)")) - o.datatype = "uinteger" - s:taboption(tab, Value, "user", translate("Run as")) - - s:taboption(tab, Flag, "verbose", translate("Verbose")) - s:taboption(tab, Flag, "ipv6_first", translate("IPv6 First"), translate("Prefer IPv6 addresses when resolving names")) - s:taboption(tab, Flag, "fast_open", translate("Enable TCP Fast Open")) - s:taboption(tab, Flag, "no_delay", translate("Enable TCP_NODELAY")) - s:taboption(tab, Flag, "reuse_port", translate("Enable SO_REUSEPORT")) -end - -function ucival_to_bool(val) - return val == "true" or val == "1" or val == "yes" or val == "on" -end - -function cfgvalue_overview(sdata) - local stype = sdata[".type"] - local lines = {} - - if stype == "ss_server" then - cfgvalue_overview_(sdata, lines, names_options_server) - cfgvalue_overview_(sdata, lines, names_options_common) - cfgvalue_overview_(sdata, lines, { - "bind_address", - }) - elseif stype == "ss_local" or stype == "ss_redir" or stype == "ss_tunnel" then - cfgvalue_overview_(sdata, lines, names_options_client) - if stype == "ss_tunnel" then - cfgvalue_overview_(sdata, lines, {"tunnel_address"}) - end - cfgvalue_overview_(sdata, lines, names_options_common) - else - return nil, nil - end - local sname = sdata[".name"] - local key = "%s.%s" % {stype, sname} - local value = { - [".name"] = sname, - name = '%s.<var>%s</var>' % {stype, sname}, - overview = table.concat(lines, "<br />"), - disabled = ucival_to_bool(sdata["disabled"]), - } - return key, value -end - -function cfgvalue_overview_(sdata, lines, names) - local line - - for _, n in ipairs(names) do - local v = sdata[n] - if v ~= nil then - if n == "key" or n == "password" then - v = translate("<hidden>") - end - local fv = "<var>%s</var>" % ut.pcdata(v) - if sdata[".type"] ~= "ss_server" and n == "server" then - fv = '<a class="label" href="%s">%s</a>' % { - ds.build_url("admin/services/shadowsocks-libev/servers", v), fv} - end - line = n .. ": " .. fv - table.insert(lines, line) - end - end -end - -function option_install_package(s, tab) - local bin = s.sectiontype:gsub("_", "-", 1) - local installed = nixio.fs.access("/usr/bin/" .. bin) - if installed then - return - end - local opkg_package = "shadowsocks-libev-" .. bin - local p_install - if tab then - p_install = s:taboption(tab, Button, "_install") - else - p_install = s:option(Button, "_install") - end - p_install.title = translate("Package is not installed") - p_install.inputtitle = translate("Install package %q" % opkg_package) - p_install.inputstyle = "apply" - - function p_install.write() - return luci.http.redirect( - luci.dispatcher.build_url("admin/system/opkg") .. - "?query=%s" % opkg_package - ) - end -end - -names_options_server = { - "server", - "server_port", - "method", - "key", - "password", - "plugin", - "plugin_opts", -} - -names_options_client = { - "server", - "local_address", - "local_port", -} - -names_options_common = { - "verbose", - "ipv6_first", - "fast_open", - "no_delay", - "reuse_port", - "mode", - "mtu", - "timeout", - "user", -} - -modes = { - "tcp_only", - "tcp_and_udp", - "udp_only", -} - -methods = { - -- aead - "aes-128-gcm", - "aes-192-gcm", - "aes-256-gcm", - "chacha20-ietf-poly1305", - "xchacha20-ietf-poly1305", - -- stream - "table", - "rc4", - "rc4-md5", - "aes-128-cfb", - "aes-192-cfb", - "aes-256-cfb", - "aes-128-ctr", - "aes-192-ctr", - "aes-256-ctr", - "bf-cfb", - "camellia-128-cfb", - "camellia-192-cfb", - "camellia-256-cfb", - "salsa20", - "chacha20", - "chacha20-ietf", -} diff --git a/applications/luci-app-shadowsocks-libev/luasrc/view/shadowsocks-libev/add_instance.htm b/applications/luci-app-shadowsocks-libev/luasrc/view/shadowsocks-libev/add_instance.htm deleted file mode 100644 index 27e101a4d..000000000 --- a/applications/luci-app-shadowsocks-libev/luasrc/view/shadowsocks-libev/add_instance.htm +++ /dev/null @@ -1,38 +0,0 @@ -<div class="cbi-section-create cbi-tblsection-create"> - <div> - <select class="cbi-input-select" id="_newinst.type" name="_newinst.type"> - <option value="_dummy">-- instance type --</option> - <option value="ss_local">ss-local</option> - <option value="ss_tunnel">ss-tunnel</option> - <option value="ss_redir">ss-redir</option> - <option value="ss_server">ss-server</option> - </select> - </div> - <div> - <input type="text" class="cbi-input-text" id="_newinst.name" name="_newinst.name" placeholder="<%:Name%>"/> - </div> - <input type="submit" class="cbi-button cbi-button-add" name="cbi.cts.<%=self.config%>" value="<%:Add%>" /> -</div> -<script type="text/javascript">//<![CDATA[ - XHR.poll(-1, '<%=url('admin/services/shadowsocks-libev/status')%>', null, - function(x, st) - { - var names = [ - <%- - for _, name in ipairs(self:cfgsections()) do - write("%q," % name) - end - -%> - ]; - var instances = st["instances"] || {}; - for (var i = 0, len = names.length; i < len; i++) { - var name = names[i]; - var el = document.getElementById('cbi-table-' + name + '-running'); - if (el) { - var running = instances.hasOwnProperty(name)? instances[name].running : false; - el.innerText = running ? 'yes' : 'no'; - } - } - } - ); -//]]></script> diff --git a/applications/luci-app-shadowsocks-libev/root/etc/uci-defaults/40_luci-shadowsocks-libev b/applications/luci-app-shadowsocks-libev/root/etc/uci-defaults/40_luci-shadowsocks-libev index 6f30fa77b..4cc9abcee 100644 --- a/applications/luci-app-shadowsocks-libev/root/etc/uci-defaults/40_luci-shadowsocks-libev +++ b/applications/luci-app-shadowsocks-libev/root/etc/uci-defaults/40_luci-shadowsocks-libev @@ -8,4 +8,6 @@ uci -q batch <<-EOF >/dev/null EOF rm -f /tmp/luci-indexcache +mkdir -p /etc/shadowsocks-libev +/etc/init.d/rpcd reload exit 0 diff --git a/applications/luci-app-shadowsocks-libev/root/usr/share/rpcd/acl.d/luci-app-shadowsocks-libev.json b/applications/luci-app-shadowsocks-libev/root/usr/share/rpcd/acl.d/luci-app-shadowsocks-libev.json new file mode 100644 index 000000000..eb56fd12a --- /dev/null +++ b/applications/luci-app-shadowsocks-libev/root/usr/share/rpcd/acl.d/luci-app-shadowsocks-libev.json @@ -0,0 +1,15 @@ +{ + "luci-app-shadowsocks-libev": { + "description": "Grant service list access to LuCI app shadowsocks-libev", + "read": { + "ubus": { + "service": [ "list" ] + } + }, + "write": { + "file": { + "/etc/shadowsocks-libev/*": [ "write" ] + } + } + } +} |