diff --git a/Makefile b/Makefile index c7a7e39d..a7283cee 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # -# Copyright (C) 2016-2018 Jian Chang +# Copyright (C) 2016-2021 Jian Chang # # This is free software, licensed under the GNU General Public License v3. # See /LICENSE for more information. @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-shadowsocks -PKG_VERSION:=1.9.1 +PKG_VERSION:=2.0.0 PKG_RELEASE:=1 PKG_LICENSE:=GPLv3 @@ -25,7 +25,7 @@ define Package/luci-app-shadowsocks/Default SUBMENU:=3. Applications TITLE:=LuCI Support for shadowsocks-libev PKGARCH:=all - DEPENDS:=+iptables $(1) + DEPENDS:=+iptables +wget +resolveip $(1) endef Package/luci-app-shadowsocks = $(call Package/luci-app-shadowsocks/Default,+ipset) @@ -87,6 +87,7 @@ define Package/luci-app-shadowsocks/install $(INSTALL_BIN) ./files/root/etc/uci-defaults/luci-shadowsocks $(1)/etc/uci-defaults/luci-shadowsocks $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) ./files/root/usr/bin/ss-rules$(2) $(1)/usr/bin/ss-rules + $(INSTALL_BIN) ./files/root/usr/bin/ss-subscribe $(1)/usr/bin/ss-subscribe endef Package/luci-app-shadowsocks-without-ipset/install = $(call Package/luci-app-shadowsocks/install,$(1),-without-ipset) diff --git a/files/luci/controller/shadowsocks.lua b/files/luci/controller/shadowsocks.lua index 87f1453a..f6e09a88 100644 --- a/files/luci/controller/shadowsocks.lua +++ b/files/luci/controller/shadowsocks.lua @@ -1,4 +1,4 @@ --- Copyright (C) 2014-2017 Jian Chang +-- Copyright (C) 2014-2021 Jian Chang -- Licensed to the public under the GNU General Public License v3. module("luci.controller.shadowsocks", package.seeall) @@ -8,6 +8,8 @@ function index() return end + local page + page = entry({"admin", "services", "shadowsocks"}, alias("admin", "services", "shadowsocks", "general"), _("ShadowSocks"), 10) @@ -25,13 +27,25 @@ function index() page.leaf = true page.acl_depends = { "luci-app-shadowsocks" } + page = entry({"admin", "services", "shadowsocks", "subscription"}, + cbi("shadowsocks/subscription"), + _("Subscription Manage"), 15) + page.leaf = true + page.acl_depends = { "luci-app-shadowsocks" } + + page = entry({"admin", "services", "shadowsocks", "subscribe"}, + call("action_subscribe")) + page.leaf = true + page.acl_depends = { "luci-app-shadowsocks" } + page = entry({"admin", "services", "shadowsocks", "servers"}, arcombine(cbi("shadowsocks/servers"), cbi("shadowsocks/servers-details")), _("Servers Manage"), 20) page.leaf = true page.acl_depends = { "luci-app-shadowsocks" } - if luci.sys.call("command -v ss-redir >/dev/null") ~= 0 then + if luci.sys.call("command -v ss-redir >/dev/null") ~= 0 + and luci.sys.call("command -v ssr-redir >/dev/null") ~= 0 then return end @@ -49,8 +63,15 @@ end function action_status() luci.http.prepare_content("application/json") luci.http.write_json({ - ss_redir = is_running("ss-redir"), - ss_local = is_running("ss-local"), - ss_tunnel = is_running("ss-tunnel") + ss_redir = (is_running("ss-redir") or is_running("ssr-redir")), + ss_local = (is_running("ss-local") or is_running("ssr-local")), + ss_tunnel = (is_running("ss-tunnel") or is_running("ssr-tunnel")) }) end + +function action_subscribe() + local section = luci.http.formvalue("section") + local code = luci.sys.call("/usr/bin/lua /usr/bin/ss-subscribe %s >/tmp/sub.log" %{section}) + luci.http.prepare_content("application/json") + luci.http.write_json({code = code}) +end diff --git a/files/luci/i18n/shadowsocks.zh-cn.po b/files/luci/i18n/shadowsocks.zh-cn.po index 42e00f9f..404525c4 100644 --- a/files/luci/i18n/shadowsocks.zh-cn.po +++ b/files/luci/i18n/shadowsocks.zh-cn.po @@ -165,3 +165,69 @@ msgstr "内网区域" msgid "Zone WAN" msgstr "外网区域" + +msgid "Group Name" +msgstr "分组名称" + +msgid "Server Type" +msgstr "服务器类型" + +msgid "Server Host" +msgstr "服务器域名" + +msgid "Protocol" +msgstr "协议" + +msgid "Protocol Param" +msgstr "协议参数" + +msgid "Obfuscation" +msgstr "混淆" + +msgid "Obfuscation Param" +msgstr "混淆参数" + +msgid "Multiprocessing" +msgstr "多进程" + +msgid "Auto(%u processes)" +msgstr "自动(%u个进程)" + +msgid "%u processes" +msgstr "%u个进程" + +msgid "Subscription Manage" +msgstr "订阅管理" + +msgid "Subscription Name" +msgstr "订阅名称" + +msgid "Subscription URL" +msgstr "订阅链接" + +msgid "Filter Words" +msgstr "过滤字符" + +msgid "Splited by /. Server whose alias contain the above string will be filtered." +msgstr "多个字符使用 / 隔开,别名包含以上字符的的节点将被过滤。" + +msgid "Auto Update" +msgstr "自动更新" + +msgid "Update Hour" +msgstr "更新时间" + +msgid "Update Now" +msgstr "立即更新" + +msgid "Updating..." +msgstr "更新中……" + +msgid "Update Completed" +msgstr "更新完成" + +msgid "Update Failed" +msgstr "更新失败" + +msgid "Update failed, need to save first" +msgstr "更新失败, 需要先保存" diff --git a/files/luci/model/cbi/shadowsocks/access-control.lua b/files/luci/model/cbi/shadowsocks/access-control.lua index 099325e3..b3734540 100644 --- a/files/luci/model/cbi/shadowsocks/access-control.lua +++ b/files/luci/model/cbi/shadowsocks/access-control.lua @@ -1,11 +1,10 @@ --- Copyright (C) 2016-2017 Jian Chang +-- Copyright (C) 2016-2021 Jian Chang -- Licensed to the public under the GNU General Public License v3. local m, s, o local shadowsocks = "shadowsocks" local uci = luci.model.uci.cursor() local nwm = require("luci.model.network").init() -local chnroute = uci:get_first("chinadns", "chinadns", "chnroute") local lan_ifaces = {} local io = require "io" @@ -49,7 +48,6 @@ s.anonymous = true o = s:option(Value, "wan_bp_list", translate("Bypassed IP List")) o:value("/dev/null", translate("NULL - As Global Proxy")) -if chnroute then o:value(chnroute, translate("ChinaDNS CHNRoute")) end o.datatype = "or(file, '/dev/null')" o.default = "/dev/null" o.rmempty = false diff --git a/files/luci/model/cbi/shadowsocks/general.lua b/files/luci/model/cbi/shadowsocks/general.lua index 48c80a93..170d004d 100644 --- a/files/luci/model/cbi/shadowsocks/general.lua +++ b/files/luci/model/cbi/shadowsocks/general.lua @@ -1,26 +1,27 @@ --- Copyright (C) 2014-2018 Jian Chang +-- Copyright (C) 2014-2021 Jian Chang -- Licensed to the public under the GNU General Public License v3. local m, s, o local shadowsocks = "shadowsocks" local uci = luci.model.uci.cursor() local servers = {} +local cores = tonumber(luci.sys.exec("grep 'processor' /proc/cpuinfo | wc -l")) local function has_bin(name) return luci.sys.call("command -v %s >/dev/null" %{name}) == 0 end -local function has_ss_bin() - return has_bin("ss-redir"), has_bin("ss-local"), has_bin("ss-tunnel") +local function has_bins() + return has_bin("ss-redir"), has_bin("ssr-redir"), has_bin("ss-local"), has_bin("ssr-local"), has_bin("ss-tunnel"), has_bin("ssr-tunnel") end local function has_udp_relay() return luci.sys.call("lsmod | grep -q TPROXY && command -v ip >/dev/null") == 0 end -local has_redir, has_local, has_tunnel = has_ss_bin() +local has_ss_redir, has_ssr_redir, has_ss_local, has_ssr_local, has_ss_tunnel, has_ssr_tunnel = has_bins() -if not has_redir and not has_local and not has_tunnel then +if not has_ss_redir and not has_ssr_redir and not has_ss_local and not has_ssr_local and not has_ss_tunnel and not has_ssr_tunnel then return Map(shadowsocks, "%s - %s" %{translate("ShadowSocks"), translate("General Settings")}, 'shadowsocks-libev binary file not found.') end @@ -30,7 +31,7 @@ local function is_running(name) end local function get_status(name) - return is_running(name) and translate("RUNNING") or translate("NOT RUNNING") + return (is_running(name %{"ss"}) or is_running(name %{"ssr"})) and translate("RUNNING") or translate("NOT RUNNING") end uci:foreach(shadowsocks, "servers", function(s) @@ -46,21 +47,21 @@ m.template = "shadowsocks/general" s = m:section(TypedSection, "general", translate("Running Status")) s.anonymous = true -if has_redir then +if has_ss_redir or has_ssr_redir then o = s:option(DummyValue, "_redir_status", translate("Transparent Proxy")) - o.value = "%s" %{get_status("ss-redir")} + o.value = "%s" %{get_status("%s-redir")} o.rawhtml = true end -if has_local then +if has_ss_local or has_ssr_local then o = s:option(DummyValue, "_local_status", translate("SOCKS5 Proxy")) - o.value = "%s" %{get_status("ss-local")} + o.value = "%s" %{get_status("%s-local")} o.rawhtml = true end -if has_tunnel then +if has_ss_tunnel or has_ssr_tunnel then o = s:option(DummyValue, "_tunnel_status", translate("Port Forward")) - o.value = "%s" %{get_status("ss-tunnel")} + o.value = "%s" %{get_status("%s-tunnel")} o.rawhtml = true end @@ -77,11 +78,11 @@ o.default = 0 o.rmempty = false -- [[ Transparent Proxy ]]-- -if has_redir then +if has_ss_redir or has_ssr_redir then s = m:section(TypedSection, "transparent_proxy", translate("Transparent Proxy")) s.anonymous = true - o = s:option(DynamicList, "main_server", translate("Main Server")) + o = s:option(ListValue, "main_server", translate("Main Server")) o:value("nil", translate("Disable")) for _, s in ipairs(servers) do o:value(s.name, s.alias) end o.default = "nil" @@ -98,6 +99,15 @@ if has_redir then o.default = "nil" o.rmempty = false + o = s:option(Value, "processes", translate("Multiprocessing")) + o:value(0, translatef("Auto(%u processes)", cores)) + for v = 1, cores * 2 do + o:value(v, translatef("%u processes", v)) + end + o.datatype = "uinteger" + o.default = 0 + o.rmempty = false + o = s:option(Value, "local_port", translate("Local Port")) o.datatype = "port" o.default = 1234 @@ -110,16 +120,25 @@ if has_redir then end -- [[ SOCKS5 Proxy ]]-- -if has_local then +if has_ss_local or has_ssr_local then s = m:section(TypedSection, "socks5_proxy", translate("SOCKS5 Proxy")) s.anonymous = true - o = s:option(DynamicList, "server", translate("Server")) + o = s:option(ListValue, "server", translate("Server")) o:value("nil", translate("Disable")) for _, s in ipairs(servers) do o:value(s.name, s.alias) end o.default = "nil" o.rmempty = false + o = s:option(Value, "processes", translate("Multiprocessing")) + o:value(0, translatef("Auto(%u processes)", cores)) + for v = 1, cores * 2 do + o:value(v, translatef("%u processes", v)) + end + o.datatype = "uinteger" + o.default = 0 + o.rmempty = false + o = s:option(Value, "local_port", translate("Local Port")) o.datatype = "port" o.default = 1080 @@ -132,16 +151,25 @@ if has_local then end -- [[ Port Forward ]]-- -if has_tunnel then +if has_ss_tunnel or has_ssr_tunnel then s = m:section(TypedSection, "port_forward", translate("Port Forward")) s.anonymous = true - o = s:option(DynamicList, "server", translate("Server")) + o = s:option(ListValue, "server", translate("Server")) o:value("nil", translate("Disable")) for _, s in ipairs(servers) do o:value(s.name, s.alias) end o.default = "nil" o.rmempty = false + o = s:option(Value, "processes", translate("Multiprocessing")) + o:value(0, translatef("Auto(%u processes)", cores)) + for v = 1, cores * 2 do + o:value(v, translatef("%u processes", v)) + end + o.datatype = "uinteger" + o.default = 0 + o.rmempty = false + o = s:option(Value, "local_port", translate("Local Port")) o.datatype = "port" o.default = 5300 diff --git a/files/luci/model/cbi/shadowsocks/servers-details.lua b/files/luci/model/cbi/shadowsocks/servers-details.lua index 4ec3c6a7..942f2514 100644 --- a/files/luci/model/cbi/shadowsocks/servers-details.lua +++ b/files/luci/model/cbi/shadowsocks/servers-details.lua @@ -1,10 +1,13 @@ --- Copyright (C) 2016-2017 Jian Chang +-- Copyright (C) 2016-2021 Jian Chang -- Licensed to the public under the GNU General Public License v3. local m, s, o local shadowsocks = "shadowsocks" local sid = arg[1] local encrypt_methods = { + "none", + "table", + "rc4", "rc4-md5", "aes-128-cfb", "aes-192-cfb", @@ -26,6 +29,44 @@ local encrypt_methods = { "xchacha20-ietf-poly1305", } +local protocols = { + "origin", + "auth_sha1", + "auth_sha1_v2", + "auth_sha1_v4", + "auth_aes128_md5", + "auth_aes128_sha1", + "auth_chain_a", + "auth_chain_b", + "auth_chain_c", + "auth_chain_d", + "auth_chain_e", + "auth_chain_f", +} + +local obfss = { + "plain", + "http_simple", + "http_post", + "tls1.2_ticket_auth", +} + +local function has_bin(name) + return luci.sys.call("command -v %s >/dev/null" %{name}) == 0 +end + +local function has_ss_bins() + return has_bin("ss-redir") or has_bin("ss-local") or has_bin("ss-tunnel") +end + +local function has_ssr_bins() + return has_bin("ssr-redir") or has_bin("ssr-local") or has_bin("ssr-tunnel") +end + +local function support_fast_open() + return luci.sys.exec("cat /proc/sys/net/ipv4/tcp_fastopen 2>/dev/null"):trim() == "3" +end + m = Map(shadowsocks, "%s - %s" %{translate("ShadowSocks"), translate("Edit Server")}) m.redirect = luci.dispatcher.build_url("admin/services/shadowsocks/servers") m.sid = sid @@ -44,12 +85,29 @@ s.addremove = false o = s:option(Value, "alias", translate("Alias(optional)")) o.rmempty = true -o = s:option(Flag, "fast_open", translate("TCP Fast Open")) +o = s:option(Value, "group", translate("Group Name")) +o.default = "Default" +o.rmempty = true + +o = s:option(ListValue, "type", translate("Server Type")) +if has_ss_bins() then o:value("ss", "Shadowsocks") end +if has_ssr_bins() then o:value("ssr", "ShadowsocksR") end +o.default = "ss" o.rmempty = false +if support_fast_open() then + o = s:option(Flag, "fast_open", translate("TCP Fast Open")) + o.rmempty = false +end + o = s:option(Flag, "no_delay", translate("TCP no-delay")) +o:depends("type", "ss") o.rmempty = false +o = s:option(Value, "host", translate("Server Host")) +o.datatype = "host" +o.rmempty = true + o = s:option(Value, "server", translate("Server Address")) o.datatype = "ipaddr" o.rmempty = false @@ -67,6 +125,7 @@ o = s:option(Value, "password", translate("Password")) o.password = true o = s:option(Value, "key", translate("Directly Key")) +o:depends("type", "ss") o = s:option(ListValue, "encrypt_method", translate("Encrypt Method")) for _, v in ipairs(encrypt_methods) do o:value(v, v:upper()) end @@ -74,8 +133,28 @@ o.rmempty = false o = s:option(Value, "plugin", translate("Plugin Name")) o.placeholder = "eg: obfs-local" +o:depends("type", "ss") o = s:option(Value, "plugin_opts", translate("Plugin Arguments")) o.placeholder = "eg: obfs=http;obfs-host=www.bing.com" +o:depends("type", "ss") + +o = s:option(ListValue, "protocol", translate("Protocol")) +for _, v in ipairs(protocols) do o:value(v, v:upper()) end +o:depends("type", "ssr") +o.rmempty = false + +o = s:option(Value, "protocol_param", translate("Protocol Param")) +o:depends("type", "ssr") +o.rmempty = true + +o = s:option(ListValue, "obfs", translate("Obfuscation")) +for _, v in ipairs(obfss) do o:value(v, v:upper()) end +o:depends("type", "ssr") +o.rmempty = false + +o = s:option(Value, "obfs_param", translate("Obfuscation Param")) +o:depends("type", "ssr") +o.rmempty = true return m diff --git a/files/luci/model/cbi/shadowsocks/servers.lua b/files/luci/model/cbi/shadowsocks/servers.lua index 62087731..cd03d07f 100644 --- a/files/luci/model/cbi/shadowsocks/servers.lua +++ b/files/luci/model/cbi/shadowsocks/servers.lua @@ -1,4 +1,4 @@ --- Copyright (C) 2016-2017 Jian Chang +-- Copyright (C) 2016-2021 Jian Chang -- Licensed to the public under the GNU General Public License v3. local m, s, o @@ -26,6 +26,19 @@ function o.cfgvalue(...) return Value.cfgvalue(...) or translate("None") end +o = s:option(DummyValue, "group", translate("Group Name")) +function o.cfgvalue(...) + return Value.cfgvalue(...) or translate("None") +end + +o = s:option(DummyValue, "type", translate("Server Type")) +function o.cfgvalue(...) + if Value.cfgvalue(...) == "ssr" then + return "ShadowsocksR" + end + return "Shadowsocks" +end + o = s:option(DummyValue, "server", translate("Server Address")) function o.cfgvalue(...) return Value.cfgvalue(...) or "?" @@ -42,9 +55,4 @@ function o.cfgvalue(...) return v and v:upper() or "?" end -o = s:option(DummyValue, "plugin", translate("Plugin")) -function o.cfgvalue(...) - return Value.cfgvalue(...) or translate("None") -end - return m diff --git a/files/luci/model/cbi/shadowsocks/subscription.lua b/files/luci/model/cbi/shadowsocks/subscription.lua new file mode 100644 index 00000000..14080617 --- /dev/null +++ b/files/luci/model/cbi/shadowsocks/subscription.lua @@ -0,0 +1,36 @@ +-- Copyright (C) 2021 Jian Chang +-- Licensed to the public under the GNU General Public License v3. + +local m, s, o +local shadowsocks = "shadowsocks" + +m = Map(shadowsocks, "%s - %s" %{translate("ShadowSocks"), translate("Subscription Manage")}) + +s = m:section(TypedSection, "subscription", translate("Subscription Manage")) +s.addremove = true +s.anonymous = true + +o = s:option(Value, "name", translate("Subscription Name")) +o.rmempty = false + +o = s:option(Value, "subscription_url", translate("Subscription URL")) +o.rmempty = false + +o = s:option(Value, "filter_words", translate("Filter Words")) +o.description = translate("Splited by /. Server whose alias contain the above string will be filtered.") +o.rmempty = true + +o = s:option(Flag, "auto_update", translate("Auto Update")) +o.default = "1" +o.rmempty = false + +o = s:option(ListValue, "update_hour", translate("Update Hour")) +for t = 0, 23 do o:value("%02d" %{t}, "%02d" %{t}) end +o.default = 6 +o.rmempty = false + +o = s:option(Button, "subscribe", translate("Update Now")) +o.rawhtml = true +o.template = "shadowsocks/subscribe" + +return m diff --git a/files/luci/view/shadowsocks/general.htm b/files/luci/view/shadowsocks/general.htm index 15e40f84..38b10c85 100644 --- a/files/luci/view/shadowsocks/general.htm +++ b/files/luci/view/shadowsocks/general.htm @@ -1,5 +1,5 @@ <%# - Copyright (C) 2017-2018 Jian Chang + Copyright (C) 2017-2021 Jian Chang Licensed to the public under the GNU General Public License v3. -%> @@ -7,71 +7,6 @@ + +<%+cbi/valuefooter%> diff --git a/files/root/etc/init.d/shadowsocks b/files/root/etc/init.d/shadowsocks index a0cac951..3f9d8be8 100644 --- a/files/root/etc/init.d/shadowsocks +++ b/files/root/etc/init.d/shadowsocks @@ -1,6 +1,6 @@ #!/bin/sh /etc/rc.common # -# Copyright (C) 2014-2017 Jian Chang +# Copyright (C) 2014-2021 Jian Chang # # This is free software, licensed under the GNU General Public License v3. # See /LICENSE for more information. @@ -10,7 +10,7 @@ START=90 STOP=15 NAME=shadowsocks -EXTRA_COMMANDS=rules +EXTRA_COMMANDS="rules" uci_get_by_name() { local ret=$(uci get $NAME.$1.$2 2>/dev/null) @@ -93,19 +93,47 @@ get_crypto_config() { fi } +get_processes() { + local cores=$(grep 'processor' /proc/cpuinfo | wc -l) + local processes=$(uci_get_by_type $1 processes $cores) + if [ "$processes" = "0" ]; then + echo $cores + else + echo $processes + fi +} + gen_config_file() { + local type=$(uci_get_by_name $1 type) local config_file=/var/etc/$NAME.$1.json - cat <<-EOF >$config_file - { - "server": "$(uci_get_by_name $1 server)", - "server_port": $(uci_get_by_name $1 server_port), - $(get_crypto_config $1) - "method": "$(uci_get_by_name $1 encrypt_method)", - "local_address": "0.0.0.0",$(get_plugin_config $1) - "timeout": $(uci_get_by_name $1 timeout 60), - "reuse_port": true - } + if [ "$type" = "ssr" ]; then + cat <<-EOF >$config_file + { + "server": "$(uci_get_by_name $1 server)", + "server_port": $(uci_get_by_name $1 server_port), + "password": "$(uci_get_by_name $1 password)", + "method": "$(uci_get_by_name $1 encrypt_method)", + "local_address": "0.0.0.0", + "timeout": $(uci_get_by_name $1 timeout 60), + "protocol": "$(uci_get_by_name $1 protocol)", + "protocol_param": "$(uci_get_by_name $1 protocol_param)", + "obfs": "$(uci_get_by_name $1 obfs)", + "obfs_param": "$(uci_get_by_name $1 obfs_param)" + } EOF + else + cat <<-EOF >$config_file + { + "server": "$(uci_get_by_name $1 server)", + "server_port": $(uci_get_by_name $1 server_port), + $(get_crypto_config $1) + "method": "$(uci_get_by_name $1 encrypt_method)", + "local_address": "0.0.0.0",$(get_plugin_config $1) + "timeout": $(uci_get_by_name $1 timeout 60), + "reuse_port": true + } +EOF + fi echo $config_file } @@ -126,70 +154,116 @@ start_rules() { } rules() { - pidof ss-redir >/dev/null || return 0 + pidof ss-redir ssr-redir >/dev/null || return 0 start_rules || /usr/bin/ss-rules -f } start_redir() { validate_server $1 || return 0 - ss-redir -c $(gen_config_file $1) $2 $(get_arg_tfo $1) $(get_arg_tnd $1) \ - -l $(uci_get_by_type transparent_proxy local_port 1234) \ - --mtu $(uci_get_by_type transparent_proxy mtu 1492) \ - -f /var/run/ss-redir$3-$1.pid + local type=$(uci_get_by_name $1 type) + if [ "$type" = "ssr" ]; then + command -v ssr-redir >/dev/null || return 1 + ssr-redir -c $(gen_config_file $1) $3 $(get_arg_tfo $1) \ + -l $(uci_get_by_type transparent_proxy local_port 1234) \ + --mtu $(uci_get_by_type transparent_proxy mtu 1492) \ + -f /var/run/ssr-redir$4-$1-$2.pid + else + command -v ss-redir >/dev/null || return 1 + ss-redir -c $(gen_config_file $1) $3 $(get_arg_tfo $1) $(get_arg_tnd $1) \ + -l $(uci_get_by_type transparent_proxy local_port 1234) \ + --mtu $(uci_get_by_type transparent_proxy mtu 1492) \ + -f /var/run/ss-redir$4-$1-$2.pid + fi } ss_redir() { - command -v ss-redir >/dev/null 2>&1 || return 1 + local processes=$(get_processes transparent_proxy) local main_server=$(uci_get_by_type transparent_proxy main_server) has_valid_server $main_server || return 1 local udp_relay_server=$(uci_get_by_type transparent_proxy udp_relay_server) if [ "$udp_relay_server" = "same" ]; then for server in $main_server; do - start_redir $server -u + for i in $(seq $processes); do + start_redir $server $i -u + done + break done else for server in $main_server; do - start_redir $server + for i in $(seq $processes); do + start_redir $server $i + done + break done for server in $udp_relay_server; do - start_redir $server -U -udp + for i in $(seq $processes); do + start_redir $server $i -U -udp + done + break done fi } start_local() { validate_server $1 || return 0 - ss-local -c $(gen_config_file $1) -u $(get_arg_tfo $1) $(get_arg_tnd $1) \ - -l $(uci_get_by_type socks5_proxy local_port 1080) \ - --mtu $(uci_get_by_type socks5_proxy mtu 1492) \ - -f /var/run/ss-local-$1.pid + local type=$(uci_get_by_name $1 type) + if [ "$type" = "ssr" ]; then + command -v ssr-local >/dev/null 2>&1 || return 0 + ssr-local -c $(gen_config_file $1) -u $(get_arg_tfo $1) \ + -l $(uci_get_by_type socks5_proxy local_port 1080) \ + --mtu $(uci_get_by_type socks5_proxy mtu 1492) \ + -f /var/run/ssr-local-$1-$2.pid + else + command -v ss-local >/dev/null 2>&1 || return 0 + ss-local -c $(gen_config_file $1) -u $(get_arg_tfo $1) $(get_arg_tnd $1) \ + -l $(uci_get_by_type socks5_proxy local_port 1080) \ + --mtu $(uci_get_by_type socks5_proxy mtu 1492) \ + -f /var/run/ss-local-$1-$2.pid + fi } ss_local() { - command -v ss-local >/dev/null 2>&1 || return 0 + local processes=$(get_processes socks5_proxy) for server in $(uci_get_by_type socks5_proxy server); do - start_local $server + for i in $(seq $processes); do + start_local $server $i + done + break done } start_tunnel() { validate_server $1 || return 0 - ss-tunnel -c $(gen_config_file $1) -u $(get_arg_tfo $1) $(get_arg_tnd $1) \ - -l $(uci_get_by_type port_forward local_port 5300) \ - -L $(uci_get_by_type port_forward destination 8.8.4.4:53) \ - --mtu $(uci_get_by_type port_forward mtu 1492) \ - -f /var/run/ss-tunnel-$1.pid + local type=$(uci_get_by_name $1 type) + if [ "$type" = "ssr" ]; then + command -v ssr-tunnel >/dev/null 2>&1 || return 0 + ssr-tunnel -c $(gen_config_file $1) \ + -l $(uci_get_by_type port_forward local_port 5300) \ + -L $(uci_get_by_type port_forward destination 8.8.4.4:53) \ + --mtu $(uci_get_by_type port_forward mtu 1492) \ + -f /var/run/ssr-tunnel-$1-$2.pid + else + command -v ss-tunnel >/dev/null 2>&1 || return 0 + ss-tunnel -c $(gen_config_file $1) -u $(get_arg_tnd $1) \ + -l $(uci_get_by_type port_forward local_port 5300) \ + -L $(uci_get_by_type port_forward destination 8.8.4.4:53) \ + --mtu $(uci_get_by_type port_forward mtu 1492) \ + -f /var/run/ss-tunnel-$1-$2.pid + fi } ss_tunnel() { - command -v ss-tunnel >/dev/null 2>&1 || return 0 + local processes=$(get_processes port_forward) for server in $(uci_get_by_type port_forward server); do - start_tunnel $server + for i in $(seq $processes); do + start_tunnel $server $i + done + break done } start() { - pidof ss-redir ss-local ss-tunnel >/dev/null && return 0 + pidof ss-redir ss-local ss-tunnel ssr-redir ssr-local ssr-tunnel>/dev/null && return 0 mkdir -p /var/run /var/etc ss_redir && rules ss_local @@ -208,7 +282,7 @@ kill_all() { stop() { /usr/bin/ss-rules -f - kill_all ss-redir ss-local ss-tunnel + kill_all ss-redir ss-local ss-tunnel ssr-redir ssr-local ssr-tunnel if [ -f /var/run/ss-plugin ]; then kill_all $(sort -u /var/run/ss-plugin) rm -f /var/run/ss-plugin diff --git a/files/root/etc/uci-defaults/luci-shadowsocks b/files/root/etc/uci-defaults/luci-shadowsocks index 098cde1d..5f798ef5 100644 --- a/files/root/etc/uci-defaults/luci-shadowsocks +++ b/files/root/etc/uci-defaults/luci-shadowsocks @@ -22,4 +22,8 @@ uci -q batch <<-EOF >/dev/null set firewall.shadowsocks.reload=1 commit firewall EOF +content=$(grep -v 'ss-subscribe' /etc/crontabs/root) +echo "$content" >/etc/crontabs/root +echo '5 * * * * /usr/bin/ss-subscribe auto >/dev/null 2>&1' >>/etc/crontabs/root +/etc/init.d/cron restart >/dev/null 2>&1 exit 0 diff --git a/files/root/usr/bin/ss-subscribe b/files/root/usr/bin/ss-subscribe new file mode 100644 index 00000000..d46084ae --- /dev/null +++ b/files/root/usr/bin/ss-subscribe @@ -0,0 +1,163 @@ +#!/usr/bin/lua + +-- Copyright (C) 2021 Jian Chang + +-- This is free software, licensed under the GNU General Public License v3. +-- See /LICENSE for more information. + +require "luci.model.uci" +require "luci.util" +require "luci.sys" +require "nixio" + +local shadowsocks = "shadowsocks" +local uci = luci.model.uci.cursor() + +function base64_decode(text) + if not text then + return "" + end + local data = text:gsub("_", "/"):gsub("-", "+") + local fix = #data % 4 + if fix > 0 then + data = data .. string.sub("====", fix + 1) + end + data = nixio.bin.b64decode(data) + if data then + return data + end + return text +end + +function http_get(url) + return luci.util.trim(luci.sys.exec("/usr/bin/wget -q --no-check-certificate -O- '%s'" %{url})) +end + +function parse_ss(data) + local pos = 0 + local params = {} + local result = {type = "ss", timeout = 300, fast_open = 0, no_delay = 0} + if data:find("#") then + pos = data:find("#") + local alias = data:sub(pos + 1, -1) + result.alias = luci.util.urldecode(alias) + end + data = data:sub(1, pos - 1) + data = luci.util.split(data, "/?") + local main = luci.util.split(data[1], "@") + local info = luci.util.split(base64_decode(main[1]), ":") + local host = luci.util.split(base64_decode(main[2]), ":") + result.encrypt_method = info[1] + result.password = info[2] + result.host = host[1] + result.server = luci.util.trim(luci.sys.exec("resolveip %s 2>/dev/null" %{host[1]})) + result.server_port = host[2] + for _, v in pairs(luci.util.split(data[2], "&")) do + local t = luci.util.split(v, '=') + params[t[1]] = luci.util.urldecode(t[2]) + end + if params.plugin then + pos = params.plugin:find(";") + if pos then + result.plugin = params.plugin:sub(1, pos - 1) + result.plugin_opts = params.plugin:sub(pos + 1, -1) + else + result.plugin = params.plugin + end + end + return result +end + +function parse_ssr(data) + data = luci.util.split(base64_decode(data), "/?") + local main = luci.util.split(data[1], ":") + local result = {type = "ssr", timeout = 300, fast_open = 0} + local params = {} + result.host = main[1] + result.server = luci.util.trim(luci.sys.exec("resolveip %s 2>/dev/null" %{main[1]})) + result.server_port = main[2] + result.protocol = main[3] + result.encrypt_method = main[4] + result.obfs = main[5] + result.password = base64_decode(main[6]) + for _, v in pairs(luci.util.split(data[2], "&")) do + local t = luci.util.split(v, '=') + params[t[1]] = t[2] + end + result.alias = base64_decode(params.remarks) + result.protocol_param = base64_decode(params.protoparam) + result.obfs_param = base64_decode(params.obfsparam) + return result +end + +function check_filter_words(words, str) + for _, w in pairs(words) do + if #w > 0 and str:find(w) then + return false + end + end + return true +end + +function exec_subscribe(section) + local url = uci:get(shadowsocks, section, "subscription_url") + if not url then + return false + end + local configs = {} + local group = uci:get(shadowsocks, section, "name") + local words = uci:get(shadowsocks, section, "filter_words") + local lines = luci.util.split(base64_decode(http_get(url)), "\n") + for _, v in pairs(lines) do + if v:find("://") then + local line = luci.util.split(v, "://") + if line[1] == 'ss' then + configs[#configs + 1] = parse_ss(line[2]) + elseif line[1] == 'ssr' then + configs[#configs + 1] = parse_ssr(line[2]) + end + end + end + uci:delete_all(shadowsocks, "servers", function(s) + return s.group == group + end) + if words then + words = luci.util.split(words, "/") + end + for _, v in pairs(configs) do + if not words or check_filter_words(words, v.alias) then + v.group = group + uci:section(shadowsocks, "servers", nil, v) + end + end + return true +end + +if #arg > 0 then + if arg[1]:lower() == "all" then + uci:foreach(shadowsocks, "subscription", function(s) + exec_subscribe(s[".name"]) + end) + elseif arg[1]:lower() == "auto" then + local hour = os.date("%H") + uci:foreach(shadowsocks, "subscription", function(s) + if s.auto_update == "1" and s.update_hour == hour then + exec_subscribe(s[".name"]) + end + end) + else + if not exec_subscribe(arg[1]) then + os.exit(1) + end + end + uci:commit(shadowsocks) + os.exit(0) +else + print("Syntax: subscribe.lua [command]") + print("\nAvailable commands:") + print("\tall force subscribe all subscription section") + print("\tauto subscribe eligible subscription section") + print("\t[section name] subscribe a specific name subscription section") +end + +os.exit(1)