57 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/passwall2/util_tuic.lua:
--------------------------------------------------------------------------------
1 | module("luci.passwall2.util_tuic", package.seeall)
2 | local api = require "luci.passwall2.api"
3 | local uci = api.uci
4 | local json = api.jsonc
5 |
6 | function gen_config(var)
7 | local node_id = var["-node"]
8 | if not node_id then
9 | print("-node 不能为空")
10 | return
11 | end
12 | local node = uci:get_all("passwall2", node_id)
13 | local local_addr = var["-local_addr"]
14 | local local_port = var["-local_port"]
15 | local server_host = var["-server_host"] or node.address
16 | local server_port = var["-server_port"] or node.port
17 | local loglevel = var["-loglevel"] or "warn"
18 |
19 | local tuic= {
20 | relay = {
21 | server = server_host .. ":" .. server_port,
22 | ip = node.tuic_ip,
23 | uuid = node.uuid,
24 | password = node.tuic_password,
25 | -- certificates = node.tuic_certificate and { node.tuic_certpath } or nil,
26 | udp_relay_mode = node.tuic_udp_relay_mode,
27 | congestion_control = node.tuic_congestion_control,
28 | heartbeat = node.tuic_heartbeat .. "s",
29 | timeout = node.tuic_timeout .. "s",
30 | gc_interval = node.tuic_gc_interval .. "s",
31 | gc_lifetime = node.tuic_gc_lifetime .. "s",
32 | alpn = node.tuic_tls_alpn,
33 | disable_sni = (node.tuic_disable_sni == "1"),
34 | zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1"),
35 | send_window = tonumber(node.tuic_send_window),
36 | receive_window = tonumber(node.tuic_receive_window)
37 | },
38 | ["local"] = {
39 | server = "[::]:" .. local_port,
40 | username = node.tuic_socks_username,
41 | password = node.tuic_socks_password,
42 | dual_stack = (node.tuic_dual_stack == "1") and true or false,
43 | max_packet_size = tonumber(node.tuic_max_package_size)
44 | },
45 | log_level = loglevel
46 | }
47 | return json.stringify(tuic, 1)
48 | end
49 |
50 | _G.gen_config = gen_config
51 |
52 | if arg[1] then
53 | local func =_G[arg[1]]
54 | if func then
55 | print(func(api.get_function_args(arg)))
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ss.lua:
--------------------------------------------------------------------------------
1 | local m, s = ...
2 |
3 | local api = require "luci.passwall2.api"
4 |
5 | if not api.is_finded("ss-local") then
6 | return
7 | end
8 |
9 | local type_name = "SS"
10 |
11 | local option_prefix = "ss_"
12 |
13 | local function _n(name)
14 | return option_prefix .. name
15 | end
16 |
17 | local ss_encrypt_method_list = {
18 | "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr",
19 | "aes-192-ctr", "aes-256-ctr", "bf-cfb", "salsa20", "chacha20", "chacha20-ietf",
20 | "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
21 | "xchacha20-ietf-poly1305"
22 | }
23 |
24 | -- [[ Shadowsocks Libev ]]
25 |
26 | s.fields["type"]:value(type_name, translate("Shadowsocks Libev"))
27 |
28 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
29 |
30 | o = s:option(Value, _n("port"), translate("Port"))
31 | o.datatype = "port"
32 |
33 | o = s:option(Value, _n("password"), translate("Password"))
34 | o.password = true
35 |
36 | o = s:option(Value, _n("method"), translate("Encrypt Method"))
37 | for a, t in ipairs(ss_encrypt_method_list) do o:value(t) end
38 |
39 | o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
40 | o.datatype = "uinteger"
41 | o.default = 300
42 |
43 | o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
44 | o:value("false")
45 | o:value("true")
46 |
47 | o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
48 | o.default = 0
49 |
50 | o = s:option(ListValue, _n("plugin"), "SIP003 " .. translate("plugin"))
51 | o.default = "none"
52 | o:value("none", translate("none"))
53 | if api.is_finded("xray-plugin") then o:value("xray-plugin") end
54 | if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end
55 | if api.is_finded("obfs-local") then o:value("obfs-local") end
56 | o:depends({ [_n("plugin_enabled")] = true })
57 |
58 | o = s:option(Value, _n("plugin_opts"), translate("opts"))
59 | o:depends({ [_n("plugin_enabled")] = true })
60 |
61 | api.luci_types(arg[1], m, s, type_name, option_prefix)
62 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: Bug Reporting
2 | description: Report any Bugs you encounter to help us improve.
3 | title: "[Bug]: "
4 | labels: bug
5 |
6 | body:
7 | - type: markdown
8 | id: tips
9 | attributes:
10 | value: |
11 | Please fill in this specification. Effective feedback will help us better solve your problem.
12 |
13 | - type: textarea
14 | id: description
15 | attributes:
16 | label: Describe the Bug you encountered
17 | description: Please describe the problem as simply and clearly as possible.
18 | validations:
19 | required: true
20 |
21 | - type: textarea
22 | id: steps
23 | attributes:
24 | label: Steps to reproduce this Bug
25 | placeholder: |
26 | 1. Select Menu'...'
27 | 2. Click the button'....'
28 | 3. Then...
29 | 4. Appear:
30 | validations:
31 | required: true
32 |
33 | - type: textarea
34 | id: purpose
35 | attributes:
36 | label: What you want to implement
37 | description: Please describe what you expect to happen as simply and clearly as possible.
38 | validations:
39 | required: true
40 |
41 | - type: textarea
42 | id: logs
43 | attributes:
44 | label: Log information
45 | description: Submit all system log information related to the problem (this is very important).
46 | validations:
47 | required: true
48 |
49 | - type: textarea
50 | id: pics
51 | attributes:
52 | label: Screenshot
53 | description: If applicable, you can add screenshots to better explain your problem.
54 | validations:
55 | required: false
56 |
57 | - type: textarea
58 | id: environment
59 | attributes:
60 | label: System related information
61 | description: Fill in relevant information to better identify the problem.
62 | placeholder: |
63 | - Passwall2 version:
64 | - Browser version (such as Chrome 96.0.4664.45 64-bit official version):
65 | - Other:
66 | validations:
67 | required: true
68 |
69 | - type: textarea
70 | id: supplements
71 | attributes:
72 | label: Other Information
73 | description: Other information related to this Bug.
74 | validations:
75 | required: false
76 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ss-rust.lua:
--------------------------------------------------------------------------------
1 | local m, s = ...
2 |
3 | local api = require "luci.passwall2.api"
4 |
5 | if not api.is_finded("ssserver") then
6 | return
7 | end
8 |
9 | local type_name = "SS-Rust"
10 |
11 | local option_prefix = "ssrust_"
12 |
13 | local function _n(name)
14 | return option_prefix .. name
15 | end
16 |
17 | local ssrust_encrypt_method_list = {
18 | "plain", "none",
19 | "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
20 | "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
21 | }
22 |
23 | -- [[ Shadowsocks Rust ]]
24 |
25 | s.fields["type"]:value(type_name, translate("Shadowsocks Rust"))
26 |
27 | o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
28 |
29 | o = s:option(Value, _n("port"), translate("Listen Port"))
30 | o.datatype = "port"
31 | o:depends({ [_n("custom")] = false })
32 |
33 | o = s:option(Value, _n("password"), translate("Password"))
34 | o.password = true
35 | o:depends({ [_n("custom")] = false })
36 |
37 | o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
38 | for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
39 | o:depends({ [_n("custom")] = false })
40 |
41 | o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
42 | o.datatype = "uinteger"
43 | o.default = 300
44 | o:depends({ [_n("custom")] = false })
45 |
46 | o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
47 | o.default = "0"
48 | o:depends({ [_n("custom")] = false })
49 |
50 | o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
51 | o.rows = 10
52 | o.wrap = "off"
53 | o:depends({ [_n("custom")] = true })
54 | o.validate = function(self, value, t)
55 | if value and api.jsonc.parse(value) then
56 | return value
57 | else
58 | return nil, translate("Must be JSON text!")
59 | end
60 | end
61 | o.custom_cfgvalue = function(self, section, value)
62 | local config_str = m:get(section, "config_str")
63 | if config_str then
64 | return api.base64Decode(config_str)
65 | end
66 | end
67 | o.custom_write = function(self, section, value)
68 | m:set(section, "config_str", api.base64Encode(value))
69 | end
70 |
71 | o = s:option(Flag, _n("log"), translate("Log"))
72 | o.default = "1"
73 | o.rmempty = false
74 |
75 | api.luci_types(arg[1], m, s, type_name, option_prefix)
76 |
--------------------------------------------------------------------------------
/luci-app-passwall2/root/usr/share/passwall2/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | CONFIG=passwall2
4 | LOG_FILE=/tmp/log/$CONFIG.log
5 |
6 | echolog() {
7 | local d="$(date "+%Y-%m-%d %H:%M:%S")"
8 | #echo -e "$d: $1"
9 | echo -e "$d: $1" >> $LOG_FILE
10 | }
11 |
12 | config_n_get() {
13 | local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null)
14 | echo "${ret:=$3}"
15 | }
16 |
17 | config_t_get() {
18 | local index=0
19 | [ -n "$4" ] && index=$4
20 | local ret=$(uci -q get $CONFIG.@$1[$index].$2 2>/dev/null)
21 | echo ${ret:=$3}
22 | }
23 |
24 | test_url() {
25 | local url=$1
26 | local try=1
27 | [ -n "$2" ] && try=$2
28 | local timeout=2
29 | [ -n "$3" ] && timeout=$3
30 | local extra_params=$4
31 | curl --help all | grep "\-\-retry-all-errors" > /dev/null
32 | [ $? == 0 ] && extra_params="--retry-all-errors ${extra_params}"
33 | status=$(/usr/bin/curl -I -o /dev/null -skL $extra_params --connect-timeout ${timeout} --retry ${try} -w %{http_code} "$url")
34 | case "$status" in
35 | 204|\
36 | 200)
37 | status=200
38 | ;;
39 | esac
40 | echo $status
41 | }
42 |
43 | test_proxy() {
44 | result=0
45 | status=$(test_url "https://www.google.com/generate_204" ${retry_num} ${connect_timeout})
46 | if [ "$status" = "200" ]; then
47 | result=0
48 | else
49 | status2=$(test_url "https://www.baidu.com" ${retry_num} ${connect_timeout})
50 | if [ "$status2" = "200" ]; then
51 | result=1
52 | else
53 | result=2
54 | ping -c 3 -W 1 223.5.5.5 > /dev/null 2>&1
55 | [ $? -eq 0 ] && {
56 | result=1
57 | }
58 | fi
59 | fi
60 | echo $result
61 | }
62 |
63 | url_test_node() {
64 | result=0
65 | local node_id=$1
66 | local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z')
67 | [ -n "${_type}" ] && {
68 | local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp,udp)
69 | /usr/share/${CONFIG}/app.sh run_socks flag="url_test_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=url_test_${node_id}.json
70 | local curlx="socks5h://127.0.0.1:${_tmp_port}"
71 | sleep 1s
72 | result=$(curl --connect-timeout 3 -o /dev/null -I -skL -w "%{http_code}:%{time_starttransfer}" -x $curlx "https://www.google.com/generate_204")
73 | pgrep -af "url_test_${node_id}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
74 | rm -rf /tmp/etc/${CONFIG}/*url_test_${node_id}*.json
75 | }
76 | echo $result
77 | }
78 |
79 | arg1=$1
80 | shift
81 | case $arg1 in
82 | test_url)
83 | test_url $@
84 | ;;
85 | url_test_node)
86 | url_test_node $@
87 | ;;
88 | esac
89 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ssr.lua:
--------------------------------------------------------------------------------
1 | local m, s = ...
2 |
3 | local api = require "luci.passwall2.api"
4 |
5 | if not api.is_finded("ssr-local") then
6 | return
7 | end
8 |
9 | local type_name = "SSR"
10 |
11 | local option_prefix = "ssr_"
12 |
13 | local function _n(name)
14 | return option_prefix .. name
15 | end
16 |
17 | local ssr_encrypt_method_list = {
18 | "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
19 | "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
20 | "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
21 | "cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
22 | "chacha20-ietf"
23 | }
24 |
25 | local ssr_protocol_list = {
26 | "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
27 | "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
28 | "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
29 | "auth_chain_d", "auth_chain_e", "auth_chain_f"
30 | }
31 | local ssr_obfs_list = {
32 | "plain", "http_simple", "http_post", "random_head", "tls_simple",
33 | "tls1.0_session_auth", "tls1.2_ticket_auth"
34 | }
35 |
36 | -- [[ ShadowsocksR Libev ]]
37 |
38 | s.fields["type"]:value(type_name, translate("ShadowsocksR Libev"))
39 |
40 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
41 |
42 | o = s:option(Value, _n("port"), translate("Port"))
43 | o.datatype = "port"
44 |
45 | o = s:option(Value, _n("password"), translate("Password"))
46 | o.password = true
47 |
48 | o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
49 | for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
50 |
51 | o = s:option(ListValue, _n("protocol"), translate("Protocol"))
52 | for a, t in ipairs(ssr_protocol_list) do o:value(t) end
53 |
54 | o = s:option(Value, _n("protocol_param"), translate("Protocol_param"))
55 |
56 | o = s:option(ListValue, _n("obfs"), translate("Obfs"))
57 | for a, t in ipairs(ssr_obfs_list) do o:value(t) end
58 |
59 | o = s:option(Value, _n("obfs_param"), translate("Obfs_param"))
60 |
61 | o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
62 | o.datatype = "uinteger"
63 | o.default = 300
64 |
65 | o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
66 | o:value("false")
67 | o:value("true")
68 |
69 | api.luci_types(arg[1], m, s, type_name, option_prefix)
70 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ss.lua:
--------------------------------------------------------------------------------
1 | local m, s = ...
2 |
3 | local api = require "luci.passwall2.api"
4 |
5 | if not api.is_finded("ss-server") then
6 | return
7 | end
8 |
9 | local type_name = "SS"
10 |
11 | local option_prefix = "ss_"
12 |
13 | local function _n(name)
14 | return option_prefix .. name
15 | end
16 |
17 | local ss_encrypt_method_list = {
18 | "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr",
19 | "aes-192-ctr", "aes-256-ctr", "bf-cfb", "camellia-128-cfb",
20 | "camellia-192-cfb", "camellia-256-cfb", "salsa20", "chacha20",
21 | "chacha20-ietf", -- aead
22 | "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
23 | "xchacha20-ietf-poly1305"
24 | }
25 |
26 | -- [[ Shadowsocks ]]
27 |
28 | s.fields["type"]:value(type_name, translate("Shadowsocks"))
29 |
30 | o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
31 |
32 | o = s:option(Value, _n("port"), translate("Listen Port"))
33 | o.datatype = "port"
34 | o:depends({ [_n("custom")] = false })
35 |
36 | o = s:option(Value, _n("password"), translate("Password"))
37 | o.password = true
38 | o:depends({ [_n("custom")] = false })
39 |
40 | o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
41 | for a, t in ipairs(ss_encrypt_method_list) do o:value(t) end
42 | o:depends({ [_n("custom")] = false })
43 |
44 | o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
45 | o.datatype = "uinteger"
46 | o.default = 300
47 | o:depends({ [_n("custom")] = false })
48 |
49 | o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
50 | o.default = "0"
51 | o:depends({ [_n("custom")] = false })
52 |
53 | o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
54 | o.rows = 10
55 | o.wrap = "off"
56 | o:depends({ [_n("custom")] = true })
57 | o.validate = function(self, value, t)
58 | if value and api.jsonc.parse(value) then
59 | return value
60 | else
61 | return nil, translate("Must be JSON text!")
62 | end
63 | end
64 | o.custom_cfgvalue = function(self, section, value)
65 | local config_str = m:get(section, "config_str")
66 | if config_str then
67 | return api.base64Decode(config_str)
68 | end
69 | end
70 | o.custom_write = function(self, section, value)
71 | m:set(section, "config_str", api.base64Encode(value))
72 | end
73 |
74 | o = s:option(Flag, _n("log"), translate("Log"))
75 | o.default = "1"
76 | o.rmempty = false
77 |
78 | api.luci_types(arg[1], m, s, type_name, option_prefix)
79 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ss-rust.lua:
--------------------------------------------------------------------------------
1 | local m, s = ...
2 |
3 | local api = require "luci.passwall2.api"
4 |
5 | if not api.is_finded("sslocal") then
6 | return
7 | end
8 |
9 | local type_name = "SS-Rust"
10 |
11 | local option_prefix = "ssrust_"
12 |
13 | local function _n(name)
14 | return option_prefix .. name
15 | end
16 |
17 | local ssrust_encrypt_method_list = {
18 | "none", "plain",
19 | "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
20 | "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
21 | }
22 |
23 | -- [[ Shadowsocks Rust ]]
24 |
25 | s.fields["type"]:value(type_name, translate("Shadowsocks Rust"))
26 |
27 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
28 |
29 | o = s:option(Value, _n("port"), translate("Port"))
30 | o.datatype = "port"
31 |
32 | o = s:option(Value, _n("password"), translate("Password"))
33 | o.password = true
34 |
35 | o = s:option(Value, _n("method"), translate("Encrypt Method"))
36 | for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
37 |
38 | o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
39 | o.datatype = "uinteger"
40 | o.default = 300
41 |
42 | o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
43 | o:value("false")
44 | o:value("true")
45 |
46 | o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
47 | o.default = 0
48 |
49 | o = s:option(Value, _n("plugin"), "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed."))
50 | o.default = "none"
51 | o:value("none", translate("none"))
52 | if api.is_finded("xray-plugin") then o:value("xray-plugin") end
53 | if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end
54 | if api.is_finded("obfs-local") then o:value("obfs-local") end
55 | if api.is_finded("shadow-tls") then o:value("shadow-tls") end
56 | o:depends({ [_n("plugin_enabled")] = true })
57 | o.validate = function(self, value, t)
58 | if value and value ~= "" and value ~= "none" then
59 | if not api.is_finded(value) then
60 | return nil, value .. ": " .. translate("Can't find this file!")
61 | else
62 | return value
63 | end
64 | end
65 | return nil
66 | end
67 |
68 | o = s:option(Value, _n("plugin_opts"), translate("opts"))
69 | o:depends({ [_n("plugin_enabled")] = true })
70 |
71 | api.luci_types(arg[1], m, s, type_name, option_prefix)
72 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua:
--------------------------------------------------------------------------------
1 | local api = require "luci.passwall2.api"
2 |
3 | m = Map("passwall2_server", translate("Server-Side"))
4 | api.set_apply_on_parse(m)
5 |
6 | t = m:section(NamedSection, "global", "global")
7 | t.anonymous = true
8 | t.addremove = false
9 |
10 | e = t:option(Flag, "enable", translate("Enable"))
11 | e.rmempty = false
12 |
13 | t = m:section(TypedSection, "user", translate("Users Manager"))
14 | t.anonymous = true
15 | t.addremove = true
16 | t.sortable = true
17 | t.template = "cbi/tblsection"
18 | t.extedit = api.url("server_user", "%s")
19 | function t.create(e, t)
20 | local uuid = api.gen_uuid()
21 | t = uuid
22 | TypedSection.create(e, t)
23 | luci.http.redirect(e.extedit:format(t))
24 | end
25 | function t.remove(e, t)
26 | e.map.proceed = true
27 | e.map:del(t)
28 | luci.http.redirect(api.url("server"))
29 | end
30 |
31 | e = t:option(Flag, "enable", translate("Enable"))
32 | e.width = "5%"
33 | e.rmempty = false
34 |
35 | e = t:option(DummyValue, "status", translate("Status"))
36 | e.rawhtml = true
37 | e.cfgvalue = function(t, n)
38 | return string.format('%s', translate("Collecting data..."))
39 | end
40 |
41 | e = t:option(DummyValue, "remarks", translate("Remarks"))
42 | e.width = "15%"
43 |
44 | e = t:option(DummyValue, "type", translate("Type"))
45 | e.width = "20%"
46 | e.rawhtml = true
47 | e.cfgvalue = function(t, n)
48 | local str = ""
49 | local type = m:get(n, "type") or ""
50 | if type == "sing-box" or type == "Xray" then
51 | local protocol = m:get(n, "protocol") or ""
52 | if protocol == "vmess" then
53 | protocol = "VMess"
54 | elseif protocol == "vless" then
55 | protocol = "VLESS"
56 | elseif protocol == "shadowsocks" then
57 | protocol = "SS"
58 | elseif protocol == "shadowsocksr" then
59 | protocol = "SSR"
60 | elseif protocol == "wireguard" then
61 | protocol = "WG"
62 | elseif protocol == "hysteria" then
63 | protocol = "HY"
64 | elseif protocol == "hysteria2" then
65 | protocol = "HY2"
66 | elseif protocol == "anytls" then
67 | protocol = "AnyTLS"
68 | else
69 | protocol = protocol:gsub("^%l",string.upper)
70 | local custom = m:get(n, "custom") or "0"
71 | if custom == "1" then
72 | protocol = translate("Custom Config")
73 | end
74 | end
75 | if type == "sing-box" then type = "Sing-Box" end
76 | type = type .. " " .. protocol
77 | end
78 | str = str .. translate(type)
79 | return str
80 | end
81 |
82 | e = t:option(DummyValue, "port", translate("Port"))
83 |
84 | e = t:option(Flag, "log", translate("Log"))
85 | e.default = "1"
86 | e.rmempty = false
87 |
88 | m:append(Template("passwall2/server/log"))
89 |
90 | m:append(Template("passwall2/server/users_list_status"))
91 | return m
92 |
93 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/hysteria2.lua:
--------------------------------------------------------------------------------
1 | local m, s = ...
2 |
3 | local api = require "luci.passwall2.api"
4 |
5 | if not api.finded_com("hysteria") then
6 | return
7 | end
8 |
9 | local type_name = "Hysteria2"
10 |
11 | local option_prefix = "hysteria2_"
12 |
13 | local function _n(name)
14 | return option_prefix .. name
15 | end
16 |
17 | -- [[ Hysteria2 ]]
18 |
19 | s.fields["type"]:value(type_name, "Hysteria2")
20 |
21 | o = s:option(ListValue, _n("protocol"), translate("Protocol"))
22 | o:value("udp", "UDP")
23 |
24 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
25 |
26 | o = s:option(Value, _n("port"), translate("Port"))
27 | o.datatype = "port"
28 |
29 | o = s:option(Value, _n("hop"), translate("Port hopping range"))
30 | o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
31 | o.rewrite_option = o.option
32 |
33 | o = s:option(Value, _n("hop_interval"), translate("Hop Interval"), translate("Example:") .. "30s (≥5s)")
34 | o.placeholder = "30s"
35 | o.default = "30s"
36 | o.rewrite_option = o.option
37 |
38 | o = s:option(Value, _n("obfs"), translate("Obfs Password"))
39 | o.rewrite_option = o.option
40 |
41 | o = s:option(Value, _n("auth_password"), translate("Auth Password"))
42 | o.password = true
43 | o.rewrite_option = o.option
44 |
45 | o = s:option(Flag, _n("fast_open"), translate("Fast Open"))
46 | o.default = "0"
47 |
48 | o = s:option(Value, _n("tls_serverName"), translate("Domain"))
49 |
50 | o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
51 | o.default = "0"
52 |
53 | o = s:option(Value, _n("tls_pinSHA256"), translate("PinSHA256"),translate("Certificate fingerprint"))
54 | o.rewrite_option = o.option
55 |
56 | o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps"))
57 | o.rewrite_option = o.option
58 |
59 | o = s:option(Value, _n("down_mbps"), translate("Max download Mbps"))
60 | o.rewrite_option = o.option
61 |
62 | o = s:option(Value, _n("recv_window"), translate("QUIC stream receive window"))
63 | o.rewrite_option = o.option
64 |
65 | o = s:option(Value, _n("recv_window_conn"), translate("QUIC connection receive window"))
66 | o.rewrite_option = o.option
67 |
68 | o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s-120s)")
69 | o.rewrite_option = o.option
70 |
71 | o = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection"))
72 | o.default = "0"
73 | o.rewrite_option = o.option
74 |
75 | o = s:option(Flag, _n("lazy_start"), translate("Lazy Start"))
76 | o.default = "0"
77 | o.rewrite_option = o.option
78 |
79 | api.luci_types(arg[1], m, s, type_name, option_prefix)
80 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm:
--------------------------------------------------------------------------------
1 | <%
2 | local api = require "luci.passwall2.api"
3 | -%>
4 |
31 |
32 |
33 |
34 | <%:DNS related issues:%>
35 |
1. <%:Certain browsers such as Chrome have built-in DNS service, which may affect DNS resolution settings. You can go to 'Settings -> Privacy and security -> Use secure DNS' menu to turn it off.%>
36 |
2. <%:If you are unable to access the internet after reboot, please try clearing the cache of your terminal devices (make sure to close all open browser application windows first, this step is especially important):%>
37 |
◦ <%:For Windows systems, open Command Prompt and run the command 'ipconfig /flushdns'.%>
38 |
◦ <%:For Mac systems, open Terminal and run the command 'sudo killall -HUP mDNSResponder'.%>
39 |
◦ <%:For mobile devices, you can clear it by reconnecting to the network, such as toggling Airplane Mode and reconnecting to WiFi.%>
40 |
41 |
42 |
3. <%:Please make sure your device's network settings point both the DNS server and default gateway to this router, to ensure DNS queries are properly routed.%>
191 |
192 | <%for k, v in pairs(com) do
193 | version[k] = api.get_app_version(k)%>
194 |
195 |
198 |
199 |
200 | 【 <%=version[k] ~="" and version[k] or translate("Null") %> 】
201 |
203 |
205 |
206 |
207 |
208 |
209 | <%end%>
210 |
--------------------------------------------------------------------------------
/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local api = require "luci.passwall2.api"
4 | local name = api.appname
5 | local fs = api.fs
6 | local sys = api.sys
7 | local uci = api.uci
8 | local jsonc = api.jsonc
9 |
10 | local arg1 = arg[1]
11 | local arg2 = arg[2]
12 | local arg3 = arg[3]
13 |
14 | local reboot = 0
15 | local geoip_update = 0
16 | local geosite_update = 0
17 | local asset_location = uci:get_first(name, 'global_rules', "v2ray_location_asset", "/usr/share/v2ray/")
18 |
19 | -- Custom geo file
20 | local geoip_api = uci:get_first(name, 'global_rules', "geoip_url", "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest")
21 | local geosite_api = uci:get_first(name, 'global_rules', "geosite_url", "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest")
22 | --
23 | local use_nft = uci:get(name, "@global_forwarding[0]", "use_nft") or "0"
24 |
25 | if arg3 == "cron" then
26 | arg2 = nil
27 | end
28 |
29 | local log = function(...)
30 | if arg1 then
31 | if arg1 == "log" then
32 | api.log(...)
33 | elseif arg1 == "print" then
34 | local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
35 | print(result)
36 | end
37 | end
38 | end
39 |
40 | -- curl
41 | local function curl(url, file)
42 | local args = {
43 | "-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "--max-time 300", "--speed-limit 51200 --speed-time 15"
44 | }
45 | if file then
46 | args[#args + 1] = "-o " .. file
47 | end
48 | local return_code, result = api.curl_logic(url, nil, args)
49 | return tonumber(result)
50 | end
51 |
52 | --Getgeoip
53 | local function fetch_geoip()
54 | --askgeoip
55 | xpcall(function()
56 | local return_code, content = api.curl_logic(geoip_api)
57 | local json = jsonc.parse(content)
58 | if json.tag_name and json.assets then
59 | for _, v in ipairs(json.assets) do
60 | if v.name and v.name == "geoip.dat.sha256sum" then
61 | local sret = curl(v.browser_download_url, "/tmp/geoip.dat.sha256sum")
62 | if sret == 200 then
63 | local f = io.open("/tmp/geoip.dat.sha256sum", "r")
64 | local content = f:read()
65 | f:close()
66 | f = io.open("/tmp/geoip.dat.sha256sum", "w")
67 | f:write(content:gsub("geoip.dat", "/tmp/geoip.dat"), "")
68 | f:close()
69 |
70 | if fs.access(asset_location .. "geoip.dat") then
71 | sys.call(string.format("cp -f %s %s", asset_location .. "geoip.dat", "/tmp/geoip.dat"))
72 | if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
73 | log("geoip Version consistent,No update required。")
74 | return 1
75 | end
76 | end
77 | for _2, v2 in ipairs(json.assets) do
78 | if v2.name and v2.name == "geoip.dat" then
79 | sret = curl(v2.browser_download_url, "/tmp/geoip.dat")
80 | if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
81 | sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geoip.dat", asset_location .. "geoip.dat"))
82 | reboot = 1
83 | log("geoip Update successful。")
84 | return 1
85 | else
86 | log("geoip Update failed,Please try again later。")
87 | end
88 | break
89 | end
90 | end
91 | end
92 | break
93 | end
94 | end
95 | end
96 | if json.message then
97 | log(json.message)
98 | end
99 | end,
100 | function(e)
101 | end)
102 |
103 | return 0
104 | end
105 |
106 | --Getgeosite
107 | local function fetch_geosite()
108 | --askgeosite
109 | xpcall(function()
110 | local return_code, content = api.curl_logic(geosite_api)
111 | local json = jsonc.parse(content)
112 | if json.tag_name and json.assets then
113 | for _, v in ipairs(json.assets) do
114 | if v.name and (v.name == "geosite.dat.sha256sum" or v.name == "dlc.dat.sha256sum") then
115 | local sret = curl(v.browser_download_url, "/tmp/geosite.dat.sha256sum")
116 | if sret == 200 then
117 | local f = io.open("/tmp/geosite.dat.sha256sum", "r")
118 | local content = f:read()
119 | f:close()
120 | f = io.open("/tmp/geosite.dat.sha256sum", "w")
121 | f:write(content:gsub("[^%s]+.dat", "/tmp/geosite.dat"), "")
122 | f:close()
123 |
124 | if fs.access(asset_location .. "geosite.dat") then
125 | sys.call(string.format("cp -f %s %s", asset_location .. "geosite.dat", "/tmp/geosite.dat"))
126 | if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
127 | log("geosite Version consistent,No update required。")
128 | return 1
129 | end
130 | end
131 | for _2, v2 in ipairs(json.assets) do
132 | if v2.name and (v2.name == "geosite.dat" or v2.name == "dlc.dat") then
133 | sret = curl(v2.browser_download_url, "/tmp/geosite.dat")
134 | if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
135 | sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geosite.dat", asset_location .. "geosite.dat"))
136 | reboot = 1
137 | log("geosite Update successful。")
138 | return 1
139 | else
140 | log("geosite Update failed,Please try again later。")
141 | end
142 | break
143 | end
144 | end
145 | end
146 | break
147 | end
148 | end
149 | end
150 | if json.message then
151 | log(json.message)
152 | end
153 | end,
154 | function(e)
155 | end)
156 |
157 | return 0
158 | end
159 |
160 | if arg2 then
161 | string.gsub(arg2, '[^' .. "," .. ']+', function(w)
162 | if w == "geoip" then
163 | geoip_update = 1
164 | end
165 | if w == "geosite" then
166 | geosite_update = 1
167 | end
168 | end)
169 | else
170 | geoip_update = uci:get_first(name, 'global_rules', "geoip_update", 1)
171 | geosite_update = uci:get_first(name, 'global_rules', "geosite_update", 1)
172 | end
173 | if geoip_update == 0 and geosite_update == 0 then
174 | os.exit(0)
175 | end
176 |
177 | log("Start updating rules...")
178 |
179 | if tonumber(geoip_update) == 1 then
180 | log("geoip Start updating...")
181 | local status = fetch_geoip()
182 | os.remove("/tmp/geoip.dat")
183 | os.remove("/tmp/geoip.dat.sha256sum")
184 | end
185 |
186 | if tonumber(geosite_update) == 1 then
187 | log("geosite Start updating...")
188 | local status = fetch_geosite()
189 | os.remove("/tmp/geosite.dat")
190 | os.remove("/tmp/geosite.dat.sha256sum")
191 | end
192 |
193 | uci:set(name, uci:get_first(name, 'global_rules'), "geoip_update", geoip_update)
194 | uci:set(name, uci:get_first(name, 'global_rules'), "geosite_update", geosite_update)
195 | api.uci_save(uci, name, true)
196 |
197 | if reboot == 1 then
198 | if arg3 == "cron" then
199 | if not fs.access("/var/lock/" .. name .. ".lock") then
200 | sys.call("touch /tmp/lock/" .. name .. "_cron.lock")
201 | end
202 | end
203 |
204 | log("Restart service,Apply new rules。")
205 | uci:set(name, "@global[0]", "flush_set", "1")
206 | api.uci_save(uci, name, true, true)
207 | end
208 | log("Rules updated...")
209 |
--------------------------------------------------------------------------------
/luci-app-passwall2/root/usr/share/passwall2/socks_auto_switch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | CONFIG=passwall2
4 | LOG_FILE=/tmp/log/$CONFIG.log
5 | LOCK_FILE_DIR=/tmp/lock
6 |
7 | flag=0
8 |
9 | echolog() {
10 | local d="$(date "+%Y-%m-%d %H:%M:%S")"
11 | #echo -e "$d: $1"
12 | echo -e "$d: $1" >> $LOG_FILE
13 | }
14 |
15 | config_n_get() {
16 | local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null)
17 | echo "${ret:=$3}"
18 | }
19 |
20 | test_url() {
21 | local url=$1
22 | local try=1
23 | [ -n "$2" ] && try=$2
24 | local timeout=2
25 | [ -n "$3" ] && timeout=$3
26 | local extra_params=$4
27 | if /usr/bin/curl --help all | grep -q "\-\-retry-all-errors"; then
28 | extra_params="--retry-all-errors ${extra_params}"
29 | fi
30 | local status=$(/usr/bin/curl -I -o /dev/null -skL ${extra_params} --connect-timeout ${timeout} --retry ${try} -w %{http_code} "$url")
31 | case "$status" in
32 | 204)
33 | status=200
34 | ;;
35 | esac
36 | echo $status
37 | }
38 |
39 | test_proxy() {
40 | local result=0
41 | local status=$(test_url "${probe_url}" ${retry_num} ${connect_timeout} "-x socks5h://127.0.0.1:${socks_port}")
42 | if [ "$status" = "200" ]; then
43 | result=0
44 | else
45 | local status2=$(test_url "https://www.baidu.com" ${retry_num} ${connect_timeout})
46 | if [ "$status2" = "200" ]; then
47 | result=1
48 | else
49 | result=2
50 | ping -c 3 -W 1 223.5.5.5 > /dev/null 2>&1
51 | [ $? -eq 0 ] && {
52 | result=1
53 | }
54 | fi
55 | fi
56 | echo $result
57 | }
58 |
59 | test_node() {
60 | local node_id=$1
61 | local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z')
62 | [ -n "${_type}" ] && {
63 | local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp,udp)
64 | /usr/share/${CONFIG}/app.sh run_socks flag="test_node_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=test_node_${node_id}.json
65 | local curlx="socks5h://127.0.0.1:${_tmp_port}"
66 | sleep 1s
67 | local _proxy_status=$(test_url "${probe_url}" ${retry_num} ${connect_timeout} "-x $curlx")
68 | # Finish SS Plug-in process
69 | local pid_file="/tmp/etc/${CONFIG}/test_node_${node_id}_plugin.pid"
70 | [ -s "$pid_file" ] && kill -9 "$(head -n 1 "$pid_file")" >/dev/null 2>&1
71 | pgrep -af "test_node_${node_id}" | awk '! /socks_auto_switch\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
72 | rm -rf /tmp/etc/${CONFIG}/test_node_${node_id}*.*
73 | if [ "${_proxy_status}" -eq 200 ]; then
74 | return 0
75 | fi
76 | }
77 | return 1
78 | }
79 |
80 | test_auto_switch() {
81 | flag=$((flag + 1))
82 | local b_nodes=$1
83 | local now_node=$2
84 | [ -z "$now_node" ] && {
85 | if [ -n "$(/usr/share/${CONFIG}/app.sh get_cache_var "socks_${id}")" ]; then
86 | now_node=$(/usr/share/${CONFIG}/app.sh get_cache_var "socks_${id}")
87 | else
88 | #echolog "SocksSwitch detection:unknown error"
89 | return 1
90 | fi
91 | }
92 |
93 | [ $flag -le 1 ] && {
94 | main_node=$now_node
95 | }
96 |
97 | local status=$(test_proxy)
98 | if [ "$status" = "2" ]; then
99 | echolog "SocksSwitch detection:Unable to connect to network,Please check if the network is normal!"
100 | return 2
101 | fi
102 |
103 | #Check whether the master node can be used
104 | if [ "$restore_switch" = "1" ] && [ -n "$main_node" ] && [ "$now_node" != "$main_node" ]; then
105 | test_node ${main_node}
106 | [ $? -eq 0 ] && {
107 | #Master node is normal,Switch to master node
108 | echolog "SocksSwitch detection:${id}master node【$(config_n_get $main_node type):[$(config_n_get $main_node remarks)]】normal,Switch to master node!"
109 | /usr/share/${CONFIG}/app.sh socks_node_switch flag=${id} new_node=${main_node}
110 | [ $? -eq 0 ] && {
111 | echolog "SocksSwitch detection:${id}Node switching completed!"
112 | }
113 | return 0
114 | }
115 | fi
116 |
117 | if [ "$status" = "0" ]; then
118 | #echolog "SocksSwitch detection:${id}【$(config_n_get $now_node type):[$(config_n_get $now_node remarks)]】normal。"
119 | return 0
120 | elif [ "$status" = "1" ]; then
121 | local new_node msg
122 | if [ "$backup_node_num" -gt 1 ]; then
123 | # When there are multiple backup nodes
124 | local first_node found node
125 | for node in $b_nodes; do
126 | [ -z "$first_node" ] && first_node="$node" # Record the first node
127 | [ "$found" = "1" ] && { new_node="$node"; break; } # After finding the current node, remove the next one
128 | [ "$node" = "$now_node" ] && found=1 # Mark the current node found
129 | done
130 | # If the current node is not found,Or the current node is the last one,Just take the first node
131 | [ -z "$new_node" ] && new_node="$first_node"
132 | msg="switch to$([ "$now_node" = "$main_node" ] && echo Standby node || echo next standby node)Detection!"
133 | else
134 | # When there is only one backup node,Polling with the master node
135 | new_node=$([ "$now_node" = "$main_node" ] && echo "$b_nodes" || echo "$main_node")
136 | msg="switch to$([ "$now_node" = "$main_node" ] && echo Standby node || echo master node)Detection!"
137 | fi
138 | echolog "SocksSwitch detection:${id}【$(config_n_get $now_node type):[$(config_n_get $now_node remarks)]】abnormal,$msg"
139 | test_node ${new_node}
140 | if [ $? -eq 0 ]; then
141 | # [ "$restore_switch" = "0" ] && {
142 | # uci set $CONFIG.${id}.node=$new_node
143 | # [ -z "$(echo $b_nodes | grep $main_node)" ] && uci add_list $CONFIG.${id}.autoswitch_backup_node=$main_node
144 | # uci commit $CONFIG
145 | # }
146 | echolog "SocksSwitch detection:${id}【$(config_n_get $new_node type):[$(config_n_get $new_node remarks)]】normal,switch to this node!"
147 | /usr/share/${CONFIG}/app.sh socks_node_switch flag=${id} new_node=${new_node}
148 | [ $? -eq 0 ] && {
149 | echolog "SocksSwitch detection:${id}Node switching completed!"
150 | }
151 | return 0
152 | else
153 | test_auto_switch "${b_nodes}" ${new_node}
154 | fi
155 | fi
156 | }
157 |
158 | start() {
159 | id=$1
160 | LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}_socks_auto_switch_${id}.lock
161 | main_node=$(config_n_get $id node)
162 | socks_port=$(config_n_get $id port 0)
163 | delay=$(config_n_get $id autoswitch_testing_time 30)
164 | connect_timeout=$(config_n_get $id autoswitch_connect_timeout 3)
165 | retry_num=$(config_n_get $id autoswitch_retry_num 1)
166 | restore_switch=$(config_n_get $id autoswitch_restore_switch 0)
167 | probe_url=$(config_n_get $id autoswitch_probe_url "https://www.google.com/generate_204")
168 | backup_node=$(config_n_get $id autoswitch_backup_node)
169 | if [ -n "$backup_node" ]; then
170 | backup_node=$(echo "$backup_node" | tr -s ' ' '\n' | uniq | tr -s '\n' ' ')
171 | backup_node_num=$(printf "%s\n" "$backup_node" | wc -w)
172 | if [ "$backup_node_num" -eq 1 ]; then
173 | [ "$main_node" = "$backup_node" ] && return
174 | fi
175 | else
176 | return
177 | fi
178 | while [ -n "$backup_node" ]; do
179 | [ -f "$LOCK_FILE" ] && {
180 | sleep 6s
181 | continue
182 | }
183 | pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && {
184 | # Not detected during specific task execution
185 | sleep 6s
186 | continue
187 | }
188 | touch $LOCK_FILE
189 | test_auto_switch "$backup_node"
190 | rm -f $LOCK_FILE
191 | sleep ${delay}
192 | done
193 | }
194 |
195 | start $@
196 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/view/passwall2/global/backup.htm:
--------------------------------------------------------------------------------
1 | <%
2 | local api = require "luci.passwall2.api"
3 | -%>
4 |
5 |
6 |
<%:Backup and Restore%>
7 |
8 | <%:Backup or Restore Client and Server Configurations.%>
9 |
10 | <%:Note: Restoring configurations across different versions may cause compatibility issues.%>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
<%:Restore Backup File%>
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
92 |
93 |
225 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua:
--------------------------------------------------------------------------------
1 | local api = require "luci.passwall2.api"
2 | local appname = api.appname
3 | local uci = api.uci
4 | local has_ss = api.is_finded("ss-redir")
5 | local has_ss_rust = api.is_finded("sslocal")
6 | local has_singbox = api.finded_com("sing-box")
7 | local has_xray = api.finded_com("xray")
8 | local has_hysteria2 = api.finded_com("hysteria")
9 | local ss_type = {}
10 | local trojan_type = {}
11 | local vmess_type = {}
12 | local vless_type = {}
13 | local hysteria2_type = {}
14 | if has_ss then
15 | local s = "shadowsocks-libev"
16 | table.insert(ss_type, s)
17 | end
18 | if has_ss_rust then
19 | local s = "shadowsocks-rust"
20 | table.insert(ss_type, s)
21 | end
22 | if has_singbox then
23 | local s = "sing-box"
24 | table.insert(trojan_type, s)
25 | table.insert(ss_type, s)
26 | table.insert(vmess_type, s)
27 | table.insert(vless_type, s)
28 | table.insert(hysteria2_type, s)
29 | end
30 | if has_xray then
31 | local s = "xray"
32 | table.insert(trojan_type, s)
33 | table.insert(ss_type, s)
34 | table.insert(vmess_type, s)
35 | table.insert(vless_type, s)
36 | end
37 | if has_hysteria2 then
38 | local s = "hysteria2"
39 | table.insert(hysteria2_type, s)
40 | end
41 |
42 | m = Map(appname)
43 | api.set_apply_on_parse(m)
44 |
45 | if api.is_js_luci() then
46 | m.on_after_apply = function(self)
47 | uci:foreach(appname, "subscribe_list", function(e)
48 | uci:delete(appname, e[".name"], "md5")
49 | end)
50 | uci:commit(appname)
51 | end
52 | end
53 |
54 | m.render = function(self, ...)
55 | Map.render(self, ...)
56 | api.optimize_cbi_ui()
57 | end
58 |
59 | -- [[ Subscribe Settings ]]--
60 | s = m:section(TypedSection, "global_subscribe", "")
61 | s.anonymous = true
62 |
63 | function m.commit_handler(self)
64 | if self.no_commit then
65 | return
66 | end
67 | self.uci:foreach(appname, "subscribe_list", function(e)
68 | self:del(e[".name"], "md5")
69 | end)
70 | end
71 |
72 | o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
73 | o:value("0", translate("Close"))
74 | o:value("1", translate("Discard List"))
75 | o:value("2", translate("Keep List"))
76 | o:value("3", translate("Discard List,But Keep List First"))
77 | o:value("4", translate("Keep List,But Discard List First"))
78 |
79 | o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
80 |
81 | o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
82 |
83 | if #ss_type > 0 then
84 | o = s:option(ListValue, "ss_type", translatef("%s Node Use Type", "Shadowsocks"))
85 | for key, value in pairs(ss_type) do
86 | o:value(value)
87 | end
88 | end
89 |
90 | if #trojan_type > 0 then
91 | o = s:option(ListValue, "trojan_type", translatef("%s Node Use Type", "Trojan"))
92 | for key, value in pairs(trojan_type) do
93 | o:value(value)
94 | end
95 | end
96 |
97 | if #vmess_type > 0 then
98 | o = s:option(ListValue, "vmess_type", translatef("%s Node Use Type", "VMess"))
99 | for key, value in pairs(vmess_type) do
100 | o:value(value)
101 | end
102 | if has_xray then
103 | o.default = "xray"
104 | end
105 | end
106 |
107 | if #vless_type > 0 then
108 | o = s:option(ListValue, "vless_type", translatef("%s Node Use Type", "VLESS"))
109 | for key, value in pairs(vless_type) do
110 | o:value(value)
111 | end
112 | if has_xray then
113 | o.default = "xray"
114 | end
115 | end
116 |
117 | if #hysteria2_type > 0 then
118 | o = s:option(ListValue, "hysteria2_type", translatef("%s Node Use Type", "Hysteria2"))
119 | for key, value in pairs(hysteria2_type) do
120 | o:value(value)
121 | end
122 | if has_hysteria2 then
123 | o.default = "hysteria2"
124 | end
125 | end
126 |
127 | if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 then
128 | o.description = string.format("%s",
129 | translate("The configured type also applies to the core specified when manually importing nodes."))
130 | end
131 |
132 | o = s:option(ListValue, "domain_strategy", "Sing-box " .. translate("Domain Strategy"), translate("Set the default domain resolution strategy for the sing-box node."))
133 | o.default = ""
134 | o:value("", translate("Auto"))
135 | o:value("prefer_ipv4", translate("Prefer IPv4"))
136 | o:value("prefer_ipv6", translate("Prefer IPv6"))
137 | o:value("ipv4_only", translate("IPv4 Only"))
138 | o:value("ipv6_only", translate("IPv6 Only"))
139 |
140 | ---- Subscribe Delete All
141 | o = s:option(DummyValue, "_stop", translate("Delete All Subscribe Node"))
142 | o.rawhtml = true
143 | function o.cfgvalue(self, section)
144 | return string.format(
145 | [[]],
146 | translate("Delete All Subscribe Node"))
147 | end
148 |
149 | o = s:option(DummyValue, "_update", translate("Manual subscription All"))
150 | o.rawhtml = true
151 | o.cfgvalue = function(self, section)
152 | return string.format([[
153 | ]],
154 | translate("Manual subscription All"))
155 | end
156 |
157 | s = m:section(TypedSection, "subscribe_list", "", "" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "")
158 | s.addremove = true
159 | s.anonymous = true
160 | s.sortable = true
161 | s.template = "cbi/tblsection"
162 | s.extedit = api.url("node_subscribe_config", "%s")
163 | function s.create(e, t)
164 | local id = TypedSection.create(e, t)
165 | luci.http.redirect(e.extedit:format(id))
166 | end
167 |
168 | o = s:option(Value, "remark", translate("Remarks"))
169 | o.width = "auto"
170 | o.rmempty = false
171 | o.validate = function(self, value, t)
172 | if value then
173 | local count = 0
174 | m.uci:foreach(appname, "subscribe_list", function(e)
175 | if e[".name"] ~= t and e["remark"] == value then
176 | count = count + 1
177 | end
178 | end)
179 | if count > 0 then
180 | return nil, translate("This remark already exists, please change a new remark.")
181 | end
182 | return value
183 | end
184 | end
185 |
186 | o = s:option(DummyValue, "_node_count", translate("Subscribe Info"))
187 | o.rawhtml = true
188 | o.cfgvalue = function(t, n)
189 | local remark = m:get(n, "remark") or ""
190 | local str = m:get(n, "rem_traffic") or ""
191 | local expired_date = m:get(n, "expired_date") or ""
192 | if expired_date ~= "" then
193 | str = str .. (str ~= "" and "/" or "") .. expired_date
194 | end
195 | str = str ~= "" and " " .. str or ""
196 | local num = 0
197 | m.uci:foreach(appname, "nodes", function(s)
198 | if s["group"] ~= "" and s["group"] == remark then
199 | num = num + 1
200 | end
201 | end)
202 | return string.format("%s%s", translate("Node num") .. ": " .. num, str)
203 | end
204 |
205 | o = s:option(Value, "url", translate("Subscribe URL"))
206 | o.width = "auto"
207 | o.rmempty = false
208 |
209 | o = s:option(DummyValue, "_remove", translate("Delete the subscribed node"))
210 | o.rawhtml = true
211 | function o.cfgvalue(self, section)
212 | local remark = m:get(section, "remark") or ""
213 | return string.format(
214 | [[]],
215 | remark, translate("Delete the subscribed node"))
216 | end
217 |
218 | o = s:option(DummyValue, "_update", translate("Manual subscription"))
219 | o.rawhtml = true
220 | o.cfgvalue = function(self, section)
221 | return string.format([[
222 | ]],
223 | section, translate("Manual subscription"))
224 | end
225 |
226 | s:append(Template(appname .. "/node_subscribe/js"))
227 |
228 | return m
229 |
--------------------------------------------------------------------------------
/luci-app-passwall2/root/usr/share/passwall2/0_default_config:
--------------------------------------------------------------------------------
1 |
2 | config global
3 | option enabled '0'
4 | option node_socks_port '1070'
5 | option localhost_proxy '1'
6 | option client_proxy '1'
7 | option socks_enabled '0'
8 | option acl_enable '0'
9 | option node 'myshunt'
10 | option direct_dns_protocol 'auto'
11 | option direct_dns_query_strategy 'UseIP'
12 | option remote_dns_protocol 'tcp'
13 | option remote_dns '1.1.1.1'
14 | option remote_dns_query_strategy 'UseIPv4'
15 | option dns_hosts 'cloudflare-dns.com 1.1.1.1
16 | dns.google.com 8.8.8.8'
17 | option log_node '1'
18 | option loglevel 'error'
19 |
20 | config global_haproxy
21 | option balancing_enable '0'
22 |
23 | config global_delay
24 | option start_daemon '1'
25 | option start_delay '60'
26 |
27 | config global_forwarding
28 | option tcp_no_redir_ports 'disable'
29 | option udp_no_redir_ports 'disable'
30 | option tcp_redir_ports '22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418'
31 | option udp_redir_ports '1:65535'
32 | option accept_icmp '0'
33 | option use_nft '0'
34 | option tcp_proxy_way 'redirect'
35 | option ipv6_tproxy '0'
36 |
37 | config global_xray
38 | option sniffing_override_dest '0'
39 |
40 | config global_other
41 | option auto_detection_time 'tcping'
42 | option show_node_info '0'
43 |
44 | config global_rules
45 | option auto_update '0'
46 | option geosite_update '1'
47 | option geoip_update '1'
48 | option v2ray_location_asset '/usr/share/v2ray/'
49 | option enable_geoview '1'
50 |
51 | config global_app
52 | option xray_file '/usr/bin/xray'
53 | option hysteria_file '/usr/bin/hysteria'
54 | option sing_box_file '/usr/bin/sing-box'
55 |
56 | config global_subscribe
57 | option filter_keyword_mode '1'
58 | list filter_discard_list '距离下次重置剩余'
59 | list filter_discard_list '套餐到期'
60 | list filter_discard_list '过期时间'
61 | list filter_discard_list '剩余流量'
62 | list filter_discard_list 'QQ群'
63 | list filter_discard_list '官网'
64 |
65 | config global_singbox
66 | option sniff_override_destination '0'
67 | option geoip_path '/usr/share/singbox/geoip.db'
68 | option geoip_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.db'
69 | option geosite_path '/usr/share/singbox/geosite.db'
70 | option geosite_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.db'
71 |
72 | config nodes 'myshunt'
73 | option remarks '分流总节点'
74 | option type 'Xray'
75 | option protocol '_shunt'
76 | option DirectGame '_direct'
77 | option ProxyGame '_default'
78 | option Direct '_direct'
79 | option GooglePlay '_default'
80 | option Proxy '_default'
81 | option China '_direct'
82 | option QUIC '_blackhole'
83 | option default_node '_direct'
84 | option domainStrategy 'IPOnDemand'
85 | option domainMatcher 'hybrid'
86 |
87 | config shunt_rules 'DirectGame'
88 | option remarks 'DirectGame'
89 | option network 'tcp,udp'
90 | option domain_list '# steam直连域名获取国内CDN走国内线路下载
91 | cm.steampowered.com
92 | steamserver.net
93 |
94 | # steam国内CDN华为云
95 | steampipe.steamcontent.tnkjmec.com
96 | # steam国内CDN白山云
97 | st.dl.eccdnx.com
98 | st.dl.bscstorage.net
99 | st.dl.pinyuncloud.com
100 | # steam国内CDN新流云(原金山云)(支持ipv6)
101 | dl.steam.clngaa.com
102 | # steam国内CDN网宿
103 | cdn.mileweb.cs.steampowered.com.8686c.com
104 | cdn-ws.content.steamchina.com
105 | # steam国内CDN腾讯云 (蒸汽中国独占)
106 | cdn-qc.content.steamchina.com
107 | # steam国内CDN阿里云(支持ipv6)
108 | cdn-ali.content.steamchina.com
109 | xz.pphimalayanrt.com
110 | lv.queniujq.cn
111 | alibaba.cdn.steampipe.steamcontent.com
112 |
113 | # 国内游戏geosite域名
114 | geosite:category-games@cn
115 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-category-games%40cn.srs
116 | '
117 |
118 | option ip_list '# steam直连IP
119 | 45.121.184.0/24
120 | 103.10.124.0/23
121 | 103.28.54.0/24
122 | 146.66.152.0/24
123 | 146.66.155.0/24
124 | 153.254.86.0/24
125 | 155.133.224.0/22
126 | 155.133.230.0/24
127 | 155.133.232.0/23
128 | 155.133.234.0/24
129 | 155.133.236.0/22
130 | 155.133.240.0/23
131 | 155.133.244.0/23
132 | 155.133.246.0/24
133 | 155.133.248.0/21
134 | 162.254.192.0/21
135 | 185.25.182.0/23
136 | 190.217.32.0/22
137 | 192.69.96.0/22
138 | 205.196.6.0/24
139 | 208.64.200.0/22
140 | 208.78.164.0/22
141 | 205.185.194.0/24'
142 |
143 | config shunt_rules 'ProxyGame'
144 | option remarks 'ProxyGame'
145 | option domain_list '# steam 商店/客服/聊天/网页布局/API/二维码 代理URL
146 | steamcommunity.com
147 | www.steamcommunity.com
148 | store.steampowered.com
149 | checkout.steampowered.com
150 | api.steampowered.com
151 | help.steampowered.com
152 | login.steampowered.com
153 | store.akamai.steamstatic.com
154 | steambroadcast.akamaized.net
155 | steamvideo-a.akamaihd.net
156 | steamusercontent-a.akamaihd.net
157 | steamstore-a.akamaihd.net
158 | steamcommunity-a.akamaihd.net
159 | steamcdn-a.akamaihd.net
160 | steamuserimages-a.akamaihd.net
161 | community.akamai.steamstatic.com
162 | avatars.akamai.steamstatic.com
163 | community.steamstatic.com
164 | cdn.akamai.steamstatic.com
165 | avatars.steamstatic.com
166 | shared.akamai.steamstatic.com
167 | clan.akamai.steamstatic.com
168 | cdn.cloudflare.steamstatic.com
169 | community.cloudflare.steamstatic.com
170 | store.cloudflare.steamstatic.com
171 | avatars.cloudflare.steamstatic.com
172 | clan.cloudflare.steamstatic.com
173 | shared.cloudflare.steamstatic.com
174 | steam-chat.com
175 | steamcloud-ugc.storage.googleapis.com
176 | steamcloud-eu-ams.storage.googleapis.com
177 | steamcloud-eu-fra.storage.googleapis.com
178 | steamcloud-finland.storage.googleapis.com
179 | steamcloud-saopaulo.storage.googleapis.com
180 | steamcloud-singapore.storage.googleapis.com
181 | steamcloud-sydney.storage.googleapis.com
182 | steamcloud-taiwan.storage.googleapis.com
183 | steamcloud-eu.storage.googleapis.com
184 |
185 | geosite:category-games
186 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-category-games.srs'
187 |
188 | config shunt_rules 'Direct'
189 | option network 'tcp,udp'
190 | option remarks 'Direct'
191 | option ip_list '114.114.114.114
192 | 114.114.115.115
193 | 223.5.5.5
194 | 223.6.6.6
195 | 119.29.29.29
196 | 180.76.76.76
197 | '
198 | option domain_list 'apple.com
199 | microsoft.com
200 | dyndns.com
201 | steamcontent.com
202 | dl.steam.clngaa.com
203 | dl.steam.ksyna.com
204 | st.dl.bscstorage.net
205 | st.dl.eccdnx.com
206 | st.dl.pinyuncloud.com
207 | cdn.mileweb.cs.steampowered.com.8686c.com
208 | cdn-ws.content.steamchina.com
209 | cdn-qc.content.steamchina.com
210 | cdn-ali.content.steamchina.com
211 | epicgames-download1-1251447533.file.myqcloud.com'
212 |
213 | config shunt_rules 'GooglePlay'
214 | option remarks 'GooglePlay'
215 | option network 'tcp,udp'
216 | option domain_list 'domain:googleapis.cn
217 | domain:googleapis.com
218 | domain:xn--ngstr-lra8j.com'
219 |
220 | config shunt_rules 'Netflix'
221 | option remarks 'Netflix'
222 | option network 'tcp,udp'
223 | option domain_list 'geosite:netflix
224 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-netflix.srs'
225 |
226 | config shunt_rules 'OpenAI'
227 | option remarks 'OpenAI'
228 | option network 'tcp,udp'
229 | option domain_list 'geosite:openai
230 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-openai.srs'
231 |
232 | config shunt_rules 'Proxy'
233 | option network 'tcp,udp'
234 | option remarks 'Proxy'
235 | option domain_list 'geosite:geolocation-!cn
236 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-geolocation-!cn.srs'
237 | option ip_list '149.154.160.0/20
238 | 91.108.4.0/22
239 | 91.108.56.0/24
240 | 109.239.140.0/24
241 | 67.198.55.0/24
242 | 8.8.4.4
243 | 8.8.8.8
244 | 208.67.222.222
245 | 208.67.220.220
246 | 1.1.1.1
247 | 1.1.1.2
248 | 1.0.0.1
249 | 9.9.9.9
250 | 149.112.112.112
251 | 2001:67c:4e8::/48
252 | 2001:b28:f23c::/48
253 | 2001:b28:f23d::/48
254 | 2001:b28:f23f::/48
255 | 2001:b28:f242::/48
256 | 2001:4860:4860::8888
257 | 2001:4860:4860::8844
258 | 2606:4700:4700::1111
259 | 2606:4700:4700::1001
260 | '
261 |
262 | config shunt_rules 'China'
263 | option remarks 'China'
264 | option network 'tcp,udp'
265 | option ip_list 'geoip:cn
266 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geoip/geoip-cn.srs'
267 | option domain_list 'geosite:cn
268 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-cn.srs'
269 |
270 | config shunt_rules 'QUIC'
271 | option remarks 'QUIC'
272 | option port '443'
273 | option network 'udp'
274 |
275 | config shunt_rules 'UDP'
276 | option remarks 'UDP'
277 | option network 'udp'
278 |
--------------------------------------------------------------------------------
/luci-app-passwall2/root/usr/share/passwall2/haproxy.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local api = require ("luci.passwall2.api")
4 | local appname = "passwall2"
5 | local fs = api.fs
6 | local jsonc = api.jsonc
7 | local uci = api.uci
8 | local sys = api.sys
9 |
10 | local log = function(...)
11 | api.log(...)
12 | end
13 |
14 | function get_ip_port_from(str)
15 | local result_port = sys.exec("echo -n " .. str .. " | sed -n 's/^.*[:#]\\([0-9]*\\)$/\\1/p'")
16 | local result_ip = sys.exec(string.format("__host=%s;__varport=%s;", str, result_port) .. "echo -n ${__host%%${__varport:+[:#]${__varport}*}}")
17 | return result_ip, result_port
18 | end
19 |
20 | local new_port
21 | local function get_new_port()
22 | if new_port then
23 | new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port %s tcp)", appname, new_port + 1)))
24 | else
25 | new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port auto tcp)", appname)))
26 | end
27 | return new_port
28 | end
29 |
30 | local var = api.get_args(arg)
31 | local haproxy_path = var["-path"]
32 | local haproxy_conf = var["-conf"]
33 | local haproxy_dns = var["-dns"] or "119.29.29.29:53,223.5.5.5:53"
34 |
35 | local cpu_thread = sys.exec('echo -n $(cat /proc/cpuinfo | grep "processor" | wc -l)') or "1"
36 | local health_check_type = uci:get(appname, "@global_haproxy[0]", "health_check_type") or "tcp"
37 | local health_check_inter = uci:get(appname, "@global_haproxy[0]", "health_check_inter") or "10"
38 | local console_port = uci:get(appname, "@global_haproxy[0]", "console_port")
39 | local bind_local = uci:get(appname, "@global_haproxy[0]", "bind_local") or "0"
40 | local bind_address = "0.0.0.0"
41 | if bind_local == "1" then bind_address = "127.0.0.1" end
42 |
43 | log("HAPROXY load balancing:")
44 | log(string.format(" * console port:%s", console_port))
45 | fs.mkdir(haproxy_path)
46 | local haproxy_file = haproxy_path .. "/" .. haproxy_conf
47 |
48 | local f_out = io.open(haproxy_file, "a")
49 |
50 | local haproxy_config = [[
51 | global
52 | daemon
53 | log 127.0.0.1 local2
54 | maxconn 60000
55 | stats socket {{path}}/haproxy.sock
56 | nbthread {{nbthread}}
57 | external-check
58 | insecure-fork-wanted
59 |
60 | defaults
61 | mode tcp
62 | log global
63 | option tcplog
64 | option dontlognull
65 | option http-server-close
66 | #option forwardfor except 127.0.0.0/8
67 | option redispatch
68 | retries 2
69 | timeout http-request 10s
70 | timeout queue 1m
71 | timeout connect 10s
72 | timeout client 1m
73 | timeout server 1m
74 | timeout http-keep-alive 10s
75 | timeout check 10s
76 | maxconn 3000
77 |
78 | resolvers mydns
79 | resolve_retries 1
80 | timeout resolve 5s
81 | hold valid 600s
82 | {{dns}}
83 | ]]
84 |
85 | haproxy_config = haproxy_config:gsub("{{path}}", haproxy_path)
86 | haproxy_config = haproxy_config:gsub("{{nbthread}}", cpu_thread)
87 |
88 | local mydns = ""
89 | local index = 0
90 | string.gsub(haproxy_dns, '[^' .. "," .. ']+', function(w)
91 | index = index + 1
92 | local s = w:gsub("#", ":")
93 | if not s:find(":") then
94 | s = s .. ":53"
95 | end
96 | mydns = mydns .. (index > 1 and "\n" or "") .. " " .. string.format("nameserver dns%s %s", index, s)
97 | end)
98 | haproxy_config = haproxy_config:gsub("{{dns}}", mydns)
99 |
100 | f_out:write(haproxy_config)
101 |
102 | local listens = {}
103 |
104 | uci:foreach(appname, "haproxy_config", function(t)
105 | if t.enabled == "1" then
106 | local server_remark
107 | local server_address
108 | local server_port
109 | local lbss = t.lbss
110 | local listen_port = tonumber(t.haproxy_port) or 0
111 | local server_node = uci:get_all(appname, lbss)
112 | if server_node and server_node.address and server_node.port then
113 | server_remark = server_node.address .. ":" .. server_node.port
114 | server_address = server_node.address
115 | server_port = server_node.port
116 | t.origin_address = server_address
117 | t.origin_port = server_port
118 | if health_check_type == "passwall_logic" then
119 | if server_node.type ~= "Socks" then
120 | local relay_port = server_node.port
121 | new_port = get_new_port()
122 | local config_file = string.format("haproxy_%s_%s.json", t[".name"], new_port)
123 | sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
124 | appname,
125 | string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s",
126 | new_port, --flag
127 | server_node[".name"], --node
128 | "127.0.0.1", --bind
129 | new_port, --socks port
130 | config_file --config file
131 | )
132 | )
133 | )
134 | server_address = "127.0.0.1"
135 | server_port = new_port
136 | end
137 | end
138 | else
139 | server_address, server_port = get_ip_port_from(lbss)
140 | server_remark = server_address .. ":" .. server_port
141 | t.origin_address = server_address
142 | t.origin_port = server_port
143 | end
144 | if server_address and server_port and listen_port > 0 then
145 | if not listens[listen_port] then
146 | listens[listen_port] = {}
147 | end
148 | t.server_remark = server_remark
149 | t.server_address = server_address
150 | t.server_port = server_port
151 | table.insert(listens[listen_port], t)
152 | else
153 | log(" - throw away1obviously invalid node")
154 | end
155 | end
156 | end)
157 |
158 | local sortTable = {}
159 | for i in pairs(listens) do
160 | if i ~= nil then
161 | table.insert(sortTable, i)
162 | end
163 | end
164 | table.sort(sortTable, function(a,b) return (a < b) end)
165 |
166 | for i, port in pairs(sortTable) do
167 | log(" + Entrance %s:%s" % {bind_address, port})
168 |
169 | f_out:write("\n" .. string.format([[
170 | listen %s
171 | bind %s:%s
172 | mode tcp
173 | balance roundrobin
174 | ]], port, bind_address, port))
175 |
176 | if health_check_type == "passwall_logic" then
177 | f_out:write(string.format([[
178 | option external-check
179 | external-check command "/usr/share/passwall2/haproxy_check.sh"
180 | ]], port, port))
181 | end
182 |
183 | local count_M, count_B = 1, 1
184 | for i, o in ipairs(listens[port]) do
185 | local remark = o.server_remark or ""
186 | -- Prevent duplicate names from causing inability to run
187 | if tostring(o.backup) ~= "1" then
188 | remark = "M" .. count_M .. "-" .. remark
189 | count_M = count_M + 1
190 | else
191 | remark = "B" .. count_B .. "-" .. remark
192 | count_B = count_B + 1
193 | end
194 | local server = o.server_address .. ":" .. o.server_port
195 | local server_conf = "server {{remark}} {{server}} weight {{weight}} {{resolvers}} check inter {{inter}} rise 1 fall 3 {{backup}}"
196 | server_conf = server_conf:gsub("{{remark}}", remark)
197 | server_conf = server_conf:gsub("{{server}}", server)
198 | server_conf = server_conf:gsub("{{weight}}", o.lbweight)
199 | local resolvers = "resolvers mydns"
200 | if api.is_ip(o.server_address) then
201 | resolvers = ""
202 | end
203 | server_conf = server_conf:gsub("{{resolvers}}", resolvers)
204 | server_conf = server_conf:gsub("{{inter}}", tonumber(health_check_inter) .. "s")
205 | server_conf = server_conf:gsub("{{backup}}", tostring(o.backup) == "1" and "backup" or "")
206 |
207 | f_out:write(" " .. server_conf .. "\n")
208 |
209 | if o.export ~= "0" then
210 | sys.call(string.format("/usr/share/passwall2/app.sh add_ip2route %s %s", o.origin_address, o.export))
211 | end
212 |
213 | log(string.format(" | - exit node:%s:%s,weight:%s", o.origin_address, o.origin_port, o.lbweight))
214 | end
215 | end
216 |
217 | --Console configuration
218 | local console_user = uci:get(appname, "@global_haproxy[0]", "console_user")
219 | local console_password = uci:get(appname, "@global_haproxy[0]", "console_password")
220 | local str = [[
221 | listen console
222 | bind 0.0.0.0:%s
223 | mode http
224 | stats refresh 30s
225 | stats uri /
226 | stats admin if TRUE
227 | %s
228 | ]]
229 | f_out:write("\n" .. string.format(str, console_port, (console_user and console_user ~= "" and console_password and console_password ~= "") and "stats auth " .. console_user .. ":" .. console_password or ""))
230 |
231 | f_out:close()
232 |
233 | --Built-in health checksURL
234 | if health_check_type == "passwall_logic" then
235 | local probeUrl = uci:get(appname, "@global_haproxy[0]", "health_probe_url") or "https://www.google.com/generate_204"
236 | local f_url = io.open(haproxy_path .. "/Probe_URL", "w")
237 | f_url:write(probeUrl)
238 | f_url:close()
239 | end
240 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua:
--------------------------------------------------------------------------------
1 | local api = require "luci.passwall2.api"
2 | local appname = api.appname
3 |
4 | m = Map(appname)
5 | m.redirect = api.url("node_subscribe")
6 | api.set_apply_on_parse(m)
7 |
8 | if not arg[1] or not m:get(arg[1]) then
9 | luci.http.redirect(m.redirect)
10 | end
11 |
12 | m.render = function(self, ...)
13 | Map.render(self, ...)
14 | api.optimize_cbi_ui()
15 | end
16 |
17 | local has_ss = api.is_finded("ss-redir")
18 | local has_ss_rust = api.is_finded("sslocal")
19 | local has_singbox = api.finded_com("sing-box")
20 | local has_xray = api.finded_com("xray")
21 | local has_hysteria2 = api.finded_com("hysteria")
22 | local ss_type = {}
23 | local trojan_type = {}
24 | local vmess_type = {}
25 | local vless_type = {}
26 | local hysteria2_type = {}
27 | if has_ss then
28 | local s = "shadowsocks-libev"
29 | table.insert(ss_type, s)
30 | end
31 | if has_ss_rust then
32 | local s = "shadowsocks-rust"
33 | table.insert(ss_type, s)
34 | end
35 | if has_singbox then
36 | local s = "sing-box"
37 | table.insert(trojan_type, s)
38 | table.insert(ss_type, s)
39 | table.insert(vmess_type, s)
40 | table.insert(vless_type, s)
41 | table.insert(hysteria2_type, s)
42 | end
43 | if has_xray then
44 | local s = "xray"
45 | table.insert(trojan_type, s)
46 | table.insert(ss_type, s)
47 | table.insert(vmess_type, s)
48 | table.insert(vless_type, s)
49 | end
50 | if has_hysteria2 then
51 | local s = "hysteria2"
52 | table.insert(hysteria2_type, s)
53 | end
54 | local nodes_table = {}
55 | for k, e in ipairs(api.get_valid_nodes()) do
56 | if e.node_type == "normal" then
57 | nodes_table[#nodes_table + 1] = {
58 | id = e[".name"],
59 | remark = e["remark"],
60 | type = e["type"],
61 | add_mode = e["add_mode"],
62 | chain_proxy = e["chain_proxy"]
63 | }
64 | end
65 | end
66 |
67 | s = m:section(NamedSection, arg[1])
68 | s.addremove = false
69 | s.dynamic = false
70 |
71 | function m.commit_handler(self)
72 | self:del(arg[1], "md5")
73 | end
74 |
75 | o = s:option(Value, "remark", translate("Subscribe Remark"))
76 | o.rmempty = false
77 |
78 | o = s:option(TextValue, "url", translate("Subscribe URL"))
79 | o.rows = 5
80 | o.rmempty = false
81 | o.validate = function(self, value)
82 | if not value or value == "" then
83 | return nil, translate("URL cannot be empty")
84 | end
85 | return value:gsub("%s+", ""):gsub("%z", "")
86 | end
87 |
88 | o = s:option(Flag, "allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
89 | o.default = "1"
90 | o.rmempty = false
91 |
92 | o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
93 | o.default = "5"
94 | o:value("0", translate("Close"))
95 | o:value("1", translate("Discard List"))
96 | o:value("2", translate("Keep List"))
97 | o:value("3", translate("Discard List,But Keep List First"))
98 | o:value("4", translate("Keep List,But Discard List First"))
99 | o:value("5", translate("Use global config"))
100 |
101 | o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
102 | o:depends("filter_keyword_mode", "1")
103 | o:depends("filter_keyword_mode", "3")
104 | o:depends("filter_keyword_mode", "4")
105 |
106 | o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
107 | o:depends("filter_keyword_mode", "2")
108 | o:depends("filter_keyword_mode", "3")
109 | o:depends("filter_keyword_mode", "4")
110 |
111 | if #ss_type > 0 then
112 | o = s:option(ListValue, "ss_type", translatef("%s Node Use Type", "Shadowsocks"))
113 | o.default = "global"
114 | o:value("global", translate("Use global config"))
115 | for key, value in pairs(ss_type) do
116 | o:value(value)
117 | end
118 | end
119 |
120 | if #trojan_type > 0 then
121 | o = s:option(ListValue, "trojan_type", translatef("%s Node Use Type", "Trojan"))
122 | o.default = "global"
123 | o:value("global", translate("Use global config"))
124 | for key, value in pairs(trojan_type) do
125 | o:value(value)
126 | end
127 | end
128 |
129 | if #vmess_type > 0 then
130 | o = s:option(ListValue, "vmess_type", translatef("%s Node Use Type", "VMess"))
131 | o.default = "global"
132 | o:value("global", translate("Use global config"))
133 | for key, value in pairs(vmess_type) do
134 | o:value(value)
135 | end
136 | end
137 |
138 | if #vless_type > 0 then
139 | o = s:option(ListValue, "vless_type", translatef("%s Node Use Type", "VLESS"))
140 | o.default = "global"
141 | o:value("global", translate("Use global config"))
142 | for key, value in pairs(vless_type) do
143 | o:value(value)
144 | end
145 | end
146 |
147 | if #hysteria2_type > 0 then
148 | o = s:option(ListValue, "hysteria2_type", translatef("%s Node Use Type", "Hysteria2"))
149 | o.default = "global"
150 | o:value("global", translate("Use global config"))
151 | for key, value in pairs(hysteria2_type) do
152 | o:value(value)
153 | end
154 | end
155 |
156 | o = s:option(ListValue, "domain_strategy", "Sing-box " .. translate("Domain Strategy"), translate("Set the default domain resolution strategy for the sing-box node."))
157 | o.default = "global"
158 | o:value("global", translate("Use global config"))
159 | o:value("", translate("Auto"))
160 | o:value("prefer_ipv4", translate("Prefer IPv4"))
161 | o:value("prefer_ipv6", translate("Prefer IPv6"))
162 | o:value("ipv4_only", translate("IPv4 Only"))
163 | o:value("ipv6_only", translate("IPv6 Only"))
164 |
165 | ---- Enable auto update subscribe
166 | o = s:option(Flag, "auto_update", translate("Enable auto update subscribe"))
167 | o.default = 0
168 | o.rmempty = false
169 |
170 | ---- Week Update
171 | o = s:option(ListValue, "week_update", translate("Update Mode"))
172 | o:value(8, translate("Loop Mode"))
173 | o:value(7, translate("Every day"))
174 | o:value(1, translate("Every Monday"))
175 | o:value(2, translate("Every Tuesday"))
176 | o:value(3, translate("Every Wednesday"))
177 | o:value(4, translate("Every Thursday"))
178 | o:value(5, translate("Every Friday"))
179 | o:value(6, translate("Every Saturday"))
180 | o:value(0, translate("Every Sunday"))
181 | o.default = 7
182 | o:depends("auto_update", true)
183 | o.rmempty = true
184 |
185 | ---- Time Update
186 | o = s:option(ListValue, "time_update", translate("Update Time(every day)"))
187 | for t = 0, 23 do o:value(t, t .. ":00") end
188 | o.default = 0
189 | o:depends("week_update", "0")
190 | o:depends("week_update", "1")
191 | o:depends("week_update", "2")
192 | o:depends("week_update", "3")
193 | o:depends("week_update", "4")
194 | o:depends("week_update", "5")
195 | o:depends("week_update", "6")
196 | o:depends("week_update", "7")
197 | o.rmempty = true
198 |
199 | ---- Interval Update
200 | o = s:option(ListValue, "interval_update", translate("Update Interval(hour)"))
201 | for t = 1, 24 do o:value(t, t .. " " .. translate("hour")) end
202 | o.default = 2
203 | o:depends("week_update", "8")
204 | o.rmempty = true
205 |
206 | o = s:option(ListValue, "access_mode", translate("Subscribe URL Access Method"))
207 | o.default = ""
208 | o:value("", translate("Auto"))
209 | o:value("direct", translate("Direct Connection"))
210 | o:value("proxy", translate("Proxy"))
211 |
212 | o = s:option(Value, "user_agent", translate("User-Agent"))
213 | o.default = "v2rayN/9.99"
214 | o:value("curl", "Curl")
215 | o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux")
216 | o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows")
217 | o:value("Passwall2/OpenWrt", "PassWall2")
218 | o:value("v2rayN/9.99", "v2rayN")
219 |
220 | o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
221 | o:value("", translate("Close(Not use)"))
222 | o:value("1", translate("Preproxy Node"))
223 | o:value("2", translate("Landing Node"))
224 |
225 | local descrStr = "Chained proxy works only with Xray or Sing-box nodes. "
226 | descrStr = descrStr .. "The chained node must be the same type as your subscription node (Xray with Xray, Sing-box with Sing-box). "
227 | descrStr = descrStr .. "You can only use manual or imported nodes as chained nodes."
228 | descrStr = translate(descrStr) .. " " .. translate("Only support a layer of proxy.")
229 |
230 | o = s:option(ListValue, "preproxy_node", translate("Preproxy Node"))
231 | o:depends({ ["chain_proxy"] = "1" })
232 | o.description = descrStr
233 |
234 | o = s:option(ListValue, "to_node", translate("Landing Node"))
235 | o:depends({ ["chain_proxy"] = "2" })
236 | o.description = descrStr
237 |
238 | for k, v in pairs(nodes_table) do
239 | if (v.type == "Xray" or v.type == "sing-box") and (not v.chain_proxy or v.chain_proxy == "") and v.add_mode ~= "2" then
240 | s.fields["preproxy_node"]:value(v.id, v.remark)
241 | s.fields["to_node"]:value(v.id, v.remark)
242 | end
243 | end
244 |
245 | return m
246 |
--------------------------------------------------------------------------------
/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua:
--------------------------------------------------------------------------------
1 | local api = require "luci.passwall2.api"
2 | local appname = api.appname
3 | local datatypes = api.datatypes
4 |
5 | m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
6 | m.redirect = api.url("rule")
7 | api.set_apply_on_parse(m)
8 |
9 | if not arg[1] or not m:get(arg[1]) then
10 | luci.http.redirect(m.redirect)
11 | end
12 |
13 | -- Add inline CSS to map description
14 | m.description = (m.description or "") .. [[
15 |
27 | ]]
28 |
29 | function clean_text(text)
30 | local nbsp = string.char(0xC2, 0xA0) -- 不间断空格(U+00A0)
31 | local fullwidth_space = string.char(0xE3, 0x80, 0x80) -- 全角空格(U+3000)
32 | return text
33 | :gsub("\t", " ")
34 | :gsub(nbsp, " ")
35 | :gsub(fullwidth_space, " ")
36 | :gsub("^%s+", "")
37 | :gsub("%s+$", "\n")
38 | :gsub("\r\n", "\n")
39 | :gsub("[ \t]*\n[ \t]*", "\n")
40 | end
41 |
42 | s = m:section(NamedSection, arg[1], "shunt_rules", "")
43 | s.addremove = false
44 | s.dynamic = false
45 |
46 | remarks = s:option(Value, "remarks", translate("Remarks"))
47 | remarks.default = arg[1]
48 | remarks.rmempty = false
49 |
50 | protocol = s:option(MultiValue, "protocol", translate("Protocol"))
51 | protocol:value("http")
52 | protocol:value("tls")
53 | protocol:value("bittorrent")
54 | protocol.widget = "checkbox"
55 | protocol.default = nil
56 | protocol.optional = false
57 |
58 | o = s:option(MultiValue, "inbound", translate("Inbound Tag"))
59 | o:value("tproxy", translate("Transparent proxy"))
60 | o:value("socks", "Socks")
61 | o.widget = "checkbox"
62 | o.default = nil
63 | o.optional = false
64 |
65 | network = s:option(ListValue, "network", translate("Network"))
66 | network:value("tcp,udp", "TCP UDP")
67 | network:value("tcp", "TCP")
68 | network:value("udp", "UDP")
69 |
70 | source = s:option(DynamicList, "source", translate("Source"))
71 | source.description = "
"
76 | source.cast = "string"
77 | source.cfgvalue = function(self, section)
78 | local value
79 | if self.tag_error[section] then
80 | value = self:formvalue(section)
81 | else
82 | value = self.map:get(section, self.option)
83 | if type(value) == "string" then
84 | local value2 = {}
85 | string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end)
86 | value = value2
87 | end
88 | end
89 | return value
90 | end
91 | source.validate = function(self, value, t)
92 | local err = {}
93 | for _, v in ipairs(value) do
94 | local flag = false
95 | if datatypes.ip4addr(v) then
96 | flag = true
97 | end
98 |
99 | if flag == false and v:find("geoip:") and v:find("geoip:") == 1 then
100 | flag = true
101 | end
102 |
103 | if flag == false then
104 | err[#err + 1] = v
105 | end
106 | end
107 |
108 | if #err > 0 then
109 | self:add_error(t, "invalid", translate("Not true format, please re-enter!"))
110 | for _, v in ipairs(err) do
111 | self:add_error(t, "invalid", v)
112 | end
113 | end
114 |
115 | return value
116 | end
117 |
118 | local dynamicList_write = function(self, section, value)
119 | local t = {}
120 | local t2 = {}
121 | if type(value) == "table" then
122 | local x
123 | for _, x in ipairs(value) do
124 | if x and #x > 0 then
125 | if not t2[x] then
126 | t2[x] = x
127 | t[#t+1] = x
128 | end
129 | end
130 | end
131 | else
132 | t = { value }
133 | end
134 | t = table.concat(t, " ")
135 | return DynamicList.write(self, section, t)
136 | end
137 |
138 | source.write = dynamicList_write
139 |
140 | sourcePort = s:option(Value, "sourcePort", translate("Source port"))
141 |
142 | port = s:option(Value, "port", translate("port"))
143 |
144 | domain_list = s:option(TextValue, "domain_list", translate("Domain"))
145 | domain_list.rows = 10
146 | domain_list.wrap = "off"
147 | domain_list.validate = function(self, value)
148 | local hosts= {}
149 | value = clean_text(value)
150 | string.gsub(value, "[^\r\n]+", function(w) table.insert(hosts, w) end)
151 | for index, host in ipairs(hosts) do
152 | local flag = 1
153 | local tmp_host = host
154 | if not host:find("#") and host:find("%s") then
155 | elseif host:find("regexp:") and host:find("regexp:") == 1 then
156 | flag = 0
157 | elseif host:find("domain:.") and host:find("domain:.") == 1 then
158 | tmp_host = host:gsub("domain:", "")
159 | elseif host:find("full:.") and host:find("full:.") == 1 then
160 | tmp_host = host:gsub("full:", "")
161 | elseif host:find("geosite:") and host:find("geosite:") == 1 then
162 | flag = 0
163 | elseif host:find("ext:") and host:find("ext:") == 1 then
164 | flag = 0
165 | elseif host:find("rule-set:", 1, true) == 1 or host:find("rs:") == 1 then
166 | local w = host:sub(host:find(":") + 1, #host)
167 | if w:find("local:") == 1 or w:find("remote:") == 1 then
168 | flag = 0
169 | end
170 | elseif host:find("#") and host:find("#") == 1 then
171 | flag = 0
172 | end
173 | if flag == 1 then
174 | if not datatypes.hostname(tmp_host) then
175 | return nil, tmp_host .. " " .. translate("Not valid domain name, please re-enter!")
176 | end
177 | end
178 | end
179 | return value
180 | end
181 | domain_list.description = "
"
182 | .. "
" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.") .. "
"
183 | .. "
" .. translate("Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.") .. "
"
184 | .. "
" .. translate("Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.") .. "
"
185 | .. "
" .. translate("Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.") .. "
"
186 | .. "
" .. translate("Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.") .. "
"
187 | .. "
"
188 | .. translate("Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'")
189 | .. "
" .. translate("Annotation: Begining with #") .. "
"
195 | .. "
"
196 | ip_list = s:option(TextValue, "ip_list", "IP")
197 | ip_list.rows = 10
198 | ip_list.wrap = "off"
199 | ip_list.validate = function(self, value)
200 | local ipmasks= {}
201 | value = clean_text(value)
202 | string.gsub(value, "[^\r\n]+", function(w) table.insert(ipmasks, w) end)
203 | for index, ipmask in ipairs(ipmasks) do
204 | if ipmask:find("geoip:") and ipmask:find("geoip:") == 1 and not ipmask:find("%s") then
205 | elseif ipmask:find("ext:") and ipmask:find("ext:") == 1 and not ipmask:find("%s") then
206 | elseif ipmask:find("rule-set:", 1, true) == 1 or ipmask:find("rs:") == 1 then
207 | local w = ipmask:sub(ipmask:find(":") + 1, #ipmask)
208 | if w:find("local:") == 1 or w:find("remote:") == 1 then
209 | flag = 0
210 | end
211 | elseif ipmask:find("#") and ipmask:find("#") == 1 then
212 | else
213 | if not (datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask)) then
214 | return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
215 | end
216 | end
217 | end
218 | return value
219 | end
220 | ip_list.description = "
"
221 | .. "
" .. translate("IP: such as '127.0.0.1'.") .. "
"
222 | .. "
" .. translate("CIDR: such as '127.0.0.0/8'.") .. "
"
223 | .. "
" .. translate("GeoIP: such as 'geoip:cn'. It begins with geoip: (lower case) and followed by two letter of country code.") .. "
"
224 | .. "
"
225 | .. translate("Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'")
226 | .. "
222 |
223 |
370 |
--------------------------------------------------------------------------------
/luci-app-passwall2/root/usr/share/passwall2/helper_dnsmasq.lua:
--------------------------------------------------------------------------------
1 | local api = require "luci.passwall2.api"
2 | local appname = "passwall2"
3 | local uci = api.uci
4 | local sys = api.sys
5 | local fs = api.fs
6 | local datatypes = api.datatypes
7 | local TMP = {}
8 |
9 | local function tinsert(table_name, val)
10 | if table_name and type(table_name) == "table" then
11 | if not TMP[table_name] then
12 | TMP[table_name] = {}
13 | end
14 | if TMP[table_name][val] then
15 | return false
16 | end
17 | table.insert(table_name, val)
18 | TMP[table_name][val] = true
19 | return true
20 | end
21 | return false
22 | end
23 |
24 | local function backup_servers()
25 | local DNSMASQ_DNS = uci:get("dhcp", "@dnsmasq[0]", "server")
26 | if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then
27 | uci:set(appname, "@global[0]", "dnsmasq_servers", DNSMASQ_DNS)
28 | api.uci_save(uci, appname, true)
29 | end
30 | end
31 |
32 | local function restore_servers()
33 | local dns_table = {}
34 | local DNSMASQ_DNS = uci:get("dhcp", "@dnsmasq[0]", "server")
35 | if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then
36 | for k, v in ipairs(DNSMASQ_DNS) do
37 | tinsert(dns_table, v)
38 | end
39 | end
40 | local OLD_SERVER = uci:get(appname, "@global[0]", "dnsmasq_servers")
41 | if OLD_SERVER and #OLD_SERVER > 0 then
42 | for k, v in ipairs(OLD_SERVER) do
43 | tinsert(dns_table, v)
44 | end
45 | uci:delete(appname, "@global[0]", "dnsmasq_servers")
46 | api.uci_save(uci, appname, true)
47 | end
48 | if dns_table and #dns_table > 0 then
49 | uci:set_list("dhcp", "@dnsmasq[0]", "server", dns_table)
50 | api.uci_save(uci, "dhcp", true)
51 | end
52 | end
53 |
54 | function stretch()
55 | local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server")
56 | local dnsmasq_noresolv = uci:get("dhcp", "@dnsmasq[0]", "noresolv")
57 | local _flag
58 | if dnsmasq_server and #dnsmasq_server > 0 then
59 | for k, v in ipairs(dnsmasq_server) do
60 | if not v:find("/") then
61 | _flag = true
62 | end
63 | end
64 | end
65 | if not _flag and dnsmasq_noresolv == "1" then
66 | uci:delete("dhcp", "@dnsmasq[0]", "noresolv")
67 | local RESOLVFILE = "/tmp/resolv.conf.d/resolv.conf.auto"
68 | local file = io.open(RESOLVFILE, "r")
69 | if not file then
70 | RESOLVFILE = "/tmp/resolv.conf.auto"
71 | else
72 | local size = file:seek("end")
73 | file:close()
74 | if size == 0 then
75 | RESOLVFILE = "/tmp/resolv.conf.auto"
76 | end
77 | end
78 | uci:set("dhcp", "@dnsmasq[0]", "resolvfile", RESOLVFILE)
79 | api.uci_save(uci, "dhcp", true)
80 | end
81 | end
82 |
83 | function restart(var)
84 | local LOG = var["-LOG"]
85 | sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
86 | if LOG == "1" then
87 | api.log("Restart dnsmasq Serve")
88 | end
89 | end
90 |
91 | function logic_restart(var)
92 | local LOG = var["-LOG"]
93 | local DEFAULT_DNS = api.get_cache_var("DEFAULT_DNS")
94 | if DEFAULT_DNS then
95 | backup_servers()
96 | --sys.call("sed -i '/list server/d' /etc/config/dhcp >/dev/null 2>&1")
97 | local dns_table = {}
98 | local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server")
99 | if dnsmasq_server and #dnsmasq_server > 0 then
100 | for k, v in ipairs(dnsmasq_server) do
101 | if v:find("/") then
102 | tinsert(dns_table, v)
103 | end
104 | end
105 | uci:set_list("dhcp", "@dnsmasq[0]", "server", dns_table)
106 | api.uci_save(uci, "dhcp", true)
107 | end
108 | sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
109 | restore_servers()
110 | else
111 | sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
112 | end
113 | if LOG == "1" then
114 | api.log("Restart dnsmasq Serve")
115 | end
116 | end
117 |
118 | function copy_instance(var)
119 | local LISTEN_PORT = var["-LISTEN_PORT"]
120 | local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"]
121 | local conf_lines = {}
122 | local DEFAULT_DNSMASQ_CFGID = sys.exec("echo -n $(uci -q show dhcp.@dnsmasq[0] | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')")
123 | for line in io.lines("/tmp/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) do
124 | local filter
125 | if line:find("passwall2") then filter = true end
126 | if line:find("ubus") then filter = true end
127 | if line:find("dhcp") then filter = true end
128 | if line:find("server=") == 1 then filter = true end
129 | if line:find("port=") == 1 then filter = true end
130 | if line:find("conf%-dir=") == 1 then
131 | filter = true
132 | if TMP_DNSMASQ_PATH then
133 | local tmp_path = line:sub(1 + #"conf-dir=")
134 | sys.call(string.format("cp -r %s/* %s/ 2>/dev/null", tmp_path, TMP_DNSMASQ_PATH))
135 | end
136 | end
137 | if line:find("address=") == 1 or (line:find("server=") == 1 and line:find("/")) then filter = nil end
138 | if not filter then
139 | tinsert(conf_lines, line)
140 | end
141 | end
142 | tinsert(conf_lines, "port=" .. LISTEN_PORT)
143 | if TMP_DNSMASQ_PATH then
144 | sys.call("rm -rf " .. TMP_DNSMASQ_PATH .. "/*passwall2*")
145 | end
146 | if var["-return"] == "1" then
147 | return conf_lines
148 | end
149 | if #conf_lines > 0 then
150 | local DNSMASQ_CONF = var["-DNSMASQ_CONF"]
151 | local conf_out = io.open(DNSMASQ_CONF, "a")
152 | conf_out:write(table.concat(conf_lines, "\n"))
153 | conf_out:write("\n")
154 | conf_out:close()
155 | end
156 | end
157 |
158 | function add_rule(var)
159 | local FLAG = var["-FLAG"]
160 | local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"]
161 | local DNSMASQ_CONF_FILE = var["-DNSMASQ_CONF_FILE"]
162 | local LISTEN_PORT = var["-LISTEN_PORT"]
163 | local DEFAULT_DNS = var["-DEFAULT_DNS"]
164 | local LOCAL_DNS = var["-LOCAL_DNS"]
165 | local TUN_DNS = var["-TUN_DNS"]
166 | local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"]
167 | local NFTFLAG = var["-NFTFLAG"]
168 | local CACHE_PATH = api.CACHE_PATH
169 | local CACHE_FLAG = "dnsmasq_" .. FLAG
170 | local CACHE_DNS_PATH = CACHE_PATH .. "/" .. CACHE_FLAG
171 | local CACHE_TEXT_FILE = CACHE_DNS_PATH .. ".txt"
172 |
173 | local list1 = {}
174 | local excluded_domain = {}
175 | local excluded_domain_str = "!"
176 |
177 | local function check_dns(domain, dns)
178 | if domain == "" or domain:find("#") then
179 | return false
180 | end
181 | if not dns then
182 | return
183 | end
184 | for k,v in ipairs(list1[domain].dns) do
185 | if dns == v then
186 | return true
187 | end
188 | end
189 | return false
190 | end
191 |
192 | local function check_ipset(domain, ipset)
193 | if domain == "" or domain:find("#") then
194 | return false
195 | end
196 | if not ipset then
197 | return
198 | end
199 | for k,v in ipairs(list1[domain].ipsets) do
200 | if ipset == v then
201 | return true
202 | end
203 | end
204 | return false
205 | end
206 |
207 | local function set_domain_dns(domain, dns)
208 | if domain == "" or domain:find("#") then
209 | return
210 | end
211 | if not dns then
212 | return
213 | end
214 | if not list1[domain] then
215 | list1[domain] = {
216 | dns = {},
217 | ipsets = {}
218 | }
219 | end
220 | for line in string.gmatch(dns, '[^' .. "," .. ']+') do
221 | if not check_dns(domain, line) then
222 | table.insert(list1[domain].dns, line)
223 | end
224 | end
225 | end
226 |
227 | local function set_domain_ipset(domain, ipset)
228 | if domain == "" or domain:find("#") then
229 | return
230 | end
231 | if not ipset then
232 | return
233 | end
234 | if not list1[domain] then
235 | list1[domain] = {
236 | dns = {},
237 | ipsets = {}
238 | }
239 | end
240 | for line in string.gmatch(ipset, '[^' .. "," .. ']+') do
241 | if not check_ipset(domain, line) then
242 | table.insert(list1[domain].ipsets, line)
243 | end
244 | end
245 | end
246 |
247 | local cache_text = ""
248 | local nodes_address_md5 = sys.exec("echo -n $(uci show passwall2 | grep '\\.address') | md5sum")
249 | local new_text = TMP_DNSMASQ_PATH .. DNSMASQ_CONF_FILE .. DEFAULT_DNS .. LOCAL_DNS .. TUN_DNS .. nodes_address_md5 .. NFTFLAG
250 | if fs.access(CACHE_TEXT_FILE) then
251 | for line in io.lines(CACHE_TEXT_FILE) do
252 | cache_text = line
253 | end
254 | end
255 |
256 | if cache_text ~= new_text then
257 | api.remove(CACHE_DNS_PATH .. "*")
258 | end
259 |
260 | local dnsmasq_default_dns = TUN_DNS
261 |
262 | local setflag_4= (NFTFLAG == "1") and "4#inet#passwall2#" or ""
263 | local setflag_6= (NFTFLAG == "1") and "6#inet#passwall2#" or ""
264 |
265 | if not fs.access(CACHE_DNS_PATH) then
266 | fs.mkdir(CACHE_DNS_PATH)
267 |
268 | local fwd_dns
269 |
270 | --Always use domesticDNSResolve node domain name
271 | if true then
272 | fwd_dns = LOCAL_DNS
273 | uci:foreach(appname, "nodes", function(t)
274 | local function process_address(address)
275 | if address == "engage.cloudflareclient.com" then return end
276 | if datatypes.hostname(address) then
277 | set_domain_dns(address, fwd_dns)
278 | set_domain_ipset(address, setflag_4 .. "passwall2_vps," .. setflag_6 .. "passwall2_vps6")
279 | end
280 | end
281 | process_address(t.address)
282 | process_address(t.download_address)
283 | end)
284 | end
285 |
286 | if list1 and next(list1) then
287 | local server_out = io.open(CACHE_DNS_PATH .. "/001-server.conf", "a")
288 | local ipset_out = io.open(CACHE_DNS_PATH .. "/ipset.conf", "a")
289 | local set_name = "ipset"
290 | if NFTFLAG == "1" then
291 | set_name = "nftset"
292 | end
293 | for key, value in pairs(list1) do
294 | if value.dns and #value.dns > 0 then
295 | for i, dns in ipairs(value.dns) do
296 | server_out:write(string.format("server=/.%s/%s", key, dns) .. "\n")
297 | end
298 | end
299 | if value.ipsets and #value.ipsets > 0 then
300 | local ipsets_str = ""
301 | for i, ipset in ipairs(value.ipsets) do
302 | ipsets_str = ipsets_str .. ipset .. ","
303 | end
304 | ipsets_str = ipsets_str:sub(1, #ipsets_str - 1)
305 | ipset_out:write(string.format("%s=/.%s/%s", set_name, key, ipsets_str) .. "\n")
306 | end
307 | end
308 | server_out:close()
309 | ipset_out:close()
310 | end
311 |
312 | local f_out = io.open(CACHE_TEXT_FILE, "a")
313 | f_out:write(new_text)
314 | f_out:close()
315 | end
316 |
317 | api.remove(TMP_DNSMASQ_PATH)
318 | fs.symlink(CACHE_DNS_PATH, TMP_DNSMASQ_PATH)
319 |
320 | if DNSMASQ_CONF_FILE ~= "nil" then
321 | local conf_lines = {}
322 | if LISTEN_PORT then
323 | --Copy dnsmasq instance
324 | conf_lines = copy_instance({["-LISTEN_PORT"] = LISTEN_PORT, ["-TMP_DNSMASQ_PATH"] = TMP_DNSMASQ_PATH, ["-return"] = "1"})
325 | --dhcp.leases to hostsMore actions
326 | local hosts = "/tmp/etc/" .. appname .. "_tmp/dhcp-hosts"
327 | sys.call("touch " .. hosts)
328 | tinsert(conf_lines, "addn-hosts=" .. hosts)
329 | else
330 | --Modify the default dnsmasq service
331 | end
332 | tinsert(conf_lines, string.format("conf-dir=%s", TMP_DNSMASQ_PATH))
333 | if dnsmasq_default_dns then
334 | for s in string.gmatch(dnsmasq_default_dns, '[^' .. "," .. ']+') do
335 | tinsert(conf_lines, string.format("server=%s", s))
336 | end
337 | tinsert(conf_lines, "all-servers")
338 | tinsert(conf_lines, "no-poll")
339 | tinsert(conf_lines, "no-resolv")
340 |
341 | if FLAG == "default" then
342 | api.set_cache_var("DEFAULT_DNS", DEFAULT_DNS)
343 | end
344 | end
345 | if #conf_lines > 0 then
346 | local conf_out = io.open(DNSMASQ_CONF_FILE, "a")
347 | conf_out:write(table.concat(conf_lines, "\n"))
348 | conf_out:write("\n")
349 | conf_out:close()
350 | end
351 | end
352 | end
353 |
354 | _G.stretch = stretch
355 | _G.restart = restart
356 | _G.logic_restart = logic_restart
357 | _G.copy_instance = copy_instance
358 | _G.add_rule = add_rule
359 |
360 | if arg[1] then
361 | local func =_G[arg[1]]
362 | if func then
363 | func(api.get_function_args(arg))
364 | end
365 | end
366 |
--------------------------------------------------------------------------------