#!/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 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") [ "$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" ;; "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 [ "$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_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" 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 # 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" chmod 0644 "$HP_DIR/resources/gfw_list.txt" fi if [ "$server_enabled" = "1" ]; then echo > "$RUN_DIR/sing-box-s.log" mkdir -p "$HP_DIR/certs" fi chown -R sing-box:sing-box "$RUN_DIR" # 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 }