2023-02-16 23:37:45 +08:00
local api = require " luci.passwall2.api "
2022-03-11 18:29:27 +08:00
local appname = api.appname
local uci = api.uci
local datatypes = api.datatypes
2023-09-06 23:36:47 +08:00
local has_singbox = api.finded_com ( " singbox " )
local has_xray = api.finded_com ( " xray " )
2022-03-11 18:29:27 +08:00
m = Map ( appname )
2023-09-22 09:11:34 +08:00
api.set_apply_on_parse ( m )
2022-03-11 18:29:27 +08:00
local nodes_table = { }
for k , e in ipairs ( api.get_valid_nodes ( ) ) do
2023-04-09 23:35:07 +08:00
nodes_table [ # nodes_table + 1 ] = e
2022-03-11 18:29:27 +08:00
end
2024-03-17 19:17:01 +08:00
local normal_list = { }
local balancing_list = { }
local shunt_list = { }
local iface_list = { }
for k , v in pairs ( nodes_table ) do
if v.node_type == " normal " then
normal_list [ # normal_list + 1 ] = v
end
if v.protocol and v.protocol == " _balancing " then
balancing_list [ # balancing_list + 1 ] = v
end
if v.protocol and v.protocol == " _shunt " then
shunt_list [ # shunt_list + 1 ] = v
end
if v.protocol and v.protocol == " _iface " then
iface_list [ # iface_list + 1 ] = v
end
end
local socks_list = { }
uci : foreach ( appname , " socks " , function ( s )
if s.enabled == " 1 " and s.node then
socks_list [ # socks_list + 1 ] = {
id = " Socks_ " .. s [ " .name " ] ,
remark = translate ( " Socks Config " ) .. " [ " .. s.port .. " 端口] "
}
end
end )
2022-03-11 18:29:27 +08:00
local doh_validate = function ( self , value , t )
2023-04-09 23:35:07 +08:00
if value ~= " " then
local flag = 0
local util = require " luci.util "
local val = util.split ( value , " , " )
local url = val [ 1 ]
val [ 1 ] = nil
for i = 1 , # val do
local v = val [ i ]
if v then
if not datatypes.ipmask4 ( v ) then
flag = 1
end
end
end
if flag == 0 then
return value
end
end
return nil , translate ( " DoH request address " ) .. " " .. translate ( " Format must be: " ) .. " URL,IP "
2022-03-11 18:29:27 +08:00
end
m : append ( Template ( appname .. " /global/status " ) )
2023-09-04 23:36:20 +08:00
local global_cfgid = uci : get_all ( appname , " @global[0] " ) [ " .name " ]
2022-03-11 18:29:27 +08:00
s = m : section ( TypedSection , " global " )
s.anonymous = true
s.addremove = false
s : tab ( " Main " , translate ( " Main " ) )
-- [[ Global Settings ]]--
o = s : taboption ( " Main " , Flag , " enabled " , translate ( " Main switch " ) )
o.rmempty = false
2023-04-10 09:16:48 +08:00
---- Node
node = s : taboption ( " Main " , ListValue , " node " , " <a style='color: red'> " .. translate ( " Node " ) .. " </a> " )
2022-03-11 18:29:27 +08:00
node : value ( " nil " , translate ( " Close " ) )
-- 分流
2023-09-05 19:23:30 +08:00
if ( has_singbox or has_xray ) and # nodes_table > 0 then
2023-05-25 23:35:42 +08:00
local function get_cfgvalue ( shunt_node_id , option )
return function ( self , section )
return m : get ( shunt_node_id , option ) or " nil "
end
end
local function get_write ( shunt_node_id , option )
return function ( self , section , value )
m : set ( shunt_node_id , option , value )
end
end
if # normal_list > 0 then
for k , v in pairs ( shunt_list ) do
local vid = v.id
2023-09-05 19:23:30 +08:00
-- shunt node type, Sing-Box or Xray
2023-05-25 23:35:42 +08:00
local type = s : taboption ( " Main " , ListValue , vid .. " -type " , translate ( " Type " ) )
2023-09-04 23:36:20 +08:00
if has_singbox then
type : value ( " sing-box " , translate ( " Sing-Box " ) )
end
2023-05-25 23:35:42 +08:00
if has_xray then
type : value ( " Xray " , translate ( " Xray " ) )
end
type.cfgvalue = get_cfgvalue ( v.id , " type " )
type.write = get_write ( v.id , " type " )
-- pre-proxy
o = s : taboption ( " Main " , Flag , vid .. " -preproxy_enabled " , translate ( " Preproxy " ) )
o : depends ( " node " , v.id )
o.rmempty = false
o.cfgvalue = get_cfgvalue ( v.id , " preproxy_enabled " )
o.write = get_write ( v.id , " preproxy_enabled " )
2024-03-17 19:17:01 +08:00
o = s : taboption ( " Main " , ListValue , vid .. " -main_node " , string.format ( ' <a style="color:red">%s</a> ' , translate ( " Preproxy Node " ) ) , translate ( " Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not. " ) )
2023-05-25 23:35:42 +08:00
o : depends ( vid .. " -preproxy_enabled " , " 1 " )
2024-03-17 19:17:01 +08:00
for k1 , v1 in pairs ( socks_list ) do
o : value ( v1.id , v1.remark )
end
2023-05-25 23:35:42 +08:00
for k1 , v1 in pairs ( balancing_list ) do
o : value ( v1.id , v1.remark )
end
2023-08-12 09:06:45 +08:00
for k1 , v1 in pairs ( iface_list ) do
o : value ( v1.id , v1.remark )
end
2023-05-25 23:35:42 +08:00
for k1 , v1 in pairs ( normal_list ) do
o : value ( v1.id , v1.remark )
end
if # o.keylist > 0 then
o.default = o.keylist [ 1 ]
end
o.cfgvalue = get_cfgvalue ( v.id , " main_node " )
o.write = get_write ( v.id , " main_node " )
2023-09-05 19:23:30 +08:00
if ( has_singbox and has_xray ) or ( v.type == " sing-box " and not has_singbox ) or ( v.type == " Xray " and not has_xray ) then
2023-05-25 23:35:42 +08:00
type : depends ( " node " , v.id )
else
type : depends ( " node " , " hide " ) --不存在的依赖,即始终隐藏
end
uci : foreach ( appname , " shunt_rules " , function ( e )
local id = e [ " .name " ]
local node_option = vid .. " - " .. id .. " _node "
if id and e.remarks then
2024-03-17 19:17:01 +08:00
o = s : taboption ( " Main " , ListValue , node_option , string.format ( ' * <a href="%s" target="_blank">%s</a> ' , api.url ( " shunt_rules " , id ) , e.remarks ) )
2023-05-25 23:35:42 +08:00
o.cfgvalue = get_cfgvalue ( v.id , id )
o.write = get_write ( v.id , id )
o : depends ( " node " , v.id )
o.default = " nil "
o : value ( " nil " , translate ( " Close " ) )
o : value ( " _default " , translate ( " Default " ) )
o : value ( " _direct " , translate ( " Direct Connection " ) )
o : value ( " _blackhole " , translate ( " Blackhole " ) )
local pt = s : taboption ( " Main " , ListValue , vid .. " - " .. id .. " _proxy_tag " , string.format ( ' * <a style="color:red">%s</a> ' , e.remarks .. " " .. translate ( " Preproxy " ) ) )
pt.cfgvalue = get_cfgvalue ( v.id , id .. " _proxy_tag " )
pt.write = get_write ( v.id , id .. " _proxy_tag " )
pt : value ( " nil " , translate ( " Close " ) )
pt : value ( " main " , translate ( " Preproxy Node " ) )
pt.default = " nil "
2024-03-17 19:17:01 +08:00
for k1 , v1 in pairs ( socks_list ) do
o : value ( v1.id , v1.remark )
end
2023-05-25 23:35:42 +08:00
for k1 , v1 in pairs ( balancing_list ) do
o : value ( v1.id , v1.remark )
end
2023-08-12 09:06:45 +08:00
for k1 , v1 in pairs ( iface_list ) do
o : value ( v1.id , v1.remark )
end
2023-05-25 23:35:42 +08:00
for k1 , v1 in pairs ( normal_list ) do
o : value ( v1.id , v1.remark )
pt : depends ( { [ node_option ] = v1.id , [ vid .. " -preproxy_enabled " ] = " 1 " } )
end
2023-04-09 23:35:07 +08:00
end
2023-05-25 23:35:42 +08:00
end )
local id = " default_node "
2024-03-17 19:17:01 +08:00
o = s : taboption ( " Main " , ListValue , vid .. " - " .. id , string.format ( ' * <a style="color:red">%s</a> ' , translate ( " Default " ) ) )
2023-05-25 23:35:42 +08:00
o.cfgvalue = get_cfgvalue ( v.id , id )
o.write = get_write ( v.id , id )
o : depends ( " node " , v.id )
o.default = " _direct "
o : value ( " _direct " , translate ( " Direct Connection " ) )
o : value ( " _blackhole " , translate ( " Blackhole " ) )
2024-03-17 19:17:01 +08:00
for k1 , v1 in pairs ( socks_list ) do
o : value ( v1.id , v1.remark )
end
2023-05-25 23:35:42 +08:00
for k1 , v1 in pairs ( balancing_list ) do
o : value ( v1.id , v1.remark )
end
2023-08-12 09:06:45 +08:00
for k1 , v1 in pairs ( iface_list ) do
o : value ( v1.id , v1.remark )
end
2023-05-25 23:35:42 +08:00
for k1 , v1 in pairs ( normal_list ) do
o : value ( v1.id , v1.remark )
end
local id = " default_proxy_tag "
o = s : taboption ( " Main " , ListValue , vid .. " - " .. id , string.format ( ' * <a style="color:red">%s</a> ' , translate ( " Default Preproxy " ) ) , translate ( " When using, localhost will connect this node first and then use this node to connect the default node. " ) )
o.cfgvalue = get_cfgvalue ( v.id , id )
o.write = get_write ( v.id , id )
o : value ( " nil " , translate ( " Close " ) )
o : value ( " main " , translate ( " Preproxy Node " ) )
for k1 , v1 in pairs ( normal_list ) do
if v1.protocol ~= " _balancing " then
o : depends ( { [ vid .. " -default_node " ] = v1.id , [ vid .. " -preproxy_enabled " ] = " 1 " } )
2023-04-09 23:35:07 +08:00
end
end
end
2023-05-25 23:35:42 +08:00
else
local tips = s : taboption ( " Main " , DummyValue , " tips " , " " )
tips.rawhtml = true
tips.cfgvalue = function ( t , n )
return string.format ( ' <a style="color: red">%s</a> ' , translate ( " There are no available nodes, please add or subscribe nodes first. " ) )
2023-04-09 23:35:07 +08:00
end
2023-05-25 23:35:42 +08:00
tips : depends ( { node = " nil " , [ " !reverse " ] = true } )
for k , v in pairs ( shunt_list ) do
tips : depends ( " node " , v.id )
2023-04-09 23:35:07 +08:00
end
2023-05-25 23:35:42 +08:00
for k , v in pairs ( balancing_list ) do
tips : depends ( " node " , v.id )
2023-04-10 09:16:48 +08:00
end
2023-04-09 23:35:07 +08:00
end
2022-03-11 18:29:27 +08:00
end
2022-04-19 07:44:18 +08:00
o = s : taboption ( " Main " , Flag , " localhost_proxy " , translate ( " Localhost Proxy " ) , translate ( " When selected, localhost can transparent proxy. " ) )
o.default = " 1 "
o.rmempty = false
2024-03-14 20:28:56 +08:00
o = s : taboption ( " Main " , Flag , " client_proxy " , translate ( " Client Proxy " ) , translate ( " When selected, devices in LAN can transparent proxy. Otherwise, it will not be proxy. But you can still use access control to allow the designated device to proxy. " ) )
o.default = " 1 "
o.rmempty = false
2023-02-08 00:47:38 +08:00
node_socks_port = s : taboption ( " Main " , Value , " node_socks_port " , translate ( " Node " ) .. " Socks " .. translate ( " Listen Port " ) )
node_socks_port.default = 1070
node_socks_port.datatype = " port "
2022-03-11 18:29:27 +08:00
s : tab ( " DNS " , translate ( " DNS " ) )
2022-04-12 20:35:28 +08:00
o = s : taboption ( " DNS " , ListValue , " remote_dns_protocol " , translate ( " Remote DNS Protocol " ) )
o : value ( " tcp " , " TCP " )
o : value ( " doh " , " DoH " )
2022-04-13 20:35:44 +08:00
o : value ( " udp " , " UDP " )
2022-03-11 18:29:27 +08:00
---- DNS Forward
2022-04-12 20:35:28 +08:00
o = s : taboption ( " DNS " , Value , " remote_dns " , translate ( " Remote DNS " ) )
2022-03-11 18:29:27 +08:00
o.datatype = " or(ipaddr,ipaddrport) "
o.default = " 1.1.1.1 "
2022-04-12 20:35:28 +08:00
o : value ( " 1.1.1.1 " , " 1.1.1.1 (CloudFlare) " )
o : value ( " 1.1.1.2 " , " 1.1.1.2 (CloudFlare-Security) " )
o : value ( " 8.8.4.4 " , " 8.8.4.4 (Google) " )
o : value ( " 8.8.8.8 " , " 8.8.8.8 (Google) " )
o : value ( " 9.9.9.9 " , " 9.9.9.9 (Quad9-Recommended) " )
o : value ( " 208.67.220.220 " , " 208.67.220.220 (OpenDNS) " )
o : value ( " 208.67.222.222 " , " 208.67.222.222 (OpenDNS) " )
o : depends ( " remote_dns_protocol " , " tcp " )
2022-04-13 20:35:44 +08:00
o : depends ( " remote_dns_protocol " , " udp " )
2022-04-12 20:35:28 +08:00
---- DoH
o = s : taboption ( " DNS " , Value , " remote_dns_doh " , translate ( " Remote DNS DoH " ) )
o.default = " https://1.1.1.1/dns-query "
o : value ( " https://1.1.1.1/dns-query " , " CloudFlare " )
o : value ( " https://1.1.1.2/dns-query " , " CloudFlare-Security " )
o : value ( " https://8.8.4.4/dns-query " , " Google 8844 " )
o : value ( " https://8.8.8.8/dns-query " , " Google 8888 " )
o : value ( " https://9.9.9.9/dns-query " , " Quad9-Recommended " )
o : value ( " https://208.67.222.222/dns-query " , " OpenDNS " )
o : value ( " https://dns.adguard.com/dns-query,176.103.130.130 " , " AdGuard " )
o : value ( " https://doh.libredns.gr/dns-query,116.202.176.26 " , " LibreDNS " )
o : value ( " https://doh.libredns.gr/ads,116.202.176.26 " , " LibreDNS (No Ads) " )
o.validate = doh_validate
o : depends ( " remote_dns_protocol " , " doh " )
o = s : taboption ( " DNS " , Value , " remote_dns_client_ip " , translate ( " Remote DNS EDNS Client Subnet " ) )
2022-03-11 18:29:27 +08:00
o.description = translate ( " Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address). " ) .. " <br /> " ..
2023-04-09 23:35:07 +08:00
translate ( " This feature requires the DNS server to support the Edns Client Subnet (RFC7871). " )
2022-03-11 18:29:27 +08:00
o.datatype = " ipaddr "
2023-09-21 23:36:20 +08:00
o : depends ( { __hide = true } )
2022-03-11 18:29:27 +08:00
2023-09-26 16:23:24 +08:00
o = s : taboption ( " DNS " , ListValue , " remote_dns_detour " , translate ( " Remote DNS Outbound " ) )
o.default = " remote "
o : value ( " remote " , translate ( " Remote " ) )
o : value ( " direct " , translate ( " Direct " ) )
2023-04-27 00:22:40 +08:00
o = s : taboption ( " DNS " , Flag , " remote_fakedns " , " FakeDNS " , translate ( " Use FakeDNS work in the shunt domain that proxy. " ) )
o.default = " 0 "
o.rmempty = false
2023-02-09 09:40:07 +08:00
o = s : taboption ( " DNS " , ListValue , " remote_dns_query_strategy " , translate ( " Remote Query Strategy " ) )
2022-04-13 20:35:44 +08:00
o.default = " UseIPv4 "
o : value ( " UseIP " )
o : value ( " UseIPv4 " )
2023-02-09 09:40:07 +08:00
o : value ( " UseIPv6 " )
2022-04-13 20:35:44 +08:00
2023-09-04 23:36:20 +08:00
o = s : taboption ( " DNS " , TextValue , " dns_hosts " , translate ( " Domain Override " ) )
o.rows = 5
o.wrap = " off "
2023-09-21 23:36:20 +08:00
o : depends ( { __hide = true } )
2023-09-04 23:36:20 +08:00
o.remove = function ( self , section )
local node_value = node : formvalue ( global_cfgid )
2023-09-13 16:22:26 +08:00
if node_value ~= " nil " then
local node_t = m : get ( node_value ) or { }
if node_t.type == " Xray " then
AbstractValue.remove ( self , section )
end
2023-09-04 23:36:20 +08:00
end
end
2022-04-13 20:35:44 +08:00
2024-03-09 04:14:41 +08:00
o = s : taboption ( " DNS " , Flag , " write_ipset_direct " , translate ( " Direct DNS result write to IPSet " ) , translate ( " Perform the matching direct domain name rules into IP to IPSet/NFTSet, and then connect directly (not entering the core). Maybe conflict with some special circumstances. " ) )
o.default = " 1 "
o.rmempty = false
o = s : taboption ( " DNS " , Button , " clear_ipset " , translate ( " Clear IPSet " ) , translate ( " Try this feature if the rule modification does not take effect. " ) )
2023-04-26 16:21:10 +08:00
o.inputstyle = " remove "
function o . write ( e , e )
2023-05-25 23:35:42 +08:00
luci.sys . call ( " [ -n \" $(nft list sets 2>/dev/null | grep \" passwall2_ \" ) \" ] && sh /usr/share/ " .. appname .. " /nftables.sh flush_nftset || sh /usr/share/ " .. appname .. " /iptables.sh flush_ipset > /dev/null 2>&1 & " )
2023-04-26 16:21:10 +08:00
luci.http . redirect ( api.url ( " log " ) )
end
2023-09-04 23:36:20 +08:00
for k , v in pairs ( nodes_table ) do
if v.type == " Xray " then
s.fields [ " remote_dns_client_ip " ] : depends ( { node = v.id , remote_dns_protocol = " tcp " } )
s.fields [ " remote_dns_client_ip " ] : depends ( { node = v.id , remote_dns_protocol = " doh " } )
s.fields [ " dns_hosts " ] : depends ( { node = v.id } )
end
end
2022-03-11 18:29:27 +08:00
s : tab ( " log " , translate ( " Log " ) )
o = s : taboption ( " log " , Flag , " close_log " , translate ( " Close Node Log " ) )
o.rmempty = false
loglevel = s : taboption ( " log " , ListValue , " loglevel " , translate ( " Log Level " ) )
loglevel.default = " warning "
loglevel : value ( " debug " )
loglevel : value ( " info " )
loglevel : value ( " warning " )
loglevel : value ( " error " )
s : tab ( " faq " , " FAQ " )
o = s : taboption ( " faq " , DummyValue , " " )
o.template = appname .. " /global/faq "
-- [[ Socks Server ]]--
o = s : taboption ( " Main " , Flag , " socks_enabled " , " Socks " .. translate ( " Main switch " ) )
o.rmempty = false
s = m : section ( TypedSection , " socks " , translate ( " Socks Config " ) )
2023-08-22 16:22:05 +08:00
s.template = " cbi/tblsection "
2022-03-11 18:29:27 +08:00
s.anonymous = true
s.addremove = true
2023-08-22 16:22:05 +08:00
s.extedit = api.url ( " socks_config " , " %s " )
2022-03-11 18:29:27 +08:00
function s . create ( e , t )
2023-08-22 16:22:05 +08:00
local uuid = api.gen_short_uuid ( )
t = uuid
TypedSection.create ( e , t )
luci.http . redirect ( e.extedit : format ( t ) )
2022-03-11 18:29:27 +08:00
end
o = s : option ( DummyValue , " status " , translate ( " Status " ) )
o.rawhtml = true
o.cfgvalue = function ( t , n )
2023-04-09 23:35:07 +08:00
return string.format ( ' <div class="_status" socks_id="%s"></div> ' , n )
2022-03-11 18:29:27 +08:00
end
---- Enable
o = s : option ( Flag , " enabled " , translate ( " Enable " ) )
o.default = 1
o.rmempty = false
socks_node = s : option ( ListValue , " node " , translate ( " Socks Node " ) )
2023-02-08 00:47:38 +08:00
local n = 1
2022-03-11 18:29:27 +08:00
uci : foreach ( appname , " socks " , function ( s )
2023-04-09 23:35:07 +08:00
if s [ " .name " ] == section then
return false
end
n = n + 1
2022-03-11 18:29:27 +08:00
end )
o = s : option ( Value , " port " , " Socks " .. translate ( " Listen Port " ) )
o.default = n + 1080
o.datatype = " port "
o.rmempty = false
2023-09-05 19:23:30 +08:00
if has_singbox or has_xray then
2023-04-09 23:35:07 +08:00
o = s : option ( Value , " http_port " , " HTTP " .. translate ( " Listen Port " ) .. " " .. translate ( " 0 is not use " ) )
o.default = 0
o.datatype = " port "
2022-03-11 18:29:27 +08:00
end
for k , v in pairs ( nodes_table ) do
2023-04-09 23:35:07 +08:00
node : value ( v.id , v [ " remark " ] )
if v.type == " Socks " then
2023-09-05 19:23:30 +08:00
if has_singbox or has_xray then
2023-04-09 23:35:07 +08:00
socks_node : value ( v.id , v [ " remark " ] )
end
else
socks_node : value ( v.id , v [ " remark " ] )
end
2022-03-11 18:29:27 +08:00
end
m : append ( Template ( appname .. " /global/footer " ) )
return m