luci-app-ssr-plus-Jo: sync with upstream source

This commit is contained in:
CN_SZTL 2020-03-04 12:19:58 +08:00
parent eb76dc2347
commit 1fb7b958aa
No known key found for this signature in database
GPG Key ID: 6850B6345C862176
11 changed files with 818 additions and 109 deletions

View File

@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-ssr-plus-Jo
PKG_VERSION:=150
PKG_RELEASE:=20200301-5
PKG_VERSION:=151
PKG_RELEASE:=20200304-5
PKG_CONFIG_DEPENDS:= CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan \

View File

@ -28,8 +28,8 @@ entry({"admin", "vpn", "shadowsocksr", "appointlist"},form("shadowsocksr/appoint
entry({"admin", "vpn", "shadowsocksr", "server"},arcombine(cbi("shadowsocksr/server"), cbi("shadowsocksr/server-config")),_("SSR Server"),22).leaf = true
end
entry({"admin", "vpn", "shadowsocksr", "status"},form("shadowsocksr/status"),_("Status"), 23).leaf = true
entry({"admin", "vpn", "shadowsocksr", "log"}, cbi("shadowsocksr/log"), _("Log"), 30).leaf = true
entry({"admin", "vpn", "shadowsocksr", "logview"}, cbi("vssr/logview", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Log") ,30).leaf=true
entry({"admin", "vpn", "shadowsocksr", "fileread"}, call("act_read"), nil).leaf=true
entry({"admin", "vpn", "shadowsocksr", "refresh"}, call("refresh_data"))
entry({"admin", "vpn", "shadowsocksr", "checkport"}, call("check_port"))
entry({"admin", "vpn", "shadowsocksr", "checkports"}, call("check_ports"))
@ -65,7 +65,7 @@ function get_subscribe()
end
luci.sys.call('uci commit shadowsocksr')
luci.sys.call(
"nohup /usr/share/shadowsocksr/subscribe.sh >/www/check_update.htm 2>/dev/null &")
"nohup /usr/bin/lua /usr/share/shadowsocksr/subscribe.lua >/www/check_update.htm 2>/dev/null &")
e.error = 0
else
@ -372,5 +372,16 @@ function check_port()
luci.http.write_json({ret = retstring , used = math.floor(tt*1000 + 0.5)})
end
function act_read(lfile)
local NXFS = require "nixio.fs"
local HTTP = require "luci.http"
local lfile = HTTP.formvalue("lfile")
local ldata={}
ldata[#ldata+1] = NXFS.readfile(lfile) or "_nofile_"
if ldata[1] == "" then
ldata[1] = "_nodata_"
end
HTTP.prepare_content("application/json")
HTTP.write_json(ldata)
end

View File

@ -0,0 +1,48 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local NXFS = require "nixio.fs"
local DISP = require "luci.dispatcher"
local HTTP = require "luci.http"
local UCI = luci.model.uci.cursor()
m = Map("shadowsocksr")
-- log directory
log_dir = UCI:get_first(m.config, "global", "log_dir") or "/tmp"
run_dir = UCI:get_first(m.config, "global", "run_dir") or "/var/etc"
local logfile_list = {}
for path in (NXFS.glob("%s/ssr*" % log_dir) or function() end) do
logfile_list[#logfile_list+1] = path
end
for path in (NXFS.glob("%s/*.*" % run_dir) or function() end) do
logfile_list[#logfile_list+1] = path
end
ns = m:section(TypedSection, "_dummy", translate("File Viewer"))
ns.addremove = false
ns.anonymous = true
function ns.cfgsections()
return{"_exrules"}
end
lv = ns:option(DynamicList, "logfiles")
lv.template = "shadowsocksr/logsview"
lv.inputtitle = translate("Read / Reread log file")
lv.rows = 25
lv.default = ""
for _, lfile in ipairs(logfile_list) do lv:value(lfile, lfile) end
function lv.cfgvalue(self, section)
if logfile_list[1] then
local lfile=logfile_list[1]
if NXFS.access(lfile) then
return lfile .. "\n" .. translate("Please press [Read] button")
end
return lfile .. "\n" .. translate("File not found or empty")
else
return log_dir .. "\/\n" .. translate("No files found")
end
end
return m

View File

@ -1,7 +1,7 @@
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
-- Licensed to the public under the GNU General Public License v3.
local IPK_Version="20200301.150"
local IPK_Version="20200304.151"
local m, s, o
local redir_run=0
local reudp_run=0
@ -96,6 +96,88 @@ if nixio.fs.access("/etc/china_ssr.txt") then
ip_count = sys.exec("cat /etc/china_ssr.txt | wc -l")
end
function processlist()
local data = {}
local netf = {}
local k
local ps = luci.util.execi("/bin/busybox top -bn1 | egrep -v dnsmasq")
local nets = luci.util.execi("netstat -netupl | egrep -v dnsmasq | awk '{print $1,$4,_,$6,$7}'")
if not ps or not nets then
return
end
for line in nets do
-- tcp 0 0 127.0.0.1:1234 0.0.0.0:* LISTEN 5103/v2ray
-- udp 0 0 127.0.0.1:1234 0.0.0.0:* 5147/v2ray
-- local proto, ip, port, nid = line:match("([^%s]+) +.* +([^ ]*):(%d+) +.* +(%d+)\/.*")
local proto, ip, port, nid = line:match("([^%s]+) (.*):(%d+)[^%d]+(%d+)\/.*")
local idx = tonumber(nid)
if idx and ip then
local newstr = "://" .. ip .. ":" .. port
local isnew = (netf[idx] and netf[idx]['listen']) and netf[idx]['listen']:match(proto .. newstr) or false
netf[idx] = {
['listen'] = ((netf[idx] and netf[idx]['listen']) and (not isnew) and (netf[idx]['listen'] .. "\n" .. proto) or proto) .. newstr,
}
end
end
-- 5103 1 root S 661m 543% 0% /usr/bin/v2ray/v2ray -config /var/etc/shadowsocksr.json
for line in ps do
local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
"^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+.?) +(%d+%%) +(%d+%%) +(.+)"
)
if cmd then
local idx = tonumber(pid)
local bin, param, cfg = cmd:match("^.*\/([^ ]*) *([^ ]*) *\/var\/etc\/([^ ]*).*")
if idx and cfg then
local listen = "NONE"
if netf[idx] and netf[idx]['listen'] then
listen = netf[idx]['listen']
end
data[idx] = {
['PID'] = pid,
['COMMAND'] = bin,
['LISTEN'] = listen,
['CONFIG'] = cfg,
['%MEM'] = mem,
['%CPU'] = cpu,
}
end
end
end
return data
end
function printstat(status, form, name)
local tabs = {
["Global Client"] = "shadowsocksr.json",
["Game Mode UDP Relay"] = "shadowsocksr_u.json",
["PDNSD"] = "pdnsd.conf",
["DNS Forward"] = "shadowsocksr_d.json",
["SOCKS5 Proxy"] = "shadowsocksr_s.json",
["Global SSR Server"] = "shadowsocksr_0.json",
}
local stat = translate("Unknown")
local sname = stat
if tabs[name] and status then
stat = translate("Not Running")
for idx, cfg in pairs(status) do
if status[idx]['CONFIG'] and status[idx]['CONFIG'] == tabs[name] then
stat = font_blue .. bold_on .. translate("Running") .. bold_off .. " > " .. status[idx]['COMMAND'] .. " -c " .. status[idx]['CONFIG'] .. font_off
sname = translate(status[idx]['COMMAND'])
break
end
end
end
local section = form:field(DummyValue,name,translate(name) .. ": " .. sname)
section.rawhtml = true
section.value = stat
return section
end
procs=processlist()
@ -187,7 +269,12 @@ m = SimpleForm("Version")
m.reset = false
m.submit = false
t = m:section(Table, procs, translate("Running Details: ") .. "(/var/etc)")
t:option(DummyValue, "PID", translate("PID"))
t:option(DummyValue, "COMMAND", translate("CMD"))
t:option(DummyValue, "LISTEN", translate("LISTEN"))
t:option(DummyValue, "%CPU", translate("CPU"))
t:option(DummyValue, "%MEM", translate("MEM"))
s=m:field(DummyValue,"redir_run",translate("Global Client"))
s.rawhtml = true

View File

@ -58,6 +58,12 @@ o = s:option(DummyValue, "", "")
o.rawhtml = true
o.template = "shadowsocksr/update_subscribe"
o = s:option(Button,"update",translate("Update All Subscribe Severs"),translate("No special needs, please click here to subscribe to update"))
o.inputstyle = "reload"
o.write = function()
luci.sys.call("bash /usr/share/shadowsocksr/subscribe.sh >>/tmp/vssr.log 2>&1")
luci.http.redirect(luci.dispatcher.build_url("admin", "vpn", "shadowsocksr", "servers"))
end
o = s:option(Button,"delete",translate("Delete All Subscribe Severs"))

View File

@ -0,0 +1,109 @@
<!-- ++ BEGIN ++ Auto Repeater ++ logsview.htm ++ -->
<%-
local values = self:formvalue(section)
if not values then
values = self:cfgvalue(section) or {self.default}
end
local function serialize_json(x, cb)
local rv, push = nil, cb
if not push then
rv = { }
push = function(tok) rv[#rv+1] = tok end
end
if x == nil then
push("null")
elseif type(x) == "table" then
push("[")
for k = 1, #x do
if k > 1 then
push(",")
end
serialize_json(x[k], push)
end
push("]")
else
push('"%s"' % tostring(x):gsub('["%z\1-\31\\]',
function(c) return '\\u%04x' % c:byte(1) end))
end
if not cb then
return table.concat(rv, "")
end
end
-%>
<%+cbi/valueheader%>
<select class="cbi-input-select" onchange="onclick_logview(this, false)"
<%=
attr("name", cbid) .. attr("id", cbid) .. attr("value", self.default) .. ifattr(self.size, "size")
%>
></select>
<script type="text/javascript">//<![CDATA[
(function() {
//var values = <%=serialize_json(values)%>;
var keylist = <%=serialize_json(self.keylist)%>;
var vallist = <%=serialize_json(self.vallist)%>;
var parent = document.getElementById("<%=cbid%>");
for (var j = 0; j < keylist.length; j++) {
var opt = document.createElement("option");
opt.value = keylist[j];
if (j == 0) {
opt.selected = "selected";
}
opt.appendChild(document.createTextNode(vallist[j]));
parent.appendChild(opt);
}
}());
//]]></script>
<script type="text/javascript">//<![CDATA[
function onclick_logview(id, bottom) {
// get elements
var txt = document.getElementById("<%=cbid%>.txt"); // TextArea
if ( !txt ) { return; } // security check
txt.value= "Reading: " + document.getElementById("<%=cbid%>").value
XHR.get('<%=luci.dispatcher.build_url("admin", "vpn", "shadowsocksr", "fileread")%>', {lfile: document.getElementById("<%=cbid%>").value} ,
function(x, ifc) {
if (! ifc) {txt.value = "XHR.get(<%=luci.dispatcher.build_url("admin", "vpn", "shadowsocksr", "fileread")%>) Failed!"; return;}
txt.value = ifc[0];
if (bottom)
txt.scrollTop = txt.scrollHeight;
else
txt.scrollTop = 0;
txt.scrollLeft = 0;
}
);
}
//]]></script>
<%
-- one button on top, one at the buttom
%>
<br /><br />
<input class="cbi-button cbi-input-button" style="align: center; width: 100%" type="button" onclick='onclick_logview(this, false)'
<%=
attr("name", section) .. attr("id", cbid .. ".btn1") .. attr("value", self.inputtitle)
%> />
<br /><br />
<%
-- set a readable style taken from openwrt theme for textarea#syslog
-- in openwrt theme there are problems with a width of 100 so we check for theme and set to lower value
%>
<textarea style="width: <%if media == "/luci-static/openwrt.org" then%>98.7%<%else%>100%<%end%> ; min-height: 200px; border: 3px solid #cccccc; padding: 5px; font-family: monospace; resize: none;" wrap="off" readonly="readonly"
<%=
attr("name", cbid .. ".txt") .. attr("id", cbid .. ".txt") .. ifattr(self.rows, "rows")
%> >
<%-=pcdata(self:cfgvalue(section))-%>
</textarea>
<br /><br />
<%
-- one button on top, one at the buttom
%>
<input class="cbi-button cbi-input-button" style="align: center; width: 100%" type="button" onclick='onclick_logview(this, true)'
<%= attr("name", section) .. attr("id", cbid .. ".btn2") .. attr("value", self.inputtitle) %> />
<br /><br />
<%+cbi/valuefooter%>
<!-- ++ END ++ Auto Repeater ++ logsview.htm ++ -->

View File

@ -1,123 +1,109 @@
<%+cbi/valueheader%>
<link rel="stylesheet" href="/luci-static/shadowsocksr/css/shadowsocksr.css?v=72883">
<label class="cbi-value-title"><%= translate("Update") %></label>
<label class="cbi-value-title"><%= translate("Before subscribing please click below to delete all servers in the subscription") %></label>
<div class="cbi-value-field">
<input class="cbi-button cbi-button-reload" id="update_subscribe" type="button"
size="0" value="<%= translate("Save And Start Subscribe") %>">
size="0" value="<%= translate("Save And Start Subscribe") %> ">
</div>
<script type="text/javascript">
//<![CDATA[
var _responseLen;
var noChange = 0;
var x = 5;
const SAVE_SUBSCRIBE_URL = '<%=luci.dispatcher.build_url("admin", "vpn", "shadowsocksr","subscribe")%>';
const SERVERS_URL = '<%=luci.dispatcher.build_url("admin", "vpn", "shadowsocksr","servers")%>';
var _responseLen;
var noChange = 0;
var modal = '<div class="modals-bg">' +
'<div class="modals">' +
'<h2><%:Subscription%></h2>'+
'<h3 style="margin-left:0;"><%:Subscribing,Please do not refresh!%></h3>'+
'<textarea cols="63" rows="28" wrap="on" readonly="readonly" id="log_content3" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>'+
'</div>'+
'</div>';
'<div class="modals">' +
'<h2><%:Subscription%></h2>' +
'<h3 style="margin-left:0;"><%:Subscribing,Please do not refresh!%></h3>' +
'<textarea cols="63" rows="28" wrap="on" readonly="readonly" id="log_content3" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>' +
'</div>' +
'</div>';
function update_subscribe() {
//显示并开始刷新订阅
function update_subscribe() {
$("body").append(modal);
$(".modals-bg").show();
setTimeout("get_realtime_log();", 500);
}
function submit_url(){
prefix_array = $("#cbi-shadowsocksr-server_subscribe .cbi-section-node").attr("id").split("-");
$(".modals-bg").show();
setTimeout("get_realtime_log();", 500);
}
//保存订阅按钮
$("#update_subscribe").click(function () {
prefix_array = $("#cbi-vssr-server_subscribe .cbi-section-node").attr("id").split("-");
prefix_array[0] = "cbid";
prefix = prefix_array.join(".");
if($("[name='"+prefix+".auto_update']").is(":checked")){
if ($("[name='" + prefix + ".auto_update']").is(":checked")) {
var auto_update = "1";
}else{
} else {
var auto_update = "0";
}
var auto_update_time = $("[name='"+prefix+".auto_update_time']").val();
var auto_update_time = $("[name='" + prefix + ".auto_update_time']").val();
var subscribe_url = [];
$("[name='"+prefix+".subscribe_url']").each(function(){
if($(this).val() != ""){
$("[name='" + prefix + ".subscribe_url']").each(function () {
if ($(this).val() != "") {
subscribe_url.push($(this).val());
}
});
if($("[name='"+prefix+".proxy']").is(":checked")){
if ($("[name='" + prefix + ".proxy']").is(":checked")) {
var proxy = "1";
}else{
} else {
var proxy = "0";
}
var data = {
auto_update:auto_update,
auto_update_time : auto_update_time,
auto_update: auto_update,
auto_update_time: auto_update_time,
subscribe_url: JSON.stringify(subscribe_url),
proxy: proxy
}
//console.log(data);
$.ajax({
type: "post",
url: "<%=luci.dispatcher.build_url('admin', 'vpn', 'shadowsocksr','subscribe')%>",
dataType : "json",
data: data,
success: function (d) {
if(d.error == 0){
//console.log("开始订阅");
update_subscribe();
}else{
alert("请至少填写一个订阅链接");
}
type: "post",
url: SAVE_SUBSCRIBE_URL,
dataType: "json",
data: data,
success: function (d) {
if (d.error == 0) {
update_subscribe();
} else {
alert("请至少填写一个订阅链接");
}
});
}
$("#update_subscribe").click(function(){
//console.log("提交数据");
submit_url();
}
});
return false;
});
function refresh_page(){
location.reload();
return false;
}
function get_realtime_log() {
$.ajax({
url: '/check_update.htm?v='+parseInt(Math.random() * 100000000),
dataType: 'html',
error: function (xhr) {
setTimeout("get_realtime_log();", 1000);
},
success: function (response) {
var retArea = document.getElementById("log_content3");
if (response.search("END SUBSCRIBE") != -1) {
$.ajax({
url: '/check_update.htm?v=' + parseInt(Math.random() * 100000000),
dataType: 'html',
error: function (xhr) {
setTimeout("get_realtime_log();", 1000);
},
success: function (response) {
var retArea = document.getElementById("log_content3");
if (response.search("END SUBSCRIBE") != -1) {
noChange++;
}
console.log(noChange);
if (noChange > 10) {
window.location.href="<%=luci.dispatcher.build_url('admin', 'vpn', 'shadowsocksr','servers')%>"
return false;
} else {
setTimeout("get_realtime_log();", 250);
}
retArea.value = response;
retArea.scrollTop = retArea.scrollHeight;
_responseLen = response.length;
},
error: function () {
setTimeout("get_realtime_log();", 500);
}
});
}
}
console.log(noChange);
if (noChange > 10) {
window.location.href = SERVERS_URL;
return false;
} else {
setTimeout("get_realtime_log();", 250);
}
retArea.value = response;
retArea.scrollTop = retArea.scrollHeight;
_responseLen = response.length;
},
error: function () {
setTimeout("get_realtime_log();", 500);
}
});
}
//]]>
</script>
<%+cbi/valuefooter%>

View File

@ -853,4 +853,14 @@ msgstr "SS/SSR/V2RAY 服务端"
msgid "Delete All Subscribe Severs"
msgstr "删除所有订阅服务器节点"
msgid "Update All Subscribe Severs"
msgstr "更新所有订阅服务器节点"
msgid "No special needs, please click here to subscribe to update"
msgstr "没有特殊需要,请点击这里订阅更新"
msgid "Running Details:"
msgstr "进程详情:"
msgid "File Viewer"
msgstr "文件查看器"

View File

@ -217,7 +217,7 @@ start_rules() {
local local_port=$(uci_get_by_name $GLOBAL_SERVER local_port)
local lan_ac_ips=$(uci_get_by_type access_control lan_ac_ips)
local lan_ac_mode="b"
local lan_ac_mode=$(uci_get_by_type access_control lan_ac_mode)
local router_proxy=$(uci_get_by_type access_control router_proxy)
if [ "$GLOBAL_SERVER" = "$UDP_RELAY_SERVER" -a $kcp_flag = 0 ]; then
ARG_UDP="-u"
@ -263,6 +263,7 @@ start_rules() {
-i "$(uci_get_by_type access_control wan_bp_list)" \
-b "$(uci_get_by_type access_control wan_bp_ips)" \
-w "$(uci_get_by_type access_control wan_fw_ips)" \
-B "$(uci_get_by_type access_control lan_bp_ips)" \
-p "$(uci_get_by_type access_control lan_fp_ips)" \
-G "$(uci_get_by_type access_control lan_gm_ips)" \
-D "$proxyport" \
@ -679,11 +680,13 @@ start_redir() {
for i in $(seq 1 $threads); do
$sscmd --config /var/etc/trojan-ssr-retcp.json >/dev/null 2>&1 &
done
echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd --version 2>&1 | head -1) Started!" >> /tmp/ssrplus.log
echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd --version 2>&1 | head -1) $threads Threads Started!" >> /tmp/ssrplus.log
elif [ "$stype" == "v2ray" ] ;then
for i in $(seq 1 $threads); do
$sscmd -config /var/etc/v2-ssr-retcp.json >/dev/null 2>&1 &
echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd -version | head -1) Started!" >> /tmp/ssrplus.log
done
echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd -version | head -1) $threads Threads Started!" >> /tmp/ssrplus.log
fi

View File

@ -26,6 +26,7 @@ usage() {
define access control mode
-b <wan_ips> wan ip of will be bypassed
-w <wan_ips> wan ip of will be forwarded
-B <bp_lan_ips> lan ip of will be bypassed proxy
-p <fp_lan_ips> lan ip of will be global proxy
-G <gm_lan_ips> lan ip of will be game mode proxy
-D <proxy_ports> proxy ports
@ -88,7 +89,7 @@ ipset_r() {
EOF
ipset -N gfwlist hash:net 2>/dev/null
$IPT -N SS_SPEC_WAN_AC
$IPT -I SS_SPEC_WAN_AC -d $server -j RETURN
$IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN
$IPT -A SS_SPEC_WAN_AC -m set --match-set ss_spec_wan_ac dst -j RETURN
$IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW
@ -99,7 +100,7 @@ EOF
$IPT -A SS_SPEC_WAN_AC -m set --match-set china dst -j RETURN
$IPT -A SS_SPEC_WAN_AC -m set --match-set gfwlist dst -j SS_SPEC_WAN_FW
$IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -m set ! --match-set china dst -j SS_SPEC_WAN_FW
$IPT -I SS_SPEC_WAN_AC -d $server -j RETURN
$IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN
elif [ "$RUNMODE" = "oversea" ] ;then
ipset -N oversea hash:net 2>/dev/null
@ -110,7 +111,7 @@ EOF
for ip in $LAN_GM_IP; do ipset -! add gmlan $ip ; done
$IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -m set --match-set china dst -j SS_SPEC_WAN_FW
$IPT -I SS_SPEC_WAN_AC -d $server -j RETURN
$IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN
elif [ "$RUNMODE" = "routers" ] ;then
ipset -! -R <<-EOF || return 1
@ -127,14 +128,16 @@ EOF
elif [ "$RUNMODE" = "all" ] ;then
$IPT -N SS_SPEC_WAN_AC
$IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW
$IPT -I SS_SPEC_WAN_AC -d $server -j RETURN
$IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN
fi
ipset -N fplan hash:net 2>/dev/null
for ip in $LAN_FP_IP; do ipset -! add fplan $ip ; done
$IPT -I SS_SPEC_WAN_AC -m set --match-set fplan src -j SS_SPEC_WAN_FW
ipset -N bplan hash:net 2>/dev/null
for ip in $LAN_BP_IP; do ipset -! add bplan $ip; done
$IPT -I SS_SPEC_WAN_AC -m set --match-set bplan src -j RETURN
ipset -N whitelist hash:net 2>/dev/null
ipset -N blacklist hash:net 2>/dev/null
$IPT -I SS_SPEC_WAN_AC -m set --match-set blacklist src -j SS_SPEC_WAN_FW
@ -157,6 +160,7 @@ fw_rule() {
$IPT -A SS_SPEC_WAN_FW -d 224.0.0.0/4 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 240.0.0.0/4 -j RETURN
$IPT -A SS_SPEC_WAN_FW -p tcp $PROXY_PORTS \
$IPT -A SS_SPEC_WAN_FW -p tcp $PROXY_PORTS \
-j REDIRECT --to-ports $local_port 2>/dev/null || {
loger 3 "Can't redirect, please check the iptables."
exit 1
@ -217,8 +221,8 @@ tp_rule() {
$ipt -A SS_SPEC_TPROXY -p udp -d 192.168.0.0/16 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 224.0.0.0/4 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 240.0.0.0/4 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d $SERVER -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d $server -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set bplan src -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set fplan src \
-j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
@ -326,7 +330,7 @@ EOF
return 0
}
while getopts ":s:l:S:L:i:e:a:b:w:p:G:D:k:oOuUfgrRczh" arg; do
while getopts ":s:l:S:L:i:e:a:B:b:w:p:G:D:k:oOuUfgrRczh" arg; do
case "$arg" in
s)
server=$OPTARG
@ -349,6 +353,11 @@ while getopts ":s:l:S:L:i:e:a:b:w:p:G:D:k:oOuUfgrRczh" arg; do
a)
LAN_AC_IP=$OPTARG
;;
B)
LAN_BP_IP=$OPTARG
;;
b)
WAN_BP_IP=$(for ip in $OPTARG; do echo $ip; done)
;;

View File

@ -0,0 +1,440 @@
#!/usr/bin/lua
------------------------------------------------
-- This file is part of the luci-app-ssr-plus subscribe.lua
-- @author William Chan <root@williamchan.me>
------------------------------------------------
require 'nixio'
require 'luci.util'
require 'luci.jsonc'
require 'luci.sys'
-- these global functions are accessed all the time by the event handler
-- so caching them is worth the effort
local luci = luci
local tinsert = table.insert
local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, string.char, string.byte, string.format, string.gsub
local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
local b64decode = nixio.bin.b64decode
local cache = {}
local nodeResult = setmetatable({}, { __index = cache }) -- update result
local name = 'shadowsocksr'
local uciType = 'servers'
local ucic = luci.model.uci.cursor()
local proxy = ucic:get_first(name, 'server_subscribe', 'proxy', '0')
local switch = ucic:get_first(name, 'server_subscribe', 'switch', '1')
local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url', {})
local log = function(...)
print(os.date("%Y-%m-%d %H:%M:%S ") .. table.concat({ ... }, " "))
end
-- 分割字符串
local function split(full, sep)
full = full:gsub("%z", "") -- 这里不是很清楚 有时候结尾带个\0
local off, result = 1, {}
while true do
local nStart, nEnd = full:find(sep, off)
if not nEnd then
local res = ssub(full, off, slen(full))
if #res > 0 then -- 过滤掉 \0
tinsert(result, res)
end
break
else
tinsert(result, ssub(full, off, nStart - 1))
off = nEnd + 1
end
end
return result
end
-- urlencode
local function get_urlencode(c)
return sformat("%%%02X", sbyte(c))
end
local function urlEncode(szText)
local str = szText:gsub("([^0-9a-zA-Z ])", get_urlencode)
str = str:gsub(" ", "+")
return str
end
local function get_urldecode(h)
return schar(tonumber(h, 16))
end
local function UrlDecode(szText)
return szText:gsub("+", " "):gsub("%%(%x%x)", get_urldecode)
end
-- trim
local function trim(text)
if not text or text == "" then
return ""
end
return (sgsub(text, "^%s*(.-)%s*$", "%1"))
end
-- md5
local function md5(content)
local stdout = luci.sys.exec('echo \"' .. urlEncode(content) .. '\" | md5sum | cut -d \" \" -f1')
-- assert(nixio.errno() == 0)
return trim(stdout)
end
-- base64
local function base64Decode(text)
local raw = text
if not text then return '' end
text = text:gsub("%z", "")
text = text:gsub("_", "/")
text = text:gsub("-", "+")
local mod4 = #text % 4
text = text .. string.sub('====', mod4 + 1)
local result = b64decode(text)
if result then
return result:gsub("%z", "")
else
return raw
end
end
-- 处理数据
local function processData(szType, content)
local result = {
type = szType,
local_port = 1234,
kcp_param = '--nocomp'
}
if szType == 'ssr' then
local dat = split(content, "/%?")
local hostInfo = split(dat[1], ':')
result.server = hostInfo[1]
result.server_port = hostInfo[2]
result.protocol = hostInfo[3]
result.encrypt_method = hostInfo[4]
result.obfs = hostInfo[5]
result.password = base64Decode(hostInfo[6])
local params = {}
for _, v in pairs(split(dat[2], '&')) do
local t = split(v, '=')
params[t[1]] = t[2]
end
result.obfs_param = base64Decode(params.obfsparam)
result.protocol_param = base64Decode(params.protoparam)
local group = base64Decode(params.group)
if group then
result.alias = "[" .. group .. "] "
end
result.alias = result.alias .. base64Decode(params.remarks)
elseif szType == 'vmess' then
local info = jsonParse(content)
result.type = 'v2ray'
result.server = info.add
result.server_port = info.port
result.transport = info.net
result.alter_id = info.aid
result.vmess_id = info.id
result.alias = info.ps
result.insecure = 1
-- result.mux = 1
-- result.concurrency = 8
if info.net == 'ws' then
result.ws_host = info.host
result.ws_path = info.path
end
if info.net == 'h2' then
result.h2_host = info.host
result.h2_path = info.path
end
if info.net == 'tcp' then
result.tcp_guise = info.type
result.http_host = info.host
result.http_path = info.path
end
if info.net == 'kcp' then
result.kcp_guise = info.type
result.mtu = 1350
result.tti = 50
result.uplink_capacity = 5
result.downlink_capacity = 20
result.read_buffer_size = 2
result.write_buffer_size = 2
end
if info.net == 'quic' then
result.quic_guise = info.type
result.quic_key = info.key
result.quic_security = info.securty
end
if info.security then
result.security = info.security
end
if info.tls == "tls" or info.tls == "1" then
result.tls = "1"
result.tls_host = info.host
else
result.tls = "0"
end
elseif szType == "ss" then
local idx_sp = 0
local alias = ""
if content:find("#") then
idx_sp = content:find("#")
alias = content:sub(idx_sp + 1, -1)
end
local info = content:sub(1, idx_sp - 1)
local hostInfo = split(base64Decode(info), "@")
local host = split(hostInfo[2], ":")
local userinfo = base64Decode(hostInfo[1])
local method = userinfo:sub(1, userinfo:find(":") - 1)
local password = userinfo:sub(userinfo:find(":") + 1, #userinfo)
result.alias = UrlDecode(alias)
result.type = "ss"
result.server = host[1]
if host[2]:find("/%?") then
local query = split(host[2], "/%?")
result.server_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
params[t[1]] = t[2]
end
if params.plugin then
local plugin_info = UrlDecode(params.plugin)
local idx_pn = plugin_info:find(";")
if idx_pn then
result.plugin = plugin_info:sub(1, idx_pn - 1)
result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
else
result.plugin = plugin_info
end
end
else
result.server_port = host[2]
end
result.encrypt_method_ss = method
result.password = password
elseif szType == "ssd" then
result.type = "ss"
result.server = content.server
result.server_port = content.port
result.password = content.password
result.encrypt_method_ss = content.encryption
result.plugin = content.plugin
result.plugin_opts = content.plugin_options
result.alias = "[" .. content.airport .. "] " .. content.remarks
elseif szType == "trojan" then
local idx_sp = 0
local alias = ""
if content:find("#") then
idx_sp = content:find("#")
alias = content:sub(idx_sp + 1, -1)
end
local info = content:sub(1, idx_sp - 1)
local hostInfo = split(info, "@")
local host = split(hostInfo[2], ":")
local userinfo = hostInfo[1]
local password = userinfo
result.alias = UrlDecode(alias)
result.type = "trojan"
result.server = host[1]
-- 按照官方的建议 默认验证ssl证书
result.insecure = "0"
result.tls = "1"
if host[2]:find("?") then
local query = split(host[2], "?")
result.server_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
params[t[1]] = t[2]
end
if params.peer then
-- 未指定peersni默认使用remote addr
result.tls_host = params.peer
end
if params.allowInsecure == "1" then
result.insecure = "1"
else
result.insecure = "0"
end
else
result.server_port = host[2]
end
result.password = password
end
if not result.alias then
result.alias = result.server .. ':' .. result.server_port
end
-- alias 不参与 hashkey 计算
local alias = result.alias
result.alias = nil
local switch_enable = result.switch_enable
result.switch_enable = nil
result.hashkey = md5(jsonStringify(result))
result.alias = alias
result.switch_enable = switch_enable
return result
end
-- wget
local function wget(url)
local stdout = luci.sys.exec('wget-ssl --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36" --no-check-certificate -t 3 -T 10 -O- "' .. url .. '"')
return trim(stdout)
end
local execute = function()
-- exec
do
if proxy == '0' then -- 不使用代理更新的话先暂停
log('服务正在暂停')
luci.sys.init.stop(name)
end
for k, url in ipairs(subscribe_url) do
local raw = wget(url)
if #raw > 0 then
local nodes, szType
local groupHash = md5(url)
cache[groupHash] = {}
tinsert(nodeResult, {})
local index = #nodeResult
-- SSD 似乎是这种格式 ssd:// 开头的
if raw:find('ssd://') then
szType = 'ssd'
local nEnd = select(2, raw:find('ssd://'))
nodes = base64Decode(raw:sub(nEnd + 1, #raw))
nodes = jsonParse(nodes)
local extra = {
airport = nodes.airport,
port = nodes.port,
encryption = nodes.encryption,
password = nodes.password
}
local servers = {}
-- SS里面包着 干脆直接这样
for _, server in ipairs(nodes.servers) do
tinsert(servers, setmetatable(server, { __index = extra }))
end
nodes = servers
else
-- ssd 外的格式
nodes = split(base64Decode(raw):gsub(" ", "\n"), "\n")
end
for _, v in ipairs(nodes) do
if v then
local result
if szType == 'ssd' then
result = processData(szType, v)
elseif not szType then
local node = trim(v)
local dat = split(node, "://")
if dat and dat[1] and dat[2] then
if dat[1] == 'ss' or dat[1] == 'trojan' then
result = processData(dat[1], dat[2])
else
result = processData(dat[1], base64Decode(dat[2]))
end
end
else
log('跳过未知类型: ' .. szType)
end
-- log(result)
if result then
if result.alias:find("过期时间") or
result.alias:find("剩余流量") or
result.alias:find("QQ群") or
result.alias:find("官网") or
result.alias:find("防失联地址") or
not result.server or
result.server:match("[^0-9a-zA-Z%-%.%s]") -- 中文做地址的 也没有人拿中文域名搞就算中文域也有Puny Code SB 机场
then
log('丢弃无效节点: ' .. result.type ..' 节点, ' .. result.alias)
else
log('成功解析: ' .. result.type ..' 节点, ' .. result.alias)
result.grouphashkey = groupHash
tinsert(nodeResult[index], result)
cache[groupHash][result.hashkey] = nodeResult[index][#nodeResult[index]]
end
end
end
end
log('成功解析节点数量: ' ..#nodes)
else
log(url .. ': 获取内容为空')
end
end
end
-- diff
do
if next(nodeResult) == nil then
log("更新失败,没有可用的节点信息")
return
end
local add, del = 0, 0
ucic:foreach(name, uciType, function(old)
if old.grouphashkey or old.hashkey then -- 没有 hash 的不参与删除
if not nodeResult[old.grouphashkey] or not nodeResult[old.grouphashkey][old.hashkey] then
ucic:delete(name, old['.name'])
del = del + 1
else
local dat = nodeResult[old.grouphashkey][old.hashkey]
ucic:tset(name, old['.name'], dat)
-- 标记一下
setmetatable(nodeResult[old.grouphashkey][old.hashkey], { __index = { _ignore = true } })
end
else
if not old.alias then
old.alias = old.server .. ':' .. old.server_port
end
log('忽略手动添加的节点: ' .. old.alias)
end
end)
for k, v in ipairs(nodeResult) do
for kk, vv in ipairs(v) do
if not vv._ignore then
local section = ucic:add(name, uciType)
ucic:tset(name, section, vv)
ucic:set(name, section, "switch_enable", switch)
add = add + 1
end
end
end
ucic:commit(name)
-- 如果原有服务器节点已经不见了就尝试换为第一个节点
local globalServer = ucic:get_first(name, 'global', 'global_server', '')
local firstServer = ucic:get_first(name, uciType)
if firstServer then
if not ucic:get(name, globalServer) then
luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
ucic:commit(name)
ucic:set(name, ucic:get_first(name, 'global'), 'global_server', ucic:get_first(name, uciType))
ucic:commit(name)
log('当前主服务器节点已被删除,正在自动更换为第一个节点。')
luci.sys.call("/etc/init.d/" .. name .. " start > /dev/null 2>&1 &")
else
log('维持当前主服务器节点。')
luci.sys.call("/etc/init.d/" .. name .." restart > /dev/null 2>&1 &")
end
else
log('没有服务器节点了,停止服务')
luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
end
log('新增节点数量: ' ..add, '删除节点数量: ' .. del)
log("END SUBSCRIBE")
log('订阅更新成功')
end
end
if subscribe_url and #subscribe_url > 0 then
xpcall(execute, function(e)
log(e)
log(debug.traceback())
log("END SUBSCRIBE")
log('发生错误, 正在恢复服务')
local firstServer = ucic:get_first(name, uciType)
if firstServer then
luci.sys.call("/etc/init.d/" .. name .." restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
log('重启服务成功')
else
luci.sys.call("/etc/init.d/" .. name .." stop > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早
log('停止服务成功')
end
end)
end