🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

@@ -0,0 +1,80 @@
config general
option transparent_proxy_enable '1'
option tproxy_enable_udp '1'
option server 'clashmeta'
option tproxy_port '7892'
option packet_mark_id '255'
list whitelist_process_uids '346'
list whitelist_process_gids '346'
option lan_interface 'br-lan'
# file descriptor limit
option rlimit_nofile '8192 16384'
# memory limit
option rlimit_data '314572800 314572800'
option dnsmasq_takeover_enable '0'
option dns_port '5353'
# option dns_count '0'
list wan_bypass_rules '114.114.114.114'
list wan_forward_rules '8.8.8.8'
option wan_forward_rule_file '/etc/mihomo/forwardlist.txt'
option wan_bypass_rule_file '/etc/mihomo/chnlist.txt'
config server 'clashmeta'
option alias 'Clash Meta (mihomo)'
option server_type 'mihomo'
option executable_path '/usr/bin/mihomo'
option config_type 'directory'
# option config_type 'file'
option config_directory '/etc/mihomo'
option config_file '/etc/mihomo/config.yml'
config server 'clash'
option alias 'Clash / Clash Premium'
option server_type 'mihomo'
option executable_path '/usr/bin/clash'
option config_type 'directory'
# option config_type 'file'
option config_directory '/etc/clash'
option config_file '/etc/clash/config.yml'
config server 'xray'
option server_type 'xray'
option executable_path '/usr/bin/xray'
option config_type 'directory'
# option config_type 'file'
option config_directory '/etc/xray'
option config_file '/etc/xray/config.json'
config server 'v2ray'
option server_type 'xray'
option executable_path '/usr/bin/v2ray'
option config_type 'directory'
# option config_type 'file'
option config_directory '/etc/v2ray'
option config_file '/etc/v2ray/config.json'
config lan_hosts 'example_bypass'
option macaddr '22:11:33:44:55:66'
option bypassed '1'
config lan_hosts 'example_forward'
option macaddr '66:55:44:33:22:11'
option bypassed '0'
+138
View File
@@ -0,0 +1,138 @@
#!/bin/sh /etc/rc.common
START=90
STOP=15
USE_PROCD=1
NAME=jederproxy
. $IPKG_INSTROOT/lib/functions/jederproxy.sh
uci_get_global_config() {
local key="$1"
local default_value="$2"
local value=$(uci -q get ${NAME}.@general[0].$key)
echo ${value:=$default_value}
}
start_jederproxy_process() {
process=$1
cwd=$2
cmd=$3
shift 3
log "Starting $process from $cmd inside $cwd"
procd_open_instance
procd_set_param respawn 1 1 0
procd_set_param env JP_TARGET_WD="$cwd"
procd_set_param command "/usr/libexec/jederproxy/jederproxy_jail"
procd_append_param command "$cmd"
for arg in "$@"; do
procd_append_param command "$arg"
done
local rlimit_nofile
local rlimit_data
rlimit_nofile="$(uci_get_global_config rlimit_nofile)"
rlimit_data="$(uci_get_global_config rlimit_data)"
if [ ! -z "${rlimit_nofile}" ] ; then
log "set limit nofile=${rlimit_nofile}"
procd_set_param limits "nofile=${rlimit_nofile}"
fi
if [ ! -z "${rlimit_data}" ] ; then
log "set limit data=${rlimit_data}"
procd_set_param limits "data=${rlimit_data}"
fi
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param file /etc/config/jederproxy
procd_set_param pidfile /var/run/jederproxy.pid
procd_close_instance
}
setup_dnsmasq() {
local dns_port=$(uci_get_global_config dns_port)
local dns_count=$(uci_get_global_config dns_count 0)
echo "# Generated dnsmasq configurations by luci-app-jederproxy" > /tmp/dnsmasq.d/jederproxy.conf
echo "strict-order" >> /tmp/dnsmasq.d/jederproxy.conf
echo "server=/#/127.0.0.1#${dns_port}" >> /tmp/dnsmasq.d/jederproxy.conf
local cur_port
for cur_port in $(seq ${dns_port} $(expr ${dns_port} + ${dns_count})); do
echo "server=127.0.0.1#${cur_port}" >> /tmp/dnsmasq.d/jederproxy.conf
done
log $(cat /tmp/dnsmasq.d/jederproxy.conf)
/etc/init.d/dnsmasq restart > /dev/null 2>&1
}
flush_dnsmasq() {
[ -f "/tmp/dnsmasq.d/jederproxy.conf" ] && rm /tmp/dnsmasq.d/jederproxy.conf
/etc/init.d/dnsmasq restart > /dev/null 2>&1
}
system_configure() {
[ "$(uci_get_global_config transparent_proxy_enable)" == "1" ] || return 0
log "Setting dnsmasq and firewall for transparent proxy..."
[ "$(uci_get_global_config dnsmasq_takeover_enable)" == "1" ] && setup_dnsmasq
setup_firewall
}
system_deconfigure() {
log "Resetting dnsmasq and firewall configurations..."
[ "$(uci_get_global_config dnsmasq_takeover_enable)" == "1" ] && flush_dnsmasq
flush_firewall
}
start_service() {
local active_server server_type executable_path config_type config_directory config_file
config_load $NAME
mkdir -p /var/etc/jederproxy
active_server=$(uci_get_global_config server)
config_get server_type "$active_server" server_type
config_get executable_path "$active_server" executable_path
config_get config_type "$active_server" config_type
config_get config_directory "$active_server" config_directory
config_get config_file "$active_server" config_file
local working_directory=""
if [ "$config_type" = "directory" ]; then
working_directory="$config_directory"
elif [ "$config_type" = "file" ]; then
working_directory=$(dirname "$config_file")
fi
if [ "$server_type" = "mihomo" ]; then
if [ "$config_type" = "directory" ]; then
start_jederproxy_process mihomo "$working_directory" "$executable_path" -d "$config_directory"
elif [ "$config_type" = "file" ]; then
start_jederproxy_process mihomo "$working_directory" "$executable_path" -f "$config_file"
fi
elif [ "$server_type" = "xray" ]; then
if [ "$config_type" = "directory" ]; then
start_jederproxy_process xray "$working_directory" "$executable_path" -confdir "$config_directory"
elif [ "$config_type" = "file" ]; then
start_jederproxy_process xray "$working_directory" "$executable_path" run -c "$config_file"
fi
fi
system_configure || system_deconfigure
}
stop_service() {
system_deconfigure
}
reload_service() {
stop
start
}
service_triggers() {
procd_add_reload_trigger "jederproxy"
}
@@ -0,0 +1 @@
include "/var/etc/jederproxy/*.nft"
@@ -0,0 +1,29 @@
FIREWALL_INCLUDE="/usr/share/jederproxy/firewall_include.lua"
log() {
logger -st jederproxy[$$] -p4 "$@"
}
setup_firewall() {
log "Setting ipset rules..."
lua /usr/share/jederproxy/gen_ipset_rules.lua | ipset -! restore
log "Generating firewall rules..."
/usr/bin/lua ${FIREWALL_INCLUDE} enable > $(uci get firewall.xray.path)
log "Triggering firewall restart..."
/etc/init.d/firewall reload 2>/dev/null
}
flush_firewall() {
log "Flushing firewall rules..."
/usr/bin/lua ${FIREWALL_INCLUDE} flush > $(uci get firewall.xray.path)
log "Triggering firewall restart..."
/etc/init.d/firewall reload 2>/dev/null
log "Flushing ipset rules..."
for setname in $(ipset -n list | grep "tp_spec"); do
ipset -! destroy $setname
done
}
@@ -0,0 +1,27 @@
FIREWALL_INCLUDE="/usr/share/jederproxy/firewall_include.ut"
log() {
logger -st jederproxy[$$] -p4 "$@"
}
setup_firewall() {
ip route add local default dev lo table 100
ip rule add fwmark 0x2333 table 100
log "Generating firewall4 rules..."
/usr/bin/utpl ${FIREWALL_INCLUDE} > /var/etc/jederproxy/firewall_include.nft
log "Triggering firewall4 reload..."
/etc/init.d/firewall reload 2>/dev/null
}
flush_firewall() {
ip rule del table 100
ip route flush table 100
log "Flushing firewall4 rules..."
rm /var/etc/jederproxy/firewall_include.nft 2>/dev/null || log ".. but fw4 rule file not exists"
log "Triggering firewall4 reload..."
/etc/init.d/firewall reload 2>/dev/null
}
@@ -0,0 +1,10 @@
#!/bin/sh
# mihomo/clash search for geoip.dat in $HOME/.config/mihomo
export HOME="$JP_TARGET_WD"
# xray search for geoip.dat in $PWD
cd "$JP_TARGET_WD"
# $HOME is unset by procd and $PWD is reset to / by procd,
# that's why we need this mini "jail"
exec "$@"
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
main() {
case "$1" in
list)
echo '{"statsquery":{},"statssys":{},"restartlogger":{}}'
;;
call)
shift
xray api $@
esac
}
main "$@"
@@ -0,0 +1,92 @@
#!/usr/bin/lua
local ucursor = require "luci.model.uci"
local flush = [[# firewall include file to stop transparent proxy
ip rule del table 100
ip route flush table 100
iptables-save -c | grep -v "JPROXY" | iptables-restore -c]]
local header = [[# firewall include file to start transparent proxy
ip route add local default dev lo table 100
ip rule add fwmark 0x2333 table 100
iptables-restore -n <<-EOF
*mangle
:JPROXY_RULES - [0:0]
:JPROXY_MARK_CONNECTIONS - [0:0]
]]
local rules = [[
##### JPROXY_RULES #####
# ignore traffic marked by JPROXY outbound
-A JPROXY_RULES -m mark --mark 0x%x -j RETURN
# connection-mark -> packet-mark
-A JPROXY_RULES -j CONNMARK --restore-mark
# ignore established connections
-A JPROXY_RULES -m mark --mark 0x2333 -j RETURN
# ignore traffic sent to reserved addresses
-A JPROXY_RULES -m set --match-set jp_ipv4_rfc1918 dst -j RETURN
# route traffic depends on whitelist/blacklists
-A JPROXY_RULES -m set --match-set jp_ether_src_bypass src -j RETURN
-A JPROXY_RULES -m set --match-set jp_ether_src_forward src -j JPROXY_MARK_CONNECTIONS
-A JPROXY_RULES -m set --match-set jp_ipv4_dst_forward dst -j JPROXY_MARK_CONNECTIONS
-A JPROXY_RULES -m set --match-set jp_ipv4_dst_bypass dst -j RETURN
-A JPROXY_RULES -j JPROXY_MARK_CONNECTIONS
##### JPROXY_MARK_CONNECTIONS #####
# mark the first packet of the connection
-A JPROXY_MARK_CONNECTIONS -p tcp --syn -j MARK --set-mark 0x2333
-A JPROXY_MARK_CONNECTIONS -p udp -m conntrack --ctstate NEW -j MARK --set-mark 0x2333
# packet-mark -> connection-mark
-A JPROXY_MARK_CONNECTIONS -j CONNMARK --save-mark
##### OUTPUT #####
-A OUTPUT -p tcp -m addrtype --src-type LOCAL ! --dst-type LOCAL -j JPROXY_RULES
##### PREROUTING #####
# proxy traffic passing through this machine (other->other)
-A PREROUTING -i %s -p tcp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j JPROXY_RULES
# hand over the marked package to TPROXY for processing
-A PREROUTING -p tcp -m mark --mark 0x2333 -j TPROXY --on-ip 127.0.0.1 --on-port %d
]]
local rules_udp = [[
##### OUTPUT #####
-A OUTPUT -p udp -m addrtype --src-type LOCAL ! --dst-type LOCAL -j JPROXY_RULES
##### PREROUTING #####
# proxy traffic passing through this machine (other->other)
-A PREROUTING -i %s -p udp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j JPROXY_RULES
# hand over the marked package to TPROXY for processing
-A PREROUTING -p udp -m mark --mark 0x2333 -j TPROXY --on-ip 127.0.0.1 --on-port %d
]]
local footer = [[
COMMIT
EOF]]
local proxy_section = ucursor:get_first("jederproxy", "general")
local proxy = ucursor:get_all("jederproxy", proxy_section)
print(flush)
if proxy.transparent_proxy_enable ~= "1" then
do
return
end
end
if arg[1] == "enable" then
print(header)
print(string.format(rules, tonumber(proxy.mark),
proxy.lan_interface, proxy.tproxy_port))
if proxy.tproxy_enable_udp ~= "1" then
print(string.format(rules_udp
proxy.lan_interface, proxy.tproxy_port))
end
print(footer)
else
print("# arg[1] == " .. arg[1] .. ", not enable")
end
@@ -0,0 +1,144 @@
#!/usr/bin/utpl
{%
const uci = require("uci");
const fs = require("fs");
function iterate_file(filename, cb) {
let fd = fs.open(filename, "r");
if (!fd) {
warn(`Unable to open '${filename}': ${fs.error()}\n`);
return;
}
let line = null, count = 0;
while (!!(line = trim(fd.read("line")))) {
if (ord(line) == 35) // skip lines which start with '#'
continue;
cb(line);
count++;
}
fd.close();
return count;
}
const cursor = uci.cursor();
cursor.load("jederproxy");
const config = cursor.get_all("jederproxy");
const general = config[filter(keys(config), k => config[k][".type"] == "general")[0]];
const jp_ether_src_forward = uniq(map(filter(keys(config), k => config[k][".type"] == "lan_hosts" && config[k].bypassed == "0"), k => config[k].macaddr) || []);
const jp_ether_src_bypass = uniq(map(filter(keys(config), k => config[k][".type"] == "lan_hosts" && config[k].bypassed == "1"), k => config[k].macaddr) || []);
const uids_direct = uniq(general.whitelist_process_uids || []);
const gids_direct = uniq(general.whitelist_process_gids || []);
const wan_bypass_rules = general.wan_bypass_rules || [];
const wan_forward_rules = general.wan_forward_rules || [];
const enable_udp = general.tproxy_enable_udp || "";
iterate_file(general.wan_bypass_rule_file || "/dev/null", line => push(wan_bypass_rules, line));
iterate_file(general.wan_forward_rule_file || "/dev/null", line => push(wan_forward_rules, line));
// push(wan_bypass_rules, split(general.fast_dns, ":")[0]);
// push(wan_forward_rules, split(general.secure_dns, ":")[0]);
%}
set jp_ether_src_bypass {
type ether_addr
size 65536
{% if (length(jp_ether_src_bypass) > 0): %}
elements = { {{ join(", ", jp_ether_src_bypass) }} }
{% endif %}
}
set jp_ether_src_forward {
type ether_addr
size 65536
{% if (length(jp_ether_src_forward) > 0): %}
elements = { {{ join(", ", jp_ether_src_forward) }} }
{% endif %}
}
set jp_ipv4_rfc1918 {
type ipv4_addr
size 65536
flags interval
elements = {
0.0.0.0/8,
10.0.0.0/8,
100.64.0.0/10,
127.0.0.0/8,
169.254.0.0/16,
172.16.0.0/12,
192.0.0.0/24,
192.0.2.0/24,
192.88.99.0/24,
192.168.0.0/16,
198.18.0.0/15,
198.51.100.0/24,
203.0.113.0/24,
224.0.0.0/4,
233.252.0.0/24,
240.0.0.0/4
}
auto-merge
}
set jp_ipv4_dst_bypass {
type ipv4_addr
size 65536
flags interval
elements = { {{ join(", ", wan_bypass_rules)}} }
auto-merge
}
set jp_ipv4_dst_forward {
type ipv4_addr
size 65536
flags interval
elements = { {{ join(", ", wan_forward_rules)}} }
auto-merge
}
chain jproxy_rules {
{% if (length(uids_direct) > 0): %}
meta skuid { {{ join(", ", uids_direct) }} } return comment "ignore traffic from processes owned by specific UIDs"
{% endif %}
{% if (length(gids_direct) > 0): %}
meta skgid { {{ join(", ", gids_direct) }} } return comment "ignore traffic from processes owned by specific GIDs"
{% endif %}
mark {{ sprintf("0x%08x", general.packet_mark_id) }} counter return comment "ignore outbound connections initiated by jederproxy"
counter meta mark set ct mark comment "connection-mark -> packet-mark"
mark 0x2333 counter return comment "ignore established connections"
ip daddr @jp_ipv4_rfc1918 return comment "ignore traffic sent to reserved IP address/networks"
ether saddr @jp_ether_src_bypass return comment "ignore traffic from specific sources (by macaddr)"
ether saddr @jp_ether_src_forward jump jproxy_mark_connection comment "enforce proxy from specific sources (by macaddr)"
ip daddr @jp_ipv4_dst_forward jump jproxy_mark_connection comment "enforce proxy to specific IP address/networks"
ip daddr @jp_ipv4_dst_bypass return comment "ignore traffic sent to specific IP address/networks"
jump jproxy_mark_connection
}
chain jproxy_mark_connection {
tcp flags & (fin|syn|rst|ack) == syn counter meta mark set 0x2333 comment "mark the first packet of TCP connections"
ip protocol udp ct state new counter meta mark set 0x2333 comment "mark the first packet of UDP connections"
counter ct mark set mark comment "packet-mark -> connection-mark"
}
chain jproxy_prerouting {
type filter hook prerouting priority filter; policy accept;
iifname "{{ general.lan_interface }}" ip protocol {tcp, udp} fib saddr type != local fib daddr type != local counter jump jproxy_rules comment "proxy TCP/UDP traffic passing through this router (other->other)"
meta l4proto tcp meta mark 0x2333 tproxy ip to 127.0.0.1:{{ general.tproxy_port }} counter accept comment "send marked TCP packets to jederproxy"
{% if (enable_udp == "1"): %}
meta l4proto udp meta mark 0x2333 tproxy ip to 127.0.0.1:{{ general.tproxy_port }} counter accept comment "send marked UDP packets to jederproxy"
{% endif %}
}
chain jproxy_output {
type route hook output priority filter; policy accept;
ip protocol tcp fib saddr type local fib daddr type != local counter jump jproxy_rules comment "Proxy TCP traffic from router itself"
{% if (enable_udp == "1"): %}
ip protocol udp fib saddr type local fib daddr type != local counter jump jproxy_rules comment "Proxy UDP traffic from router itself"
{% endif %}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,78 @@
#!/usr/bin/lua
local io = require("io")
local ucursor = require "luci.model.uci"
local proxy_section = ucursor:get_first("jederproxy", "general")
local proxy = ucursor:get_all("jederproxy", proxy_section)
local gen_ipset_rules_extra = dofile("/usr/share/jederproxy/gen_ipset_rules_extra.lua")
local create_ipset_rules = [[create jp_ether_src_bypass hash:mac hashsize 64
create jp_ether_src_forward hash:mac hashsize 64
create jp_ipv4_rfc1918 hash:net hashsize 64
create jp_ipv4_dst_bypass hash:net hashsize 64
create jp_ipv4_dst_forward hash:net hashsize 64]]
local function create_ipset()
print(create_ipset_rules)
end
local function split_ipv4_host_port(val, port_default)
local found, _, ip, port = val:find("([%d.]+):(%d+)")
if found == nil then
return val, tonumber(port_default)
else
return ip, tonumber(port)
end
end
local function lan_access_control()
ucursor:foreach("xray", "lan_hosts", function(v)
if v.bypassed == '0' then
print(string.format("add jp_ether_src_forward %s", v.macaddr))
else
print(string.format("add jp_ether_src_bypass %s", v.macaddr))
end
end)
end
local function iterate_list(ln, set_name)
local ip_list = proxy[ln]
if ip_list == nil then
return
end
for _, line in ipairs(ip_list) do
print(string.format("add %s %s", set_name, line))
end
end
local function iterate_file(fn, set_name)
if fn == nil then
return
end
local f = io.open(fn)
if f == nil then
return
end
for line in io.lines(fn) do
if line ~= "" then
print(string.format("add %s %s", set_name, line))
end
end
f:close()
end
local function dns_ips()
local fast_dns_ip, fast_dns_port = split_ipv4_host_port(proxy.fast_dns, 53)
local secure_dns_ip, secure_dns_port = split_ipv4_host_port(proxy.secure_dns, 53)
print(string.format("add jp_ipv4_dst_bypass %s", fast_dns_ip))
print(string.format("add jp_ipv4_dst_forward %s", secure_dns_ip))
end
create_ipset()
dns_ips()
lan_access_control()
iterate_list("wan_bypass_rules", "jp_ipv4_dst_bypass")
iterate_file(proxy.wan_bypass_rule_file or "/dev/null", "jp_ipv4_dst_bypass")
iterate_list("wan_forward_rules", "jp_ipv4_dst_forward")
iterate_file(proxy.wan_forward_rule_file or "/dev/null", "jp_ipv4_dst_forward")
gen_ipset_rules_extra(proxy)
@@ -0,0 +1,23 @@
#!/usr/bin/lua
local special_purpose_rules = [[add jp_ipv4_rfc1918 255.255.255.255
add jp_ipv4_rfc1918 0.0.0.0/8
add jp_ipv4_rfc1918 10.0.0.0/8
add jp_ipv4_rfc1918 100.64.0.0/10
add jp_ipv4_rfc1918 127.0.0.0/8
add jp_ipv4_rfc1918 169.254.0.0/16
add jp_ipv4_rfc1918 172.16.0.0/12
add jp_ipv4_rfc1918 192.0.0.0/24
add jp_ipv4_rfc1918 192.0.2.0/24
add jp_ipv4_rfc1918 192.88.99.0/24
add jp_ipv4_rfc1918 192.168.0.0/16
add jp_ipv4_rfc1918 198.18.0.0/15
add jp_ipv4_rfc1918 198.51.100.0/24
add jp_ipv4_rfc1918 203.0.113.0/24
add jp_ipv4_rfc1918 224.0.0.0/4
add jp_ipv4_rfc1918 233.252.0.0/24
add jp_ipv4_rfc1918 240.0.0.0/4]]
return function(proxy)
print(special_purpose_rules)
end
@@ -0,0 +1,17 @@
{
"admin/services/jederproxy": {
"title": "Jederproxy",
"action": {
"type": "view",
"path": "jederproxy"
},
"depends": {
"acl": [
"luci-app-jederproxy"
],
"uci": {
"jederproxy": true
}
}
}
}
@@ -0,0 +1,25 @@
{
"luci-app-jederproxy": {
"description": "Grant access to jederproxy configurations",
"read": {
"ubus": {
"luci": [
"getInitList",
"setInitAction"
]
},
"uci": [
"jederproxy"
],
"file": {
"/etc/init.d/jederproxy": [ "exec" ],
"/var/run/jederproxy.pid": [ "read" ]
}
},
"write": {
"uci": [
"jederproxy"
]
}
}
}
@@ -0,0 +1,323 @@
'use strict';
'require view';
'require uci';
'require rpc';
'require form';
'require fs';
'require ui';
'require network';
'require tools.widgets as widgets';
var callInitList, callInitAction, ServiceController;
const FEATURE_FLAGS = {
have_fw3: false, // %FLAG_NO_FW3% managed by build system
have_fw4: false, // %FLAG_NO_FW4% managed by build system
// %FLAG_FW3% have_fw3: true, // uncomment by build system on fw3
// %FLAG_FW4% have_fw4: true, // uncomment by build system on fw4
};
callInitList = rpc.declare({
object: 'luci',
method: 'getInitList',
params: [ 'name' ],
expect: { '': {} }
}),
callInitAction = rpc.declare({
object: 'luci',
method: 'setInitAction',
params: [ 'name', 'action' ],
expect: { result: false }
});
function check_resource_files(load_result) {
let geoip_existence = false;
let geoip_size = 0;
let geosite_existence = false;
let geosite_size = 0;
let firewall4 = false;
let xray_bin_default = false;
let optional_features = {};
for (const f of load_result) {
if (f.name == "xray") {
xray_bin_default = true;
}
if (f.name == "geoip.dat") {
geoip_existence = true;
geoip_size = '%.2mB'.format(f.size);
}
if (f.name == "geosite.dat") {
geosite_existence = true;
geosite_size = '%.2mB'.format(f.size);
}
if (f.name == "firewall_include.ut") {
firewall4 = true;
}
if (f.name.startsWith("optional_feature_")) {
optional_features[f.name] = true;
}
}
return {
geoip_existence: geoip_existence,
geoip_size: geoip_size,
geosite_existence: geosite_existence,
geosite_size: geosite_size,
optional_features: optional_features,
firewall4: firewall4,
xray_bin_default: xray_bin_default,
}
}
function check_dns_format(_, dns) {
if (/^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/.test(dns)) {
// IPv4 Address
return true;
}
if (/(https|tcp|quic)(\+local)?:\/\/([-\w\d@:%._\+~#=]{1,256}\.[\w\d()]{1,6}\b)(:(\d+))?([-\w\d()@:%_\+.~#?&\/=]*)/.test(dns)) {
// DoH/TCP/QUIC Server Address
return true;
}
if (dns === "localhost") {
return true;
}
return "Invalid DNS address";
}
ServiceController = form.DummyValue.extend({
handleAction: function(name, action, ev) {
return callInitAction(name, action).then(function(success) {
ui.addNotification(null, E('p', _('Service %s success.').format(action)), 'info');
}).catch(function (e) {
ui.addNotification(null, E("p", _('Unable to perform service %s: %s').format(action, e.message)), "error");
});
},
renderWidget: function(section_id, option_id, cfgvalue) {
return E([], [
E('span', { 'class': 'control-group' }, [
E('button', {
class: 'btn cbi-button-%s'.format(this.service_enabled ? 'positive' : 'negative'),
click: ui.createHandlerFn(this, function() {
return callInitAction(this.service_name, this.service_enabled ? "disable": "enable").then(rc => ui.addNotification(null, E('p', _('Toggle startup success.')), 'info'));
}),
disabled: this.control_disabled
}, this.service_enabled ? _('Enabled') : _('Disabled')),
E('button', { 'class': 'btn cbi-button-action', 'click': ui.createHandlerFn(this, 'handleAction', this.service_name, 'start'), 'disabled': this.control_disabled }, _('Start')),
E('button', { 'class': 'btn cbi-button-action', 'click': ui.createHandlerFn(this, 'handleAction', this.service_name, 'restart'), 'disabled': this.control_disabled }, _('Restart')),
E('button', { 'class': 'btn cbi-button-action', 'click': ui.createHandlerFn(this, 'handleAction', this.service_name, 'stop'), 'disabled': this.control_disabled }, _('Stop'))
])
]);
},
});
return view.extend({
handleServiceReload: function (ev) {
return callInitAction("jederproxy", "restart").then(function(rc) {
ui.addNotification(null, E('p', _('Reload service success.')), 'info');
}).catch(function (e) {
ui.addNotification(null, E("p", _('Unable to reload service: %s').format(e.message)), "error");
});
},
handleSaveApply: function (ev, mode) {
return this.__base__.handleSaveApply(ev, mode).then(this.handleServiceReload);
},
load: function () {
return Promise.all([
uci.load("jederproxy"), // config_data
fs.list("/usr/share/jederproxy"), // xray_dir
network.getHostHints(), // network_hosts
L.resolveDefault(fs.read("/var/run/jederproxy.pid"), null), // xray_pid
callInitList("jederproxy"), // xray_service_status
])
},
render: function (load_result) {
const config_data = load_result[0];
const xray_dir = load_result[1];
const network_hosts = load_result[2];
const xray_pid = load_result[3];
const xray_service_status = load_result[4]["jederproxy"];
const geoip_direct_code = uci.get_first(config_data, "general", "geoip_direct_code");
const { geoip_existence, geoip_size, geosite_existence, geosite_size, optional_features, firewall4, xray_bin_default } = check_resource_files(xray_dir);
const status_text = xray_pid ? (_("[Xray is running]") + `[PID:${xray_pid.trim()}]`) : _("[Xray is stopped]");
let asset_file_status = _('WARNING: at least one of asset files (geoip.dat, geosite.dat) is not found under /usr/share/jederproxy. Xray may not work properly. See <a href="https://github.com/ttimasdf/luci-app-jederproxy">here</a> for help.')
if (geoip_existence) {
if (geosite_existence) {
asset_file_status = _('Asset files check: ') + `geoip.dat ${geoip_size}; geosite.dat ${geosite_size}. ` + _('Report issues or request for features <a href="https://github.com/ttimasdf/luci-app-jederproxy">here</a>.')
}
}
const m = new form.Map('jederproxy', _('Xray'), status_text + " " + asset_file_status);
var s, o, ss;
s = m.section(form.TypedSection, 'general');
s.addremove = false;
s.anonymous = true;
s.tab('general', _('General Settings'));
o = s.taboption('general', form.ListValue, 'server', _('Server Instance'))
o.datatype = "uciname"
// o.value("disabled", _("Disabled"))
for (const v of uci.sections(config_data, "server")) {
o.value(v[".name"], v.alias || v[".name"])
}
o = s.taboption('general', ServiceController, "_service", _("Service Control"), _("Refresh the page manually for actions to take effect"));
o.service_name = "jederproxy"
o.service_enabled = xray_service_status.enabled;
o.service_index = xray_service_status.index;
o = s.taboption('general', form.Flag, 'dnsmasq_takeover_enable', _('Enable dnsmasq Takeover'), _('Enable this option force using xray dns inbound port as dnsmasq\'s upstream server.'))
o = s.taboption('general', form.Flag, 'transparent_proxy_enable', _('Enable Transparent Proxy'), _('This enables DNS query forwarding and TProxy for both TCP and optionally UDP connections.'))
o = s.taboption('general', form.Flag, 'tproxy_enable_udp', _('Enable UDP Forward'), _('UDP Forward could be switched off for better compatibility with some service providers'))
o.depends("transparent_proxy_enable", "1")
o = s.taboption('general', form.SectionValue, "jederproxy_servers", form.GridSection, 'server', _('Proxy Servers'), _("Servers are referenced by index (order in the following list). Deleting servers may result in changes of upstream servers actually used by proxy and bridge."))
ss = o.subsection
ss.sortable = false
ss.anonymous = true
ss.addremove = true
ss.tab('general', _('General Settings'));
o = ss.taboption('general', form.Value, "alias", _("Alias"))
o.rmempty = true
o = ss.taboption('general', form.Value, 'executable_path', _('Proxy Executable Path'))
o.rmempty = false
o = ss.taboption('general', form.ListValue, 'server_type', _('Server Type'))
o.value("mihomo")
o.value("xray")
o.default = "mihomo"
o = ss.taboption('general', form.ListValue, 'config_type', _('Config file type'))
o.value("directory")
o.value("file")
o.default = "file"
o = ss.taboption('general', form.Value, 'config_directory', _('Config directory path'))
o.depends("config_type", "directory")
o.rmempty = false
o = ss.taboption('general', form.Value, 'config_file', _('Config file path'))
o.depends("config_type", "file")
o.rmempty = false
s.tab('proxy', _('iptables/nftables Settings'));
o = s.taboption('proxy', form.Value, 'tproxy_port', _('Transparent Proxy Port'), _('The <code>tproxy-port</code> value in mihomo/clash or <code>port</code> value of dokodemo inbound in v2ray/xray'))
o.datatype = 'port'
o.default = 7892
o = s.taboption('proxy', form.Value, 'packet_mark_id', _('Packet Mark Number'), _('The <code>routing-mark</code> value in mihomo/clash or <code>streamSettings.sockopt.mark</code> value in v2ray/xray'))
o.datatype = 'range(1, 255)'
o.default = 255
// if (firewall4) {
// o = s.taboption('proxy', form.DynamicList, 'uids_direct', _('Skip Proxy for uids'), _("Processes started by users with these uids won't be forwarded through Xray."))
// o.datatype = "integer"
// o = s.taboption('proxy', form.DynamicList, 'gids_direct', _('Skip Proxy for gids'), _("Processes started by users in groups with these gids won't be forwarded through Xray."))
// o.datatype = "integer"
// }
o = s.taboption('proxy', form.DynamicList, 'whitelist_process_uids', _('Whitelist UIDs'), _('Ignore local traffic of specific UIDs'))
o.rmempty = true
o = s.taboption('proxy', form.DynamicList, 'whitelist_process_gids', _('Whitelist GIDs'), _('Ignore local traffic of specific GIDs'))
o.rmempty = true
o = s.taboption('proxy', widgets.DeviceSelect, 'lan_interface', _("LAN Interface"))
o.noaliases = true
o.rmempty = false
o.nocreate = true
o = s.taboption('proxy', form.SectionValue, "access_control_lan_hosts", form.TableSection, 'lan_hosts', _('LAN Hosts Access Control'), _("Will not enable transparent proxy for these MAC addresses."))
ss = o.subsection;
ss.sortable = false
ss.anonymous = true
ss.addremove = true
o = ss.option(form.Value, "macaddr", _("MAC Address"))
L.sortedKeys(network_hosts.hosts).forEach(function (mac) {
o.value(mac, E([], [mac, ' (', E('strong', [network_hosts.hosts[mac].name || L.toArray(network_hosts.hosts[mac].ipaddrs || network_hosts.hosts[mac].ipv4)[0] || L.toArray(network_hosts.hosts[mac].ip6addrs || network_hosts.hosts[mac].ipv6)[0] || '?']), ')']));
});
o.datatype = "macaddr"
o.rmempty = false
o = ss.option(form.ListValue, "bypassed", _("Access Control Strategy"))
o.value("0", "Always forwarded")
o.value("1", "Always bypassed")
o.rmempty = false
s.tab('dns', _('DNS Settings'));
o = s.taboption('dns', form.Value, 'dns_port', _('Xray DNS Server Port'), _("Do not use port 53 (dnsmasq), port 5353 (mDNS) or other common ports"))
o.datatype = 'port'
o.default = 5300
o = s.taboption('dns', form.Value, 'dns_count', _('Extra DNS Server Ports'), _('Listen for DNS Requests on multiple ports (all of which serves as dnsmasq upstream servers).<br/>For example if Xray DNS Server Port is 5300 and use 3 extra ports, 5300 - 5303 will be used for DNS requests.<br/>Increasing this value may help reduce the possibility of temporary DNS lookup failures.'))
o.datatype = 'range(0, 50)'
o.default = 0
s.tab('transparent_proxy_rules', _('Transparent Proxy Rules'));
o = s.taboption('transparent_proxy_rules', form.DynamicList, "wan_bypass_rules", _("Bypassed IP"), _("Requests to these IPs won't be forwarded through your proxy."))
o.datatype = "ip4addr"
o.rmempty = true
o = s.taboption('transparent_proxy_rules', form.Value, 'wan_bypass_rule_file', _('Bypassed IP list file'))
o.rmempty = false
o = s.taboption('transparent_proxy_rules', form.DynamicList, "wan_forward_rules", _("Forwarded IP"))
o.datatype = "ip4addr"
o.rmempty = true
o = s.taboption('transparent_proxy_rules', form.Value, 'wan_forward_rule_file', _('Forwarded IP list file'))
o.rmempty = false
s.tab('procd_options', _('Process Options'))
function rlimit_validate(section_id, value) {
let rlimit_regex = new RegExp("^[0-9]+ [0-9]+$","g");
if (value == "" || rlimit_regex.test(value)) {
return true
} else {
return "rlimit format: [soft] [hard]"
}
}
o = s.taboption('procd_options', form.Value, 'rlimit_nofile', _('Max Open Files'), _('Set xray process resource limit <code>RLIMIT_NOFILE</code>: max number of open file descriptors.'))
o.value("", "[unset]")
o.value("1024 4096", "1024 4096 (system default)")
o.value("8192 16384")
o.value("102400 204800")
o.default = ""
o.validate = rlimit_validate
o = s.taboption('procd_options', form.Value, 'rlimit_data', _('Max Allocated Memory'), _('Set xray process resource limit <code>RLIMIT_DATA</code>: max memory usage.'))
o.value("", "[unset]")
o.value("52428800 52428800", "50 MiB")
o.value("104857600 104857600", "100 MiB")
o.value("209715200 209715200", "200 MiB")
o.value("419430400 419430400", "400 MiB")
o.default = ""
o.validate = rlimit_validate
return m.render();
}
});