update 2026-05-10 09:48:30

This commit is contained in:
action committed 2026-05-10 09:48:30 +08:00
1 parent 95ac96413e
commit 0b362674f1
6244 files changed
+2992248

No files matched your search

@@ -0,0 +1,32 @@
{
"bounding": [
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW",
"CAP_SYS_PTRACE"
],
"effective": [
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW",
"CAP_SYS_PTRACE"
],
"ambient": [
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW",
"CAP_SYS_PTRACE"
],
"permitted": [
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW",
"CAP_SYS_PTRACE"
],
"inheritable": [
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW",
"CAP_SYS_PTRACE"
]
}
+74
View File
@@ -0,0 +1,74 @@
config homeproxy 'infra'
option __warning 'DO NOT EDIT THIS SECTION, OR YOU ARE ON YOUR OWN!'
option common_port '22,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418'
option mixed_port '5330'
option redirect_port '5331'
option tproxy_port '5332'
option dns_port '5333'
option dns_redirect '1'
option ntp_server 'nil'
option sniff_override '1'
option udp_timeout ''
option tun_name 'singtun0'
option tun_addr4 '172.19.0.1/30'
option tun_addr6 'fdfe:dcba:9876::1/126'
option tun_mtu '9000'
option table_mark '100'
option self_mark '100'
option tproxy_mark '101'
option tun_mark '102'
config homeproxy 'migration'
option crontab '1'
config homeproxy 'config'
option main_node 'nil'
option main_udp_node 'same'
option dns_server 'https://dns.quad9.net/dns-query'
option china_dns_server 'https://dns.alidns.com/dns-query'
option routing_mode 'bypass_mainland_china'
option routing_port 'common'
option proxy_mode 'redirect_tproxy'
option ipv6_support '1'
option github_token ''
option log_level 'warn'
config homeproxy 'control'
option lan_proxy_mode 'disabled'
list wan_proxy_ipv4_ips '91.105.192.0/23'
list wan_proxy_ipv4_ips '91.108.4.0/22'
list wan_proxy_ipv4_ips '91.108.8.0/21'
list wan_proxy_ipv4_ips '91.108.16.0/21'
list wan_proxy_ipv4_ips '91.108.56.0/22'
list wan_proxy_ipv4_ips '95.161.64.0/20'
list wan_proxy_ipv4_ips '149.154.160.0/20'
list wan_proxy_ipv4_ips '185.76.151.0/24'
list wan_proxy_ipv6_ips '2001:67c:4e8::/48'
list wan_proxy_ipv6_ips '2001:b28:f23c::/47'
list wan_proxy_ipv6_ips '2001:b28:f23f::/48'
list wan_proxy_ipv6_ips '2a0a:f280::/32'
config homeproxy 'routing'
option sniff_override '1'
option default_outbound 'direct-out'
option default_outbound_dns 'default-dns'
config homeproxy 'dns'
option dns_strategy 'prefer_ipv4'
option default_server 'local-dns'
option disable_cache '0'
option disable_cache_expire '0'
config homeproxy 'subscription'
option auto_update '0'
option allow_insecure '0'
option latency_test_mode 'icmp'
option packet_encoding 'xudp'
option update_via_proxy '0'
option auto_update_time '0 */6 * * *'
option filter_nodes 'disabled'
config homeproxy 'server'
option enabled '0'
option log_level 'warn'
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
202605092310
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
202605092310
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
202605092310
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
202605092310
+19
View File
@@ -0,0 +1,19 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2022-2023 ImmortalWrt.org
NAME="homeproxy"
log_max_size="50" #KB
main_log_file="/var/run/$NAME/$NAME.log"
singc_log_file="/var/run/$NAME/sing-box-c.log"
sings_log_file="/var/run/$NAME/sing-box-s.log"
while true; do
sleep 180
for i in "$main_log_file" "$singc_log_file" "$sings_log_file"; do
[ -s "$i" ] || continue
[ "$(( $(ls -l "$i" | awk -F ' ' '{print $5}') / 1024 >= log_max_size))" -eq "0" ] || echo "" > "$i"
done
done
+665
View File
@@ -0,0 +1,665 @@
#!/usr/bin/utpl -S
{%-
import { readfile } from 'fs';
import { cursor } from 'uci';
import { isEmpty } from '/etc/homeproxy/scripts/homeproxy.uc';
const fw4 = require('fw4');
function array_to_nftarr(array) {
if (type(array) !== 'array')
return null;
return `{ ${join(', ', uniq(array))} }`;
}
function resolve_ipv6(str) {
if (isEmpty(str))
return null;
let ipv6 = fw4.parse_subnet(str)?.[0];
if (!ipv6 || ipv6.family !== 6)
return null;
if (ipv6.bits > -1)
return `${ipv6.addr}/${ipv6.bits}`;
else
return `& ${ipv6.mask} == ${ipv6.addr}`;
}
function resolve_mark(str) {
if (isEmpty(str))
return null;
let mark = fw4.parse_mark(str);
if (isEmpty(mark))
return null;
if (mark.mask === 0xffffffff)
return fw4.hex(mark.mark);
else if (mark.mark === 0)
return `mark and ${fw4.hex(~mark.mask & 0xffffffff)}`;
else if (mark.mark === mark.mask)
return `mark or ${fw4.hex(mark.mark)}`;
else if (mark.mask === 0)
return `mark xor ${fw4.hex(mark.mark)}`;
else
return `mark and ${fw4.hex(~mark.mask & 0xffffffff)} xor ${fw4.hex(mark.mark)}`;
}
/* Misc config */
const resources_dir = '/etc/homeproxy/resources';
/* UCI config start */
const cfgname = 'homeproxy';
const uci = cursor();
uci.load(cfgname);
const routing_mode = uci.get(cfgname, 'config', 'routing_mode') || 'bypass_mainland_china';
let outbound_node, outbound_udp_node, china_dns_server, bypass_cn_traffic;
if (routing_mode !== 'custom') {
outbound_node = uci.get(cfgname, 'config', 'main_node') || 'nil';
outbound_udp_node = uci.get(cfgname, 'config', 'main_udp_node') || 'nil';
china_dns_server = uci.get(cfgname, 'config', 'china_dns_server');
} else {
outbound_node = uci.get(cfgname, 'routing', 'default_outbound') || 'nil';
bypass_cn_traffic = uci.get(cfgname, 'routing', 'bypass_cn_traffic') || '0';
}
let routing_port = uci.get(cfgname, 'config', 'routing_port');
if (routing_port === 'common')
routing_port = uci.get(cfgname, 'infra', 'common_port') || '22,53,80,143,443,465,587,853,873,993,995,8080,8443,9418';
const proxy_mode = uci.get(cfgname, 'config', 'proxy_mode') || 'redirect_tproxy',
ipv6_support = uci.get(cfgname, 'config', 'ipv6_support') || '0';
let self_mark, redirect_port,
tproxy_port, tproxy_mark,
tun_name, tun_mark;
if (match(proxy_mode, /redirect/)) {
self_mark = uci.get(cfgname, 'infra', 'self_mark') || '100';
redirect_port = uci.get(cfgname, 'infra', 'redirect_port') || '5331';
}
if (match(proxy_mode, /tproxy/))
if (outbound_udp_node !== 'nil' || routing_mode === 'custom') {
tproxy_port = uci.get(cfgname, 'infra', 'tproxy_port') || '5332';
tproxy_mark = resolve_mark(uci.get(cfgname, 'infra', 'tproxy_mark') || '101');
}
if (match(proxy_mode, /tun/)) {
tun_name = uci.get(cfgname, 'infra', 'tun_name') || 'singtun0';
tun_mark = resolve_mark(uci.get(cfgname, 'infra', 'tun_mark') || '102');
}
const control_options = [
"listen_interfaces", "lan_proxy_mode",
"lan_direct_mac_addrs", "lan_direct_ipv4_ips", "lan_direct_ipv6_ips",
"lan_proxy_mac_addrs", "lan_proxy_ipv4_ips", "lan_proxy_ipv6_ips",
"lan_gaming_mode_mac_addrs", "lan_gaming_mode_ipv4_ips", "lan_gaming_mode_ipv6_ips",
"lan_global_proxy_mac_addrs", "lan_global_proxy_ipv4_ips", "lan_global_proxy_ipv6_ips",
"wan_proxy_ipv4_ips", "wan_proxy_ipv6_ips",
"wan_direct_ipv4_ips", "wan_direct_ipv6_ips"
];
const control_info = {};
for (let i in control_options)
control_info[i] = uci.get(cfgname, 'control', i);
const dns_redirect = uci.get(cfgname, 'infra', 'dns_redirect') || '1';
const dns_hijacked = uci.get('dhcp', '@dnsmasq[0]', 'dns_redirect') || '0',
dns_port = uci.get('dhcp', '@dnsmasq[0]', 'port') || '53';
/* UCI config end */
-%}
{# Reserved addresses -#}
set homeproxy_local_addr_v4 {
type ipv4_addr
flags interval
auto-merge
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.31.196.0/24,
192.52.193.0/24,
192.88.99.0/24,
192.168.0.0/16,
192.175.48.0/24,
198.18.0.0/15,
198.51.100.0/24,
203.0.113.0/24,
224.0.0.0/4,
240.0.0.0/4
}
}
{% if (ipv6_support === '1'): %}
set homeproxy_local_addr_v6 {
type ipv6_addr
flags interval
auto-merge
elements = {
::/128,
::1/128,
::ffff:0:0/96,
100::/64,
64:ff9b::/96,
2001::/32,
2001:10::/28,
2001:20::/28,
2001:db8::/28,
2002::/16,
fc00::/7,
fe80::/10,
ff00::/8
}
}
{% endif %}
{% if (routing_mode === 'gfwlist'): %}
set homeproxy_gfw_list_v4 {
type ipv4_addr
flags interval
auto-merge
}
{% if (ipv6_support === '1'): %}
set homeproxy_gfw_list_v6 {
type ipv6_addr
flags interval
auto-merge
}
{% endif /* ipv6_support */ %}
{% elif (match(routing_mode, /mainland_china/) || bypass_cn_traffic === '1'): %}
set homeproxy_mainland_addr_v4 {
type ipv4_addr
flags interval
auto-merge
elements = {
{% for (let cnip4 in split(trim(readfile(resources_dir + '/china_ip4.txt')), /[\r\n]/)): %}
{{ cnip4 }},
{% endfor %}
}
}
{% if ((ipv6_support === '1') || china_dns_server): %}
set homeproxy_mainland_addr_v6 {
type ipv6_addr
flags interval
auto-merge
elements = {
{% for (let cnip6 in split(trim(readfile(resources_dir + '/china_ip6.txt')), /[\r\n]/)): %}
{{ cnip6 }},
{% endfor %}
}
}
{% endif /* ipv6_support */ %}
{% endif /* routing_mode */ %}
{# WAN ACL addresses #}
set homeproxy_wan_proxy_addr_v4 {
type ipv4_addr
flags interval
auto-merge
{% if (control_info.wan_proxy_ipv4_ips): %}
elements = { {{ join(', ', control_info.wan_proxy_ipv4_ips) }} }
{% endif %}
}
{% if (ipv6_support === '1'): %}
set homeproxy_wan_proxy_addr_v6 {
type ipv6_addr
flags interval
auto-merge
{% if (control_info.wan_proxy_ipv6_ips): %}
elements = { {{ join(', ', control_info.wan_proxy_ipv6_ips) }} }
{% endif /* wan_proxy_ipv6_ips*/ %}
}
{% endif /* ipv6_support */ %}
set homeproxy_wan_direct_addr_v4 {
type ipv4_addr
flags interval
auto-merge
{% if (control_info.wan_direct_ipv4_ips): %}
elements = { {{ join(', ', control_info.wan_direct_ipv4_ips) }} }
{% endif %}
}
{% if (ipv6_support === '1'): %}
set homeproxy_wan_direct_addr_v6 {
type ipv6_addr
flags interval
auto-merge
{% if (control_info.wan_direct_ipv6_ips): %}
elements = { {{ join(', ', control_info.wan_direct_ipv6_ips) }} }
{% endif /* wan_direct_ipv6_ips */ %}
}
{% endif /* ipv6_support */ %}
{% if (routing_port): %}
set homeproxy_routing_port {
type inet_service
flags interval
auto-merge
elements = { {{ join(', ', split(routing_port, ',')) }} }
}
{% endif %}
{# DNS hijack & TCP redirect #}
chain dstnat {
{% if ((dns_redirect === '1') && (dns_hijacked !== '1')): %}
{% if (control_info.listen_interfaces): %}
meta iifname {{ array_to_nftarr(control_info.listen_interfaces) }}
{%- endif /* listen_interfaces */ %}
meta nfproto { ipv4, ipv6 } udp dport 53 counter redirect to :{{ dns_port }} comment "!{{ cfgname }}: DNS hijack"
{% endif /* dns_hijacked */ %}
{% if (match(proxy_mode, /redirect/)): %}
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect_lanac
{% endif /* proxy_mode */ %}
}
{# TCP redirect #}
{% if (match(proxy_mode, /redirect/)): %}
chain homeproxy_redirect_proxy {
meta l4proto tcp counter redirect to :{{ redirect_port }}
}
chain homeproxy_redirect_proxy_port {
{% if (routing_port): %}
tcp dport != @homeproxy_routing_port counter return
{% endif %}
goto homeproxy_redirect_proxy
}
chain homeproxy_redirect_lanac {
{% if (control_info.listen_interfaces): %}
meta iifname != {{ array_to_nftarr(control_info.listen_interfaces) }} counter return
{% endif %}
meta mark {{ self_mark }} counter return
{% if (control_info.lan_proxy_mode === 'listed_only'): %}
{% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_redirect
{% endif /* lan_proxy_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect
{% endfor /* lan_proxy_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_redirect
{% endif /* lan_proxy_mac_addrs */ %}
{% elif (control_info.lan_proxy_mode === 'except_listed'): %}
{% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return
{% endif /* lan_direct_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_direct_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter return
{% endfor /* lan_direct_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_direct_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return
{% endif /* lan_direct_mac_addrs */ %}
{% endif /* lan_proxy_mode */ %}
{% if (control_info.lan_proxy_mode !== 'listed_only'): %}
counter goto homeproxy_redirect
{% endif %}
}
chain homeproxy_redirect {
meta mark {{ self_mark }} counter return
ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_redirect_proxy_port
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_redirect_proxy_port
{% endif %}
ip daddr @homeproxy_local_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_local_addr_v6 counter return
{% endif %}
{% if (routing_mode !== 'custom'): %}
{% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_redirect_proxy_port
{% endif /* lan_global_proxy_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect_proxy_port
{% endfor /* lan_global_proxy_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_redirect_proxy_port
{% endif /* lan_global_proxy_mac_addrs */ %}
{% endif /* routing_mode */ %}
ip daddr @homeproxy_wan_direct_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_wan_direct_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% if (routing_mode === 'gfwlist'): %}
ip daddr != @homeproxy_gfw_list_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_gfw_list_v6 counter return
{% endif /* ipv6_support */ %}
{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
ip daddr @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% elif (routing_mode === 'proxy_mainland_china'): %}
ip daddr != @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% endif /* routing_mode */ %}
{% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter goto homeproxy_redirect_proxy
{% endif /* lan_gaming_mode_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect_proxy
{% endfor /* lan_gaming_mode_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter goto homeproxy_redirect_proxy
{% endif /* lan_gaming_mode_mac_addrs */ %}
counter goto homeproxy_redirect_proxy_port
}
chain homeproxy_output_redir {
type nat hook output priority filter -105; policy accept
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect
}
{% endif %}
{# UDP tproxy #}
{% if (match(proxy_mode, /tproxy/) && (outbound_udp_node !== 'nil' || routing_mode === 'custom')): %}
chain homeproxy_mangle_tproxy {
meta l4proto udp meta mark set {{ tproxy_mark }} tproxy ip to 127.0.0.1:{{ tproxy_port }} counter accept
{% if (ipv6_support === '1'): %}
meta l4proto udp meta mark set {{ tproxy_mark }} tproxy ip6 to [::1]:{{ tproxy_port }} counter accept
{% endif %}
}
chain homeproxy_mangle_tproxy_port {
{% if (routing_port): %}
udp dport != @homeproxy_routing_port counter return
{% endif %}
goto homeproxy_mangle_tproxy
}
chain homeproxy_mangle_mark {
{% if (routing_port): %}
udp dport != @homeproxy_routing_port counter return
{% endif %}
meta l4proto udp meta mark set {{ tproxy_mark }} counter accept
}
chain homeproxy_mangle_lanac {
{% if (control_info.listen_interfaces): %}
meta iifname != {{ array_to_nftarr(uniq([...control_info.listen_interfaces, ...['lo']])) }} counter return
{% endif %}
meta iifname != lo udp dport 53 counter return
meta mark {{ self_mark }} counter return
{% if (control_info.lan_proxy_mode === 'listed_only'): %}
{% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_mangle_prerouting
{% endif /* lan_proxy_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_prerouting
{% endfor /* lan_proxy_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_mangle_prerouting
{% endif /* lan_proxy_mac_addrs */ %}
{% elif (control_info.lan_proxy_mode === 'except_listed'): %}
{% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return
{% endif /* lan_direct_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_direct_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter return
{% endfor /* lan_direct_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_direct_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return
{% endif /* lan_direct_mac_addrs */ %}
{% endif /* lan_proxy_mode */ %}
{% if (control_info.lan_proxy_mode !== 'listed_only'): %}
counter goto homeproxy_mangle_prerouting
{% endif %}
}
chain homeproxy_mangle_prerouting {
ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_tproxy_port
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_tproxy_port
{% endif %}
ip daddr @homeproxy_local_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_local_addr_v6 counter return
{% endif %}
{% if (routing_mode !== 'custom'): %}
{% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tproxy_port
{% endif /* lan_global_proxy_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tproxy_port
{% endfor /* lan_global_proxy_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_mangle_tproxy_port
{% endif /* lan_global_proxy_mac_addrs */ %}
{% endif /* routing_mode */ %}
ip daddr @homeproxy_wan_direct_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_wan_direct_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% if (routing_mode === 'gfwlist'): %}
ip daddr != @homeproxy_gfw_list_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_gfw_list_v6 counter return
{% endif /* ipv6_support */ %}
udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC"
{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
ip daddr @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% if (routing_mode !== 'custom'): %}
udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC"
{% endif /* routing_mode */ %}
{% elif (routing_mode === 'proxy_mainland_china'): %}
ip daddr != @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% endif /* routing_mode */ %}
{% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter goto homeproxy_mangle_tproxy
{% endif /* lan_gaming_mode_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tproxy
{% endfor /* lan_gaming_mode_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter goto homeproxy_mangle_tproxy
{% endif /* lan_gaming_mode_mac_addrs */ %}
counter goto homeproxy_mangle_tproxy_port
}
chain homeproxy_mangle_output {
meta mark {{ self_mark }} counter return
ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_mark
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_mark
{% endif %}
ip daddr @homeproxy_local_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_local_addr_v6 counter return
{% endif %}
ip daddr @homeproxy_wan_direct_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_wan_direct_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% if (routing_mode === 'gfwlist'): %}
ip daddr != @homeproxy_gfw_list_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_gfw_list_v6 counter return
{% endif /* ipv6_support */ %}
{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
ip daddr @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% elif (routing_mode === 'proxy_mainland_china'): %}
ip daddr != @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% endif /* routing_mode */ %}
counter goto homeproxy_mangle_mark
}
chain mangle_prerouting {
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto udp jump homeproxy_mangle_lanac
}
chain mangle_output {
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto udp jump homeproxy_mangle_output
}
{% endif %}
{# TUN #}
{% if (match(proxy_mode, /tun/)): %}
chain homeproxy_mangle_lanac {
iifname {{ tun_name }} counter return
udp dport 53 counter return
{% if (control_info.listen_interfaces): %}
meta iifname != {{ array_to_nftarr(control_info.listen_interfaces) }} counter return
{% endif %}
{% if (control_info.lan_proxy_mode === 'listed_only'): %}
{% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tun
{% endif /* lan_proxy_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tun
{% endfor /* lan_proxy_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_mangle_tun
{% endif /* lan_proxy_mac_addrs */ %}
{% elif (control_info.lan_proxy_mode === 'except_listed'): %}
{% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return
{% endif /* lan_direct_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_direct_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter return
{% endfor /* lan_direct_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_direct_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return
{% endif /* lan_direct_mac_addrs */ %}
{% endif /* lan_proxy_mode */ %}
{% if (control_info.lan_proxy_mode !== 'listed_only'): %}
counter goto homeproxy_mangle_tun
{% endif %}
}
chain homeproxy_mangle_tun_mark {
{% if (routing_port): %}
{% if (proxy_mode === 'tun'): %}
tcp dport != @homeproxy_routing_port counter return
{% endif /* proxy_mode */ %}
udp dport != @homeproxy_routing_port counter return
{% endif /* routing_port */ %}
counter meta mark set {{ tun_mark }}
}
chain homeproxy_mangle_tun {
iifname {{ tun_name }} counter return
ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_tun_mark
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_tun_mark
{% endif %}
ip daddr @homeproxy_local_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_local_addr_v6 counter return
{% endif %}
{% if (routing_mode !== 'custom'): %}
{% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tun_mark
{% endif /* lan_global_proxy_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tun_mark
{% endfor /* lan_global_proxy_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_mangle_tun_mark
{% endif /* lan_global_proxy_mac_addrs */ %}
{% endif /* routing_mode */ %}
{% if (control_info.wan_direct_ipv4_ips): %}
ip daddr {{ array_to_nftarr(control_info.wan_direct_ipv4_ips) }} counter return
{% endif /* wan_direct_ipv4_ips */ %}
{% if (control_info.wan_direct_ipv6_ips): %}
ip6 daddr {{ array_to_nftarr(control_info.wan_direct_ipv6_ips) }} counter return
{% endif /* wan_direct_ipv6_ips */ %}
{% if (routing_mode === 'gfwlist'): %}
ip daddr != @homeproxy_gfw_list_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_gfw_list_v6 counter return
{% endif /* ipv6_support */ %}
udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC"
{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
ip daddr @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% if (routing_mode !== 'custom'): %}
udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC"
{% endif /* routing_mode */ %}
{% elif (routing_mode === 'proxy_mainland_china'): %}
ip daddr != @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% endif /* routing_mode */ %}
{% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %}
ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter meta mark set {{ tun_mark }}
{% endif /* lan_gaming_mode_ipv4_ips */ %}
{% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %}
ip6 saddr {{ resolve_ipv6(ipv6) }} counter meta mark set {{ tun_mark }}
{% endfor /* lan_gaming_mode_ipv6_ips */ %}
{% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %}
ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter meta mark set {{ tun_mark }}
{% endif /* lan_gaming_mode_mac_addrs */ %}
counter goto homeproxy_mangle_tun_mark
}
chain mangle_prerouting {
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { {{ (proxy_mode === 'tun') ? 'tcp, udp' : 'udp' }} } jump homeproxy_mangle_lanac
}
chain mangle_output {
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { {{ (proxy_mode === 'tun') ? 'tcp, udp' : 'udp' }} } jump homeproxy_mangle_tun
}
{% endif %}
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/ucode
'use strict';
import { writefile } from 'fs';
import { cursor } from 'uci';
import { isEmpty, RUN_DIR } from 'homeproxy';
const cfgname = 'homeproxy';
const uci = cursor();
uci.load(cfgname);
const routing_mode = uci.get(cfgname, 'config', 'routing_mode') || 'bypass_mainland_china',
proxy_mode = uci.get(cfgname, 'config', 'proxy_mode') || 'redirect_tproxy';
let outbound_node, tun_name;
if (match(proxy_mode, /tun/)) {
if (routing_mode === 'custom')
outbound_node = uci.get(cfgname, 'routing', 'default_outbound') || 'nil';
else
outbound_node = uci.get(cfgname, 'config', 'main_node') || 'nil';
if (outbound_node !== 'nil')
tun_name = uci.get(cfgname, 'infra', 'tun_name') || 'singtun0';
}
const server_enabled = uci.get(cfgname, 'server', 'enabled');
let forward = [],
input = [];
if (tun_name) {
push(forward, `oifname ${tun_name} counter accept comment "!${cfgname}: accept tun forward"`);
push(input ,`iifname ${tun_name} counter accept comment "!${cfgname}: accept tun input"`);
}
if (server_enabled === '1') {
uci.foreach(cfgname, 'server', (s) => {
if (s.enabled !== '1' || s.firewall !== '1')
return;
let proto = s.network || '{ tcp, udp }';
push(input, `meta l4proto ${proto} th dport ${s.port} counter accept comment "!${cfgname}: accept server ${s['.name']}"`);
});
}
if (!isEmpty(forward))
writefile(RUN_DIR + '/fw4_forward.nft', join('\n', forward) + '\n');
if (!isEmpty(input))
writefile(RUN_DIR + '/fw4_input.nft', join('\n', input) + '\n');
File diff suppressed because it is too large. Load diff
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/ucode
/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2023 ImmortalWrt.org
*/
'use strict';
import { writefile } from 'fs';
import { cursor } from 'uci';
import {
isEmpty, strToBool, strToInt, strToTime,
removeBlankAttrs, HP_DIR, RUN_DIR
} from 'homeproxy';
/* UCI config start */
const uci = cursor();
const uciconfig = 'homeproxy';
uci.load(uciconfig);
const uciserver = 'server';
const log_level = uci.get(uciconfig, uciserver, 'log_level') || 'warn';
/* UCI config end */
const config = {};
/* Log */
config.log = {
disabled: false,
level: log_level,
output: RUN_DIR + '/sing-box-s.log',
timestamp: true
};
config.inbounds = [];
uci.foreach(uciconfig, uciserver, (cfg) => {
if (cfg.enabled !== '1')
return;
push(config.inbounds, {
type: cfg.type,
tag: 'cfg-' + cfg['.name'] + '-in',
listen: cfg.address || '::',
listen_port: strToInt(cfg.port),
bind_interface: cfg.bind_interface,
reuse_addr: strToBool(cfg.reuse_addr),
tcp_fast_open: strToBool(cfg.tcp_fast_open),
tcp_multi_path: strToBool(cfg.tcp_multi_path),
udp_fragment: strToBool(cfg.udp_fragment),
udp_timeout: strToTime(cfg.udp_timeout),
network: cfg.network,
/* AnyTLS */
padding_scheme: cfg.anytls_padding_scheme,
/* Hysteria */
up_mbps: strToInt(cfg.hysteria_up_mbps),
down_mbps: strToInt(cfg.hysteria_down_mbps),
obfs: cfg.hysteria_obfs_type ? {
type: cfg.hysteria_obfs_type,
password: cfg.hysteria_obfs_password
} : cfg.hysteria_obfs_password,
recv_window_conn: strToInt(cfg.hysteria_recv_window_conn),
recv_window_client: strToInt(cfg.hysteria_revc_window_client),
max_conn_client: strToInt(cfg.hysteria_max_conn_client),
disable_mtu_discovery: strToBool(cfg.hysteria_disable_mtu_discovery),
ignore_client_bandwidth: strToBool(cfg.hysteria_ignore_client_bandwidth),
masquerade: cfg.hysteria_masquerade,
/* Shadowsocks */
method: (cfg.type === 'shadowsocks') ? cfg.shadowsocks_encrypt_method : null,
password: (cfg.type in ['shadowsocks', 'shadowtls']) ? cfg.password : null,
/* Tuic */
congestion_control: cfg.tuic_congestion_control,
auth_timeout: strToTime(cfg.tuic_auth_timeout),
zero_rtt_handshake: strToBool(cfg.tuic_enable_zero_rtt),
heartbeat: strToTime(cfg.tuic_heartbeat),
/* AnyTLS / HTTP / Hysteria (2) / Mixed / Socks / Trojan / Tuic / VLESS / VMess */
users: (cfg.type !== 'shadowsocks') ? [
{
name: !(cfg.type in ['http', 'mixed', 'naive', 'socks']) ? 'cfg-' + cfg['.name'] + '-server' : null,
username: cfg.username,
password: cfg.password,
/* Hysteria */
auth: (cfg.hysteria_auth_type === 'base64') ? cfg.hysteria_auth_payload : null,
auth_str: (cfg.hysteria_auth_type === 'string') ? cfg.hysteria_auth_payload : null,
/* Tuic */
uuid: cfg.uuid,
/* VLESS / VMess */
flow: cfg.vless_flow,
alterId: strToInt(cfg.vmess_alterid)
}
] : null,
multiplex: (cfg.multiplex === '1') ? {
enabled: true,
padding: strToBool(cfg.multiplex_padding),
brutal: (cfg.multiplex_brutal === '1') ? {
enabled: true,
up_mbps: strToInt(cfg.multiplex_brutal_up),
down_mbps: strToInt(cfg.multiplex_brutal_down)
} : null
} : null,
tls: (cfg.tls === '1') ? {
enabled: true,
server_name: cfg.tls_sni,
alpn: cfg.tls_alpn,
min_version: cfg.tls_min_version,
max_version: cfg.tls_max_version,
cipher_suites: cfg.tls_cipher_suites,
certificate_path: cfg.tls_cert_path,
key_path: cfg.tls_key_path,
acme: (cfg.tls_acme === '1') ? {
domain: cfg.tls_acme_domain,
data_directory: HP_DIR + '/certs',
default_server_name: cfg.tls_acme_dsn,
email: cfg.tls_acme_email,
provider: cfg.tls_acme_provider,
disable_http_challenge: strToBool(cfg.tls_acme_dhc),
disable_tls_alpn_challenge: (cfg.tls_acme_dtac),
alternative_http_port: strToInt(cfg.tls_acme_ahp),
alternative_tls_port: strToInt(cfg.tls_acme_atp),
external_account: (cfg.tls_acme_external_account === '1') ? {
key_id: cfg.tls_acme_ea_keyid,
mac_key: cfg.tls_acme_ea_mackey
} : null,
dns01_challenge: (cfg.tls_dns01_challenge === '1') ? {
provider: cfg.tls_dns01_provider,
access_key_id: cfg.tls_dns01_ali_akid,
access_key_secret: cfg.tls_dns01_ali_aksec,
region_id: cfg.tls_dns01_ali_rid,
api_token: cfg.tls_dns01_cf_api_token
} : null
} : null,
ech: (cfg.tls_ech_key) ? {
enabled: true,
key: split(cfg.tls_ech_key, '\n'),
// config: split(cfg.tls_ech_config, '\n')
} : null,
reality: (cfg.tls_reality === '1') ? {
enabled: true,
private_key: cfg.tls_reality_private_key,
short_id: cfg.tls_reality_short_id,
max_time_difference: strToTime(cfg.tls_reality_max_time_difference),
handshake: {
server: cfg.tls_reality_server_addr,
server_port: strToInt(cfg.tls_reality_server_port)
}
} : null
} : null,
transport: !isEmpty(cfg.transport) ? {
type: cfg.transport,
host: cfg.http_host || cfg.httpupgrade_host,
path: cfg.http_path || cfg.ws_path,
headers: cfg.ws_host ? {
Host: cfg.ws_host
} : null,
method: cfg.http_method,
max_early_data: strToInt(cfg.websocket_early_data),
early_data_header_name: cfg.websocket_early_data_header,
service_name: cfg.grpc_servicename,
idle_timeout: strToTime(cfg.http_idle_timeout),
ping_timeout: strToTime(cfg.http_ping_timeout)
} : null
});
});
if (length(config.inbounds) === 0)
exit(1);
system('mkdir -p ' + RUN_DIR);
writefile(RUN_DIR + '/sing-box-s.json', sprintf('%.J\n', removeBlankAttrs(config)));
+230
View File
@@ -0,0 +1,230 @@
/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2023 ImmortalWrt.org
*/
import { mkstemp } from 'fs';
import { urldecode_params } from 'luci.http';
/* Global variables start */
export const HP_DIR = '/etc/homeproxy';
export const RUN_DIR = '/var/run/homeproxy';
/* Global variables end */
/* Utilities start */
/* Kanged from luci-app-commands */
export function shellQuote(s) {
return `'${replace(s, "'", "'\\''")}'`;
};
export function isBinary(str) {
for (let off = 0, byte = ord(str); off < length(str); byte = ord(str, ++off))
if (byte <= 8 || (byte >= 14 && byte <= 31))
return true;
return false;
};
export function executeCommand(...args) {
let outfd = mkstemp();
let errfd = mkstemp();
const exitcode = system(`${join(' ', args)} >&${outfd.fileno()} 2>&${errfd.fileno()}`);
outfd.seek(0);
errfd.seek(0);
const stdout = outfd.read(1024 * 512) ?? '';
const stderr = errfd.read(1024 * 512) ?? '';
outfd.close();
errfd.close();
const binary = isBinary(stdout);
return {
command: join(' ', args),
stdout: binary ? null : stdout,
stderr,
exitcode,
binary
};
};
export function getTime(epoch) {
const local_time = localtime(epoch);
return replace(replace(sprintf(
'%d-%2d-%2d@%2d:%2d:%2d',
local_time.year,
local_time.mon,
local_time.mday,
local_time.hour,
local_time.min,
local_time.sec
), ' ', '0'), '@', ' ');
};
export function wGET(url, ua) {
if (!url || type(url) !== 'string')
return null;
if (!ua)
ua = 'Wget/1.21 (HomeProxy, like v2rayN)';
const output = executeCommand(`/usr/bin/wget -qO- --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {};
return trim(output.stdout);
};
/* Utilities end */
/* String helper start */
export function isEmpty(res) {
return !res || res === 'nil' || (type(res) in ['array', 'object'] && length(res) === 0);
};
export function strToBool(str) {
return (str === '1') || null;
};
export function strToInt(str) {
return !isEmpty(str) ? (int(str) || null) : null;
};
export function strToTime(str) {
return !isEmpty(str) ? (str + 's') : null;
};
export function removeBlankAttrs(res) {
let content;
if (type(res) === 'object') {
content = {};
map(keys(res), (k) => {
if (type(res[k]) in ['array', 'object'])
content[k] = removeBlankAttrs(res[k]);
else if (res[k] !== null && res[k] !== '')
content[k] = res[k];
});
} else if (type(res) === 'array') {
content = [];
map(res, (k, i) => {
if (type(k) in ['array', 'object'])
push(content, removeBlankAttrs(k));
else if (k !== null && k !== '')
push(content, k);
});
} else
return res;
return content;
};
export function validateHostname(hostname) {
return (match(hostname, /^[a-zA-Z0-9_]+$/) != null ||
(match(hostname, /^[a-zA-Z0-9_][a-zA-Z0-9_%-.]*[a-zA-Z0-9]$/) &&
match(hostname, /[^0-9.]/)));
};
export function validation(datatype, data) {
if (!datatype || !data)
return null;
const ret = system(`/sbin/validate_data ${shellQuote(datatype)} ${shellQuote(data)} 2>/dev/null`);
return (ret === 0);
};
/* String helper end */
/* String parser start */
export function decodeBase64Str(str) {
if (isEmpty(str))
return null;
str = trim(str);
str = replace(str, '_', '/');
str = replace(str, '-', '+');
const padding = length(str) % 4;
if (padding)
str = str + substr('====', padding);
return b64dec(str);
};
export function parseURL(url) {
if (type(url) !== 'string')
return null;
const services = {
http: '80',
https: '443'
};
const objurl = {};
objurl.href = url;
url = replace(url, /#(.+)$/, (_, val) => {
objurl.hash = val;
return '';
});
url = replace(url, /^(\w[A-Za-z0-9\+\-\.]+):/, (_, val) => {
objurl.protocol = val;
return '';
});
url = replace(url, /\?(.+)/, (_, val) => {
objurl.search = val;
objurl.searchParams = urldecode_params(val);
return '';
});
url = replace(url, /^\/\/([^\/]+)/, (_, val) => {
val = replace(val, /^([^@]+)@/, (_, val) => {
objurl.userinfo = val;
return '';
});
val = replace(val, /:(\d+)$/, (_, val) => {
objurl.port = val;
return '';
});
if (validation('ip4addr', val) ||
validation('ip6addr', replace(val, /\[|\]/g, '')) ||
validation('hostname', val))
objurl.hostname = val;
return '';
});
objurl.pathname = url || '/';
if (!objurl.protocol || !objurl.hostname)
return null;
if (objurl.userinfo) {
objurl.userinfo = replace(objurl.userinfo, /:(.+)$/, (_, val) => {
objurl.password = val;
return '';
});
if (match(objurl.userinfo, /^[A-Za-z0-9\+\-\_\.]+$/)) {
objurl.username = objurl.userinfo;
delete objurl.userinfo;
} else {
delete objurl.userinfo;
delete objurl.password;
}
};
if (!objurl.port)
objurl.port = services[objurl.protocol];
objurl.host = objurl.hostname + (objurl.port ? `:${objurl.port}` : '');
objurl.origin = `${objurl.protocol}://${objurl.host}`;
return objurl;
};
/* String parser end */
+261
View File
@@ -0,0 +1,261 @@
#!/usr/bin/ucode
/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2025 ImmortalWrt.org
*/
'use strict';
import { cursor } from 'uci';
import { isEmpty, parseURL } from 'homeproxy';
const uci = cursor();
const uciconfig = 'homeproxy';
uci.load(uciconfig);
const uciinfra = 'infra',
ucimigration = 'migration',
ucimain = 'config',
ucinode = 'node',
ucidns = 'dns',
ucidnsserver = 'dns_server',
ucidnsrule = 'dns_rule',
ucirouting = 'routing',
uciroutingnode = 'routing_node',
uciroutingrule = 'routing_rule',
uciserver = 'server';
/* chinadns-ng has been removed */
if (uci.get(uciconfig, uciinfra, 'china_dns_port'))
uci.delete(uciconfig, uciinfra, 'china_dns_port');
/* chinadns server now only accepts single server */
const china_dns_server = uci.get(uciconfig, ucimain, 'china_dns_server');
if (type(china_dns_server) === 'array') {
uci.set(uciconfig, ucimain, 'china_dns_server', china_dns_server[0]);
} else {
if (match(china_dns_server, /,/))
uci.set(uciconfig, ucimain, 'china_dns_server', split(china_dns_server, ',')[0]);
}
/* github_token option has been moved to config section */
const github_token = uci.get(uciconfig, uciinfra, 'github_token');
if (github_token) {
uci.set(uciconfig, ucimain, 'github_token', github_token);
uci.delete(uciconfig, uciinfra, 'github_token')
}
/* ntp_server was introduced */
if (!uci.get(uciconfig, uciinfra, 'ntp_server'))
uci.set(uciconfig, uciinfra, 'ntp_server', 'nil');
/* tun_gso was deprecated in sb 1.11 */
if (!isEmpty(uci.get(uciconfig, uciinfra, 'tun_gso')))
uci.delete(uciconfig, uciinfra, 'tun_gso');
/* create migration section */
if (!uci.get(uciconfig, ucimigration))
uci.set(uciconfig, ucimigration, uciconfig);
/* delete old crontab command */
const migration_crontab = uci.get(uciconfig, ucimigration, 'crontab');
if (!migration_crontab) {
system('sed -i "/update_crond.sh/d" "/etc/crontabs/root" 2>"/dev/null"');
uci.set(uciconfig, ucimigration, 'crontab', '1');
}
/* log_level was introduced */
if (isEmpty(uci.get(uciconfig, ucimain, 'log_level')))
uci.set(uciconfig, ucimain, 'log_level', 'warn');
if (isEmpty(uci.get(uciconfig, uciserver, 'log_level')))
uci.set(uciconfig, uciserver, 'log_level', 'warn');
/* empty value defaults to all ports now */
if (uci.get(uciconfig, ucimain, 'routing_port') === 'all')
uci.delete(uciconfig, ucimain, 'routing_port');
/* experimental section was removed */
if (uci.get(uciconfig, 'experimental'))
uci.delete(uciconfig, 'experimental');
/* block-dns was removed from built-in dns servers */
const default_dns_server = uci.get(uciconfig, ucidns, 'default_server');
if (default_dns_server === 'block-dns') {
/* append a rule at last to block all DNS queries */
uci.set(uciconfig, '_migration_dns_final_block', ucidnsrule);
uci.set(uciconfig, '_migration_dns_final_block', 'label', 'migration_final_block_dns');
uci.set(uciconfig, '_migration_dns_final_block', 'enabled', '1');
uci.set(uciconfig, '_migration_dns_final_block', 'mode', 'default');
uci.set(uciconfig, '_migration_dns_final_block', 'action', 'reject');
uci.set(uciconfig, ucidns, 'default_server', 'default-dns');
}
const dns_server_migration = {};
/* DNS servers options */
uci.foreach(uciconfig, ucidnsserver, (cfg) => {
/* legacy format was deprecated in sb 1.12 */
if (cfg.address) {
const addr = parseURL((!match(cfg.address, /:\/\//) ? 'udp://' : '') + (validation('ip6addr', cfg.address) ? `[${cfg.address}]` : cfg.address));
/* RCode was moved into DNS rules */
if (addr.protocol === 'rcode') {
dns_server_migration[cfg['.name']] = { action: 'predefined' };
switch (addr.hostname) {
case 'success':
dns_server_migration[cfg['.name']].rcode = 'NOERROR';
break;
case 'format_error':
dns_server_migration[cfg['.name']].rcode = 'FORMERR';
break;
case 'server_failure':
dns_server_migration[cfg['.name']].rcode = 'SERVFAIL';
break;
case 'name_error':
dns_server_migration[cfg['.name']].rcode = 'NXDOMAIN';
break;
case 'not_implemented':
dns_server_migration[cfg['.name']].rcode = 'NOTIMP';
break;
case 'refused':
default:
dns_server_migration[cfg['.name']].rcode = 'REFUSED';
break;
}
uci.delete(uciconfig, cfg['.name']);
return;
}
uci.set(uciconfig, cfg['.name'], 'type', addr.protocol);
uci.set(uciconfig, cfg['.name'], 'server', addr.hostname);
uci.set(uciconfig, cfg['.name'], 'server_port', addr.port);
uci.set(uciconfig, cfg['.name'], 'path', (addr.pathname !== '/') ? addr.pathname : null);
uci.delete(uciconfig, cfg['.name'], 'address');
}
if (cfg.strategy) {
if (cfg['.name'] === default_dns_server)
uci.set(uciconfig, ucidns, 'default_strategy', cfg.strategy);
dns_server_migration[cfg['.name']] = { strategy: cfg.strategy };
uci.delete(uciconfig, cfg['.name'], 'strategy');
}
if (cfg.client_subnet) {
if (cfg['.name'] === default_dns_server)
uci.set(uciconfig, ucidns, 'client_subnet', cfg.client_subnet);
if (isEmpty(dns_server_migration[cfg['.name']]))
dns_server_migration[cfg['.name']] = {};
dns_server_migration[cfg['.name']].client_subnet = cfg.client_subnet;
uci.delete(uciconfig, cfg['.name'], 'client_subnet');
}
});
/* DNS rules options */
uci.foreach(uciconfig, ucidnsrule, (cfg) => {
/* outbound was removed in sb 1.12 */
if (cfg.outbound) {
uci.delete(uciconfig, cfg['.name']);
if (!cfg.enabled)
return;
map(cfg.outbound, (outbound) => {
switch (outbound) {
case 'direct-out':
case 'block-out':
break;
case 'any-out':
uci.set(uciconfig, ucirouting, 'default_outbound_dns', cfg.server);
break;
default:
uci.set(uciconfig, cfg.outbound, 'domain_resolver', cfg.server);
break;
}
});
return;
}
/* rule_set_ipcidr_match_source was renamed in sb 1.10 */
if (cfg.rule_set_ipcidr_match_source === '1')
uci.rename(uciconfig, cfg['.name'], 'rule_set_ipcidr_match_source', 'rule_set_ip_cidr_match_source');
/* block-dns was moved into action in sb 1.11 */
if (cfg.server === 'block-dns') {
uci.set(uciconfig, cfg['.name'], 'action', 'reject');
uci.delete(uciconfig, cfg['.name'], 'server');
} else if (!cfg.action) {
/* add missing 'action' field */
uci.set(uciconfig, cfg['.name'], 'action', 'route');
}
/* strategy and client_subnet were moved into dns rules */
if (dns_server_migration[cfg.server]) {
if (dns_server_migration[cfg.server].strategy)
uci.set(uciconfig, cfg['.name'], 'strategy', dns_server_migration[cfg.server].strategy);
if (dns_server_migration[cfg.server].client_subnet)
uci.set(uciconfig, cfg['.name'], 'client_subnet', dns_server_migration[cfg.server].client_subnet);
if (dns_server_migration[cfg.server].rcode) {
uci.set(uciconfig, cfg['.name'], 'action', 'predefined');
uci.set(uciconfig, cfg['.name'], 'rcode', dns_server_migration[cfg.server].rcode);
uci.delete(uciconfig, cfg['.name'], 'server');
}
}
});
/* nodes options */
uci.foreach(uciconfig, ucinode, (cfg) => {
/* tls_ech_tls_disable_drs is useless and deprecated in sb 1.12 */
if (!isEmpty(cfg.tls_ech_tls_disable_drs))
uci.delete(uciconfig, cfg['.name'], 'tls_ech_tls_disable_drs');
/* tls_ech_enable_pqss is useless and deprecated in sb 1.12 */
if (!isEmpty(cfg.tls_ech_enable_pqss))
uci.delete(uciconfig, cfg['.name'], 'tls_ech_enable_pqss');
/* wireguard_gso was deprecated in sb 1.11 */
if (!isEmpty(cfg.wireguard_gso))
uci.delete(uciconfig, cfg['.name'], 'wireguard_gso');
});
/* routing rules options */
uci.foreach(uciconfig, uciroutingrule, (cfg) => {
/* rule_set_ipcidr_match_source was renamed in sb 1.10 */
if (cfg.rule_set_ipcidr_match_source === '1')
uci.rename(uciconfig, cfg['.name'], 'rule_set_ipcidr_match_source', 'rule_set_ip_cidr_match_source');
/* block-out was moved into action in sb 1.11 */
if (cfg.outbound === 'block-out') {
uci.set(uciconfig, cfg['.name'], 'action', 'reject');
uci.delete(uciconfig, cfg['.name'], 'outbound');
} else if (!cfg.action) {
/* add missing 'action' field */
uci.set(uciconfig, cfg['.name'], 'action', 'route');
}
});
/* server options */
/* auto_firewall was moved into server options */
const auto_firewall = uci.get(uciconfig, uciserver, 'auto_firewall');
if (!isEmpty(auto_firewall))
uci.delete(uciconfig, uciserver, 'auto_firewall');
uci.foreach(uciconfig, uciserver, (cfg) => {
/* auto_firewall was moved into server options */
if (auto_firewall === '1')
uci.set(uciconfig, cfg['.name'], 'firewall' , '1');
/* sniff_override was deprecated in sb 1.11 */
if (!isEmpty(cfg.sniff_override))
uci.delete(uciconfig, cfg['.name'], 'sniff_override');
/* domain_strategy is now pointless without sniff override */
if (!isEmpty(cfg.domain_strategy))
uci.delete(uciconfig, cfg['.name'], 'domain_strategy');
});
if (!isEmpty(uci.changes(uciconfig)))
uci.commit(uciconfig);
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2023 ImmortalWrt.org
SCRIPTS_DIR="/etc/homeproxy/scripts"
for i in "china_ip4" "china_ip6" "gfw_list" "china_list"; do
"$SCRIPTS_DIR"/update_resources.sh "$i"
done
"$SCRIPTS_DIR"/update_subscriptions.uc
+89
View File
@@ -0,0 +1,89 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2022-2025 ImmortalWrt.org
NAME="homeproxy"
RESOURCES_DIR="/etc/$NAME/resources"
mkdir -p "$RESOURCES_DIR"
RUN_DIR="/var/run/$NAME"
LOG_PATH="$RUN_DIR/$NAME.log"
mkdir -p "$RUN_DIR"
log() {
echo -e "$(date "+%Y-%m-%d %H:%M:%S") $*" >> "$LOG_PATH"
}
to_upper() {
echo -e "$1" | tr "[a-z]" "[A-Z]"
}
check_list_update() {
local LIST_FILE="$1"
local REPO_NAME="$2"
local REPO_BRANCH="$3"
local REPO_FILE="$4"
local LOCK_FILE="$RUN_DIR/update_resources-$LIST_FILE.lock"
local GITHUB_TOKEN="$(uci -q get homeproxy.config.github_token)"
exec 200>"$LOCK_FILE"
if ! flock -n 200 &> "/dev/null"; then
log "[$(to_upper "$LIST_FILE")] A task is already running."
return 2
fi
local NEW_VER=$(curl -sL ${GITHUB_TOKEN:+-H "Authorization: Bearer $GITHUB_TOKEN"} "https://api.github.com/repos/$REPO_NAME/releases/latest" | jsonfilter -e "@.tag_name")
if [ -z "$NEW_VER" ]; then
log "[$(to_upper "$LIST_FILE")] Failed to get the latest version, please retry later."
return 1
fi
local OLD_VER=$(cat "$RESOURCES_DIR/$LIST_FILE.ver" 2>/dev/null || echo "NOT FOUND")
if [ "$OLD_VER" = "$NEW_VER" ]; then
log "[$(to_upper "$LIST_FILE")] Current version: $NEW_VER."
log "[$(to_upper "$LIST_FILE")] You're already at the latest version."
return 3
else
log "[$(to_upper "$LIST_FILE")] Local version: $OLD_VER, latest version: $NEW_VER."
fi
if ! curl -sL -o "$RUN_DIR/$REPO_FILE" "https://cdn.jsdelivr.net/gh/$REPO_NAME@$REPO_BRANCH/$REPO_FILE" || [ ! -s "$RUN_DIR/$REPO_FILE" ]; then
rm -f "$RUN_DIR/$REPO_FILE"
log "[$(to_upper "$LIST_FILE")] Update failed."
return 1
fi
mv -f "$RUN_DIR/$REPO_FILE" "$RESOURCES_DIR/$LIST_FILE.${REPO_FILE##*.}"
echo -e "$NEW_VER" > "$RESOURCES_DIR/$LIST_FILE.ver"
log "[$(to_upper "$LIST_FILE")] Successfully updated."
return 0
}
case "$1" in
"china_ip4")
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "cncidr.txt" && \
sed -i "/IP-CIDR6,/d; s/IP-CIDR,//g" "$RESOURCES_DIR/china_ip4.txt"
;;
"china_ip6")
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "cncidr.txt" && \
sed -i "/IP-CIDR,/d; s/IP-CIDR6,//g" "$RESOURCES_DIR/china_ip6.txt"
;;
"gfw_list")
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "gfw.txt" && \
sed -i "s/^\.//g" "$RESOURCES_DIR/gfw_list.txt"
;;
"china_list")
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "direct.txt" && \
sed -i "s/^\.//g" "$RESOURCES_DIR/china_list.txt"
;;
*)
echo -e "Usage: $0 <china_ip4 / china_ip6 / gfw_list / china_list>"
exit 1
;;
esac
File diff suppressed because it is too large. Load diff
+338
View File
@@ -0,0 +1,338 @@
#!/bin/sh /etc/rc.common
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2022-2023 ImmortalWrt.org
USE_PROCD=1
START=99
STOP=10
CONF="homeproxy"
PROG="/usr/bin/sing-box"
HP_DIR="/etc/homeproxy"
RUN_DIR="/var/run/homeproxy"
LOG_PATH="$RUN_DIR/homeproxy.log"
# we don't know which is the default server, just take the first one
DNSMASQ_UCI_CONFIG="$(uci -q show "dhcp.@dnsmasq[0]" | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')"
if [ -f "/tmp/etc/dnsmasq.conf.$DNSMASQ_UCI_CONFIG" ]; then
DNSMASQ_DIR="$(awk -F '=' '/^conf-dir=/ {print $2}' "/tmp/etc/dnsmasq.conf.$DNSMASQ_UCI_CONFIG")/dnsmasq-homeproxy.d"
else
DNSMASQ_DIR="/tmp/dnsmasq.d/dnsmasq-homeproxy.d"
fi
log() {
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [DAEMON] $*" >> "$LOG_PATH"
}
start_service() {
config_load "$CONF"
local routing_mode proxy_mode
config_get routing_mode "config" "routing_mode" "bypass_mainland_china"
config_get proxy_mode "config" "proxy_mode" "redirect_tproxy"
local outbound_node
if [ "$routing_mode" != "custom" ]; then
config_get outbound_node "config" "main_node" "nil"
else
config_get outbound_node "routing" "default_outbound" "nil"
fi
local server_enabled
config_get_bool server_enabled "server" "enabled" "0"
if [ "$outbound_node" = "nil" ] && [ "$server_enabled" = "0" ]; then
return 1
fi
mkdir -p "$RUN_DIR"
if [ "$outbound_node" != "nil" ]; then
# Generate/Validate client config
ucode -S "$HP_DIR/scripts/generate_client.uc" 2>>"$LOG_PATH"
if [ ! -e "$RUN_DIR/sing-box-c.json" ]; then
log "Error: failed to generate client configuration."
return 1
elif ! "$PROG" check --config "$RUN_DIR/sing-box-c.json" 2>>"$LOG_PATH"; then
log "Error: wrong client configuration detected."
return 1
fi
# Auto update
local auto_update auto_update_time
config_get_bool auto_update "subscription" "auto_update" "0"
if [ "$auto_update" = "1" ]; then
config_get auto_update_time "subscription" "auto_update_time" "0 */6 * * *"
sed -i "/#${CONF}_autosetup/d" "/etc/crontabs/root" 2>"/dev/null"
echo -e "$auto_update_time $HP_DIR/scripts/update_crond.sh #${CONF}_autosetup" >> "/etc/crontabs/root"
/etc/init.d/cron restart
fi
# DNSMasq rules
local ipv6_support dns_port
config_get_bool ipv6_support "config" "ipv6_support" "0"
config_get dns_port "infra" "dns_port" "5333"
mkdir -p "$DNSMASQ_DIR"
echo -e "conf-dir=$DNSMASQ_DIR" > "$DNSMASQ_DIR/../dnsmasq-homeproxy.conf"
case "$routing_mode" in
"bypass_mainland_china"|"custom"|"global")
cat <<-EOF >> "$DNSMASQ_DIR/redirect-dns.conf"
no-poll
no-resolv
server=127.0.0.1#$dns_port
EOF
;;
"gfwlist")
local gfw_nftset_v6
[ "$ipv6_support" -eq "0" ] || gfw_nftset_v6=",6#inet#fw4#homeproxy_gfw_list_v6"
sed -r -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port\nnftset=\/\1\\/4#inet#fw4#homeproxy_gfw_list_v4$gfw_nftset_v6/g" \
"$HP_DIR/resources/gfw_list.txt" > "$DNSMASQ_DIR/gfw_list.conf"
;;
"proxy_mainland_china")
sed -r -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port/g" \
"$HP_DIR/resources/china_list.txt" > "$DNSMASQ_DIR/china_list.conf"
;;
esac
if [ "$routing_mode" != "custom" ] && [ -s "$HP_DIR/resources/proxy_list.txt" ]; then
local wan_nftset_v6
[ "$ipv6_support" -eq "0" ] || wan_nftset_v6=",6#inet#fw4#homeproxy_wan_proxy_addr_v6"
sed -r -e '/^\s*$/d' -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port\nnftset=\/\1\\/4#inet#fw4#homeproxy_wan_proxy_addr_v4$wan_nftset_v6/g" \
"$HP_DIR/resources/proxy_list.txt" > "$DNSMASQ_DIR/proxy_list.conf"
fi
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1
# Setup routing table
local table_mark
config_get table_mark "infra" "table_mark" "100"
case "$proxy_mode" in
"redirect_tproxy")
local outbound_udp_node
config_get outbound_udp_node "config" "main_udp_node" "nil"
if [ "$outbound_udp_node" != "nil" ] || [ "$routing_mode" = "custom" ]; then
local tproxy_mark
config_get tproxy_mark "infra" "tproxy_mark" "101"
ip rule add fwmark "$tproxy_mark" table "$table_mark"
ip route add local 0.0.0.0/0 dev lo table "$table_mark"
if [ "$ipv6_support" -eq "1" ]; then
ip -6 rule add fwmark "$tproxy_mark" table "$table_mark"
ip -6 route add local ::/0 dev lo table "$table_mark"
fi
fi
;;
"redirect_tun"|"tun")
local tun_name tun_mark
config_get tun_name "infra" "tun_name" "singtun0"
config_get tun_mark "infra" "tun_mark" "102"
ip tuntap add mode tun user root name "$tun_name"
sleep 1s
ip link set "$tun_name" up
ip route replace default dev "$tun_name" table "$table_mark"
ip rule add fwmark "$tun_mark" lookup "$table_mark"
ip -6 route replace default dev "$tun_name" table "$table_mark"
ip -6 rule add fwmark "$tun_mark" lookup "$table_mark"
;;
esac
# sing-box (client)
procd_open_instance "sing-box-c"
procd_set_param command "$PROG"
procd_append_param command run --config "$RUN_DIR/sing-box-c.json"
# QUIC-GO GSO is broken on kernel 6.6 currently
uname -r | grep -Eq "^6\.6" && procd_set_param env "QUIC_GO_DISABLE_GSO"="true"
if [ -x "/sbin/ujail" ] && [ "$routing_mode" != "custom" ] && ! grep -Eq '"type": "(wireguard|tun)"' "$RUN_DIR/sing-box-c.json"; then
procd_add_jail "sing-box-c" log procfs
procd_add_jail_mount "$RUN_DIR/sing-box-c.json"
procd_add_jail_mount_rw "$RUN_DIR/sing-box-c.log"
[ "$routing_mode" != "bypass_mainland_china" ] || procd_add_jail_mount_rw "$RUN_DIR/cache.db"
procd_add_jail_mount "$HP_DIR/certs/"
procd_add_jail_mount "/etc/ssl/"
procd_add_jail_mount "/etc/localtime"
procd_add_jail_mount "/etc/TZ"
procd_set_param capabilities "/etc/capabilities/homeproxy.json"
procd_set_param no_new_privs 1
procd_set_param user sing-box
procd_set_param group sing-box
fi
procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000"
procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
fi
if [ "$server_enabled" = "1" ]; then
# Generate/Validate server config
ucode -S "$HP_DIR/scripts/generate_server.uc" 2>>"$LOG_PATH"
if [ ! -e "$RUN_DIR/sing-box-s.json" ]; then
log "Error: failed to generate server configuration."
return 1
elif ! "$PROG" check --config "$RUN_DIR/sing-box-s.json" 2>>"$LOG_PATH"; then
log "Error: wrong server configuration detected."
return 1
fi
# sing-box (server)
procd_open_instance "sing-box-s"
procd_set_param command "$PROG"
procd_append_param command run --config "$RUN_DIR/sing-box-s.json"
# QUIC-GO GSO is broken on kernel 6.6 currently
uname -r | grep -Eq "^6\.6" && procd_set_param env "QUIC_GO_DISABLE_GSO"="true"
if [ -x "/sbin/ujail" ]; then
procd_add_jail "sing-box-s" log procfs
procd_add_jail_mount "$RUN_DIR/sing-box-s.json"
procd_add_jail_mount_rw "$RUN_DIR/sing-box-s.log"
procd_add_jail_mount_rw "$HP_DIR/certs/"
procd_add_jail_mount "/etc/acme/"
procd_add_jail_mount "/etc/ssl/"
procd_add_jail_mount "/etc/localtime"
procd_add_jail_mount "/etc/TZ"
procd_set_param capabilities "/etc/capabilities/homeproxy.json"
procd_set_param no_new_privs 1
procd_set_param user sing-box
procd_set_param group sing-box
fi
procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000"
procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
fi
# log-cleaner
procd_open_instance "log-cleaner"
procd_set_param command "$HP_DIR/scripts/clean_log.sh"
procd_set_param respawn
procd_close_instance
case "$routing_mode" in
"bypass_mainland_china")
# Prepare cache db
[ -e "$RUN_DIR/cache.db" ] || touch "$RUN_DIR/cache.db"
;;
"custom")
# Prepare ruleset directory
[ -d "$HP_DIR/ruleset" ] || mkdir -p "$HP_DIR/ruleset"
;;
esac
[ "$outbound_node" = "nil" ] || echo > "$RUN_DIR/sing-box-c.log"
if [ "$server_enabled" = "1" ]; then
echo > "$RUN_DIR/sing-box-s.log"
mkdir -p "$HP_DIR/certs"
fi
# Update permissions for ujail
chown -R sing-box:sing-box "$RUN_DIR"
# Setup firewall
ucode "$HP_DIR/scripts/firewall_pre.uc"
[ "$outbound_node" = "nil" ] || utpl -S "$HP_DIR/scripts/firewall_post.ut" > "$RUN_DIR/fw4_post.nft"
fw4 reload >"/dev/null" 2>&1
log "sing-box $(sing-box version -n) started."
}
stop_service() {
sed -i "/#${CONF}_autosetup/d" "/etc/crontabs/root" 2>"/dev/null"
/etc/init.d/cron restart >"/dev/null" 2>&1
# Setup firewall
# Load config
config_load "$CONF"
local table_mark tproxy_mark tun_mark tun_name
config_get table_mark "infra" "table_mark" "100"
config_get tproxy_mark "infra" "tproxy_mark" "101"
config_get tun_mark "infra" "tun_mark" "102"
config_get tun_name "infra" "tun_name" "singtun0"
# Tproxy
ip rule del fwmark "$tproxy_mark" table "$table_mark" 2>"/dev/null"
ip route del local 0.0.0.0/0 dev lo table "$table_mark" 2>"/dev/null"
ip -6 rule del fwmark "$tproxy_mark" table "$table_mark" 2>"/dev/null"
ip -6 route del local ::/0 dev lo table "$table_mark" 2>"/dev/null"
# TUN
ip route del default dev "$tun_name" table "$table_mark" 2>"/dev/null"
ip rule del fwmark "$tun_mark" table "$table_mark" 2>"/dev/null"
ip -6 route del default dev "$tun_name" table "$table_mark" 2>"/dev/null"
ip -6 rule del fwmark "$tun_mark" table "$table_mark" 2>"/dev/null"
# Nftables rules
for i in "homeproxy_dstnat_redir" "homeproxy_output_redir" \
"homeproxy_redirect" "homeproxy_redirect_proxy" \
"homeproxy_redirect_proxy_port" "homeproxy_redirect_lanac" \
"homeproxy_mangle_prerouting" "homeproxy_mangle_output" \
"homeproxy_mangle_tproxy" "homeproxy_mangle_tproxy_port" \
"homeproxy_mangle_tproxy_lanac" "homeproxy_mangle_mark" \
"homeproxy_mangle_tun" "homeproxy_mangle_tun_mark"; do
nft flush chain inet fw4 "$i"
nft delete chain inet fw4 "$i"
done 2>"/dev/null"
for i in "homeproxy_local_addr_v4" "homeproxy_local_addr_v6" \
"homeproxy_gfw_list_v4" "homeproxy_gfw_list_v6" \
"homeproxy_mainland_addr_v4" "homeproxy_mainland_addr_v6" \
"homeproxy_wan_proxy_addr_v4" "homeproxy_wan_proxy_addr_v6" \
"homeproxy_wan_direct_addr_v4" "homeproxy_wan_direct_addr_v6" \
"homeproxy_routing_port"; do
nft flush set inet fw4 "$i"
nft delete set inet fw4 "$i"
done 2>"/dev/null"
echo 2>"/dev/null" > "$RUN_DIR/fw4_forward.nft"
echo 2>"/dev/null" > "$RUN_DIR/fw4_input.nft"
echo 2>"/dev/null" > "$RUN_DIR/fw4_post.nft"
fw4 reload >"/dev/null" 2>&1
# Remove DNS hijack
rm -rf "$DNSMASQ_DIR/../dnsmasq-homeproxy.conf" "$DNSMASQ_DIR"
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1
rm -f "$RUN_DIR/sing-box-c.json" "$RUN_DIR/sing-box-c.log" \
"$RUN_DIR/sing-box-s.json" "$RUN_DIR/sing-box-s.log"
log "Service stopped."
}
service_stopped() {
# Load config
config_load "$CONF"
local tun_name
config_get tun_name "infra" "tun_name" "singtun0"
# TUN
ip link set "$tun_name" down 2>"/dev/null"
ip tuntap del mode tun name "$tun_name" 2>"/dev/null"
}
reload_service() {
log "Reloading service..."
stop
start
}
service_triggers() {
procd_add_reload_trigger "$CONF"
procd_add_interface_trigger "interface.*.up" wan /etc/init.d/$CONF reload
}
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
[ -f "/www/luci-static/resources/icons/loading.gif" ] && \
sed -i "s,/loading.svg,/loading.gif,g" "/www/luci-static/resources/view/homeproxy/status.js"
uci -q batch <<-EOF >"/dev/null"
delete firewall.homeproxy_pre
delete firewall.homeproxy_forward
set firewall.homeproxy_forward=include
set firewall.homeproxy_forward.type=nftables
set firewall.homeproxy_forward.path="/var/run/homeproxy/fw4_forward.nft"
set firewall.homeproxy_forward.position="chain-pre"
set firewall.homeproxy_forward.chain="forward"
delete firewall.homeproxy_input
set firewall.homeproxy_input=include
set firewall.homeproxy_input.type=nftables
set firewall.homeproxy_input.path="/var/run/homeproxy/fw4_input.nft"
set firewall.homeproxy_input.position="chain-pre"
set firewall.homeproxy_input.chain="input"
delete firewall.homeproxy_post
set firewall.homeproxy_post=include
set firewall.homeproxy_post.type=nftables
set firewall.homeproxy_post.path="/var/run/homeproxy/fw4_post.nft"
set firewall.homeproxy_post.position="table-post"
commit firewall
EOF
exit 0
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
ucode "/etc/homeproxy/scripts/migrate_config.uc"
exit 0
@@ -0,0 +1,45 @@
{
"admin/services/homeproxy": {
"title": "HomeProxy",
"order": 10,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-homeproxy" ],
"uci": { "homeproxy": true }
}
},
"admin/services/homeproxy/client": {
"title": "Client Settings",
"order": 10,
"action": {
"type": "view",
"path": "homeproxy/client"
}
},
"admin/services/homeproxy/node": {
"title": "Node Settings",
"order": 15,
"action": {
"type": "view",
"path": "homeproxy/node"
}
},
"admin/services/homeproxy/server": {
"title": "Server Settings",
"order": 20,
"action": {
"type": "view",
"path": "homeproxy/server"
}
},
"admin/services/homeproxy/status": {
"title": "Service Status",
"order": 30,
"action": {
"type": "view",
"path": "homeproxy/status"
}
}
}
@@ -0,0 +1,24 @@
{
"luci-app-homeproxy": {
"description": "Grant access to homeproxy configuration",
"read": {
"file": {
"/etc/homeproxy/scripts/update_subscriptions.uc": [ "exec" ],
"/var/run/homeproxy/homeproxy.log": [ "read" ],
"/var/run/homeproxy/sing-box-c.log": [ "read" ],
"/var/run/homeproxy/sing-box-s.log": [ "read" ]
},
"ubus": {
"service": [ "list" ],
"luci.homeproxy": [ "*" ]
},
"uci": [ "homeproxy" ]
},
"write": {
"file": {
"/tmp/homeproxy_certificate.tmp": [ "write" ]
},
"uci": [ "homeproxy" ]
}
}
}
File diff suppressed because it is too large. Load diff