388 lines
13 KiB
Bash
Executable File
388 lines
13 KiB
Bash
Executable File
#!/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" "2"
|
|
echo -e "0 $auto_update_time * * * $HP_DIR/scripts/update_crond.sh" >> "/etc/crontabs/root"
|
|
/etc/init.d/cron restart
|
|
fi
|
|
|
|
# DNSMasq rules
|
|
local ipv6_support
|
|
config_get_bool ipv6_support "config" "ipv6_support" "0"
|
|
local dns_port china_dns_server china_dns_port
|
|
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
|
|
"gfwlist")
|
|
[ "$ipv6_support" -eq "0" ] || local 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"
|
|
;;
|
|
"bypass_mainland_china")
|
|
config_get china_dns_server "config" "china_dns_server"
|
|
config_get china_dns_port "infra" "china_dns_port" "5334"
|
|
|
|
if [ -e "/usr/bin/chinadns-ng" ] && [ -n "$china_dns_server" ]; then
|
|
cat <<-EOF >> "$DNSMASQ_DIR/redirect-dns.conf"
|
|
no-poll
|
|
no-resolv
|
|
server=127.0.0.1#$china_dns_port
|
|
EOF
|
|
else
|
|
china_dns_server=""
|
|
sed -r -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port/g" \
|
|
"$HP_DIR/resources/gfw_list.txt" > "$DNSMASQ_DIR/gfw_list.conf"
|
|
fi
|
|
;;
|
|
"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"
|
|
;;
|
|
"custom"|"global")
|
|
cat <<-EOF >> "$DNSMASQ_DIR/redirect-dns.conf"
|
|
no-poll
|
|
no-resolv
|
|
server=127.0.0.1#$dns_port
|
|
EOF
|
|
;;
|
|
esac
|
|
|
|
if [ "$routing_mode" != "custom" ] && [ -s "$HP_DIR/resources/proxy_list.txt" ]; then
|
|
[ "$ipv6_support" -eq "0" ] || local 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"
|
|
|
|
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"
|
|
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
|
|
|
|
# chinadns-ng
|
|
if [ -n "$china_dns_server" ]; then
|
|
local wandns="$(ifstatus wan | jsonfilter -e '@["dns-server"][0]' || echo "119.29.29.29")"
|
|
china_dns_server="${china_dns_server/wan/$wandns}"
|
|
china_dns_server="${china_dns_server// /,}"
|
|
|
|
for i in $(seq 1 "$(grep -c "processor" "/proc/cpuinfo")"); do
|
|
procd_open_instance "chinadns-ng-$i"
|
|
|
|
procd_set_param command "/usr/bin/chinadns-ng"
|
|
procd_append_param command --bind-port "$china_dns_port"
|
|
procd_append_param command --china-dns "$china_dns_server"
|
|
procd_append_param command --trust-dns "127.0.0.1#$dns_port"
|
|
procd_append_param command --ipset-name4 "inet@fw4@homeproxy_mainland_addr_v4"
|
|
procd_append_param command --ipset-name6 "inet@fw4@homeproxy_mainland_addr_v6"
|
|
procd_append_param command --chnlist-file "$HP_DIR/resources/china_list.txt"
|
|
procd_append_param command --gfwlist-file "$HP_DIR/resources/gfw_list.txt"
|
|
procd_append_param command --reuse-port
|
|
|
|
if chinadns-ng --version | grep -q "target:"; then
|
|
procd_append_param command --cache 10000
|
|
procd_append_param command --cache-stale 3600
|
|
procd_append_param command --verdict-cache 10000
|
|
[ "$ipv6_support" -eq "1" ] || procd_append_param command --no-ipv6=ip:non_china
|
|
else
|
|
[ "$ipv6_support" -eq "1" ] || procd_append_param command --no-ipv6=tC
|
|
fi
|
|
|
|
if [ -x "/sbin/ujail" ]; then
|
|
procd_add_jail "chinadns-ng" log
|
|
procd_add_jail_mount "$HP_DIR/resources/china_list.txt"
|
|
procd_add_jail_mount "$HP_DIR/resources/gfw_list.txt"
|
|
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
|
|
done
|
|
fi
|
|
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"
|
|
|
|
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 "$HP_DIR/certs/"
|
|
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
|
|
|
|
# Prepare ruleset directory for custom routing mode
|
|
if [ "$routing_mode" = "custom" ]; then
|
|
[ -d "$HP_DIR/ruleset" ] || mkdir -p "$HP_DIR/ruleset"
|
|
fi
|
|
|
|
# Update permissions for ujail
|
|
if [ "$outbound_node" != "nil" ]; then
|
|
echo > "$RUN_DIR/sing-box-c.log"
|
|
chown sing-box:sing-box "$RUN_DIR/sing-box-c.log"
|
|
chown sing-box:sing-box "$RUN_DIR/sing-box-c.json"
|
|
chmod 0644 "$HP_DIR/resources/gfw_list.txt"
|
|
fi
|
|
if [ "$server_enabled" = "1" ]; then
|
|
echo > "$RUN_DIR/sing-box-s.log"
|
|
chown sing-box:sing-box "$RUN_DIR/sing-box-s.log"
|
|
chown sing-box:sing-box "$RUN_DIR/sing-box-s.json"
|
|
fi
|
|
|
|
# Setup firewall
|
|
utpl -S "$HP_DIR/scripts/firewall_pre.ut" > "$RUN_DIR/fw4_pre.nft"
|
|
[ "$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 version | awk 'NR==1{print $1,$3}') started."
|
|
}
|
|
|
|
stop_service() {
|
|
sed -i "/update_crond.sh/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 > "$RUN_DIR/fw4_pre.nft" 2>"/dev/null"
|
|
echo > "$RUN_DIR/fw4_post.nft" 2>"/dev/null"
|
|
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
|
|
}
|