476 lines
14 KiB
Bash
Executable File
476 lines
14 KiB
Bash
Executable File
#!/bin/sh /etc/rc.common
|
|
|
|
. "${IPKG_INSTROOT}/lib/functions/network.sh"
|
|
|
|
USE_PROCD=1
|
|
|
|
START=99
|
|
STOP=10
|
|
|
|
CONF="fchomo"
|
|
PROG="/usr/bin/mihomo"
|
|
|
|
HM_DIR="/etc/fchomo"
|
|
TEMPS_DIR="$HM_DIR/templates"
|
|
EXE_DIR="/usr/libexec/fchomo"
|
|
SDL_DIR="/usr/share/fchomo"
|
|
RUN_DIR="/var/run/fchomo"
|
|
LOG_PATH="$RUN_DIR/fchomo.log"
|
|
|
|
# Compatibility
|
|
[ -x "$(command -v apk)" ] && OPM='apk' || OPM='opkg'
|
|
#
|
|
# thanks to homeproxy
|
|
# 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-fchomo.d"
|
|
else
|
|
DNSMASQ_DIR="/tmp/dnsmasq.d/dnsmasq-fchomo.d"
|
|
fi
|
|
#
|
|
# opmc action $@
|
|
opmc() {
|
|
local action="$1"; shift;
|
|
|
|
if [ "$OPM" = "apk" ]; then
|
|
case "$action" in
|
|
"list")
|
|
action="list -q";;
|
|
"list-installed")
|
|
action="list -qI";;
|
|
esac
|
|
fi
|
|
|
|
$OPM $action "$@"
|
|
}
|
|
|
|
config_load "$CONF"
|
|
|
|
# define global var: DEF_WAN DEF_WAN6 NIC_* NIC6_*
|
|
define_nic() {
|
|
local dev sub addr
|
|
# get all active NICs
|
|
for dev in $(ls /sys/class/net/); do
|
|
#ipv4
|
|
sub=$(ip -o -4 addr|sed -En "s|.*${dev}\s+inet\s+([0-9\./]+).*|\1|gp")
|
|
eval "NIC_${dev//[[:punct:]]/_}=\"\$sub\""
|
|
#ipv6
|
|
sub=$(ip -o -6 addr|sed -En "s|.*${dev}\s+inet6\s+([A-Za-z0-9\./:]+).*|\1|gp")
|
|
# ref: https://github.com/openwrt/openwrt/blob/main/package/base-files/files/lib/functions/network.sh#L53 #network_get_subnet6()
|
|
for _ in $sub; do
|
|
for addr in $sub; do
|
|
case "$addr" in fe[8ab]?:*|f[cd]??:*)
|
|
continue
|
|
esac
|
|
sub=$addr; break
|
|
done
|
|
# Attempt to return first non-fe80::/10 range
|
|
for addr in $sub; do
|
|
case "$addr" in fe[8ab]?:*)
|
|
continue
|
|
esac
|
|
sub=$addr; break
|
|
done
|
|
# Return first item
|
|
for addr in $sub; do
|
|
sub=$addr; break
|
|
done
|
|
done
|
|
eval "NIC6_${dev//[[:punct:]]/_}=\"\$sub\""
|
|
done
|
|
# get default gateway 0.0.0.0/::
|
|
network_find_wan DEF_WAN true
|
|
network_find_wan6 DEF_WAN6 true
|
|
|
|
return 0
|
|
}
|
|
define_nic
|
|
|
|
load_interfaces() {
|
|
local bind_ifname
|
|
config_get bind_ifname "$1" "bind_interface"
|
|
|
|
[ -z "$bind_ifname" ] || interfaces=" $(uci -q show network|grep "device='$bind_ifname'"|cut -f2 -d'.') $interfaces"
|
|
}
|
|
|
|
log() {
|
|
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [DAEMON] $*" >> "$LOG_PATH"
|
|
}
|
|
|
|
start_service() {
|
|
local client_enabled server_enabled server_auto_firewall
|
|
config_get client_enabled "routing" "client_enabled" "0"
|
|
config_get_bool server_enabled "routing" "server_enabled" "0"
|
|
config_get_bool server_auto_firewall "routing" "server_auto_firewall" "0"
|
|
|
|
if [ "$client_enabled" = "0" -a "$server_enabled" = "0" ]; then
|
|
return 1
|
|
fi
|
|
|
|
mkdir -p "$RUN_DIR"
|
|
|
|
# Client
|
|
if [ "$client_enabled" = "1" ]; then
|
|
if [ -z "$1" -o "$1" = "mihomo-c" ]; then
|
|
# Generate/Validate client config
|
|
ucode -S "$SDL_DIR/generate_client.uc" 2>>"$LOG_PATH" | yq -Poy | yq \
|
|
'.sniffer["force-domain"][] style="double"
|
|
| .sniffer["skip-domain"][] style="double"
|
|
| with(.dns["nameserver-policy"] | keys; .. style="double")
|
|
| .dns["fallback-filter"].domain[] style="double"
|
|
| with(.["proxy-providers"][] | select(.payload); .payload style="literal")
|
|
| with(.["rule-providers"][] | select(.payload); .payload style="literal")' \
|
|
| sed -E 's,^(\s*payload:) \|-,\1,' \
|
|
> "$RUN_DIR/mihomo-c.yaml"
|
|
yq eval-all -i '. as $item ireduce ({}; . * $item )' "$RUN_DIR/mihomo-c.yaml" "$TEMPS_DIR/"*.yaml
|
|
|
|
if [ ! -e "$RUN_DIR/mihomo-c.yaml" ]; then
|
|
log "Error: failed to generate client configuration."
|
|
return 1
|
|
elif ! "$PROG" -t -d "$HM_DIR" -f "$RUN_DIR/mihomo-c.yaml" >/dev/null 2>>"$LOG_PATH"; then
|
|
log "Error: wrong client configuration detected."
|
|
return 1
|
|
fi
|
|
echo > "$RUN_DIR/mihomo-c.log"
|
|
|
|
# Deploy Clash API Dashboard
|
|
local dashboard_repo
|
|
config_get dashboard_repo "api" "dashboard_repo" ""
|
|
|
|
if [ -n "$dashboard_repo" -a ! -d "$RUN_DIR/ui" ]; then
|
|
tar -xzf "$HM_DIR/resources/$(echo "$dashboard_repo" | sed 's|\W|_|g').tgz" -C "$RUN_DIR/"
|
|
mv "$RUN_DIR/"*-gh-pages/ "$RUN_DIR/ui/"
|
|
fi
|
|
|
|
# Setup DNSMasq servers and IP-sets
|
|
local global_ipv6 dns_ipv6
|
|
config_get_bool global_ipv6 "global" "ipv6" "1"
|
|
config_get_bool dns_ipv6 "dns" "ipv6" "1"
|
|
local dns_port tcp_dns_port
|
|
config_get dns_port "dns" "port" "7853"
|
|
config_get tcp_dns_port "inbound" "tunnel_port" "7893"
|
|
local routing_mode routing_domain
|
|
config_get routing_mode "routing" "routing_mode" ""
|
|
config_get_bool routing_domain "routing" "routing_domain" "0"
|
|
|
|
mkdir -p "$DNSMASQ_DIR"
|
|
echo -e "conf-dir=$DNSMASQ_DIR" > "$DNSMASQ_DIR/../dnsmasq-fchomo.conf"
|
|
cat <<-EOF > "$DNSMASQ_DIR/forward-dns.conf"
|
|
no-poll
|
|
no-resolv
|
|
server=127.0.0.1#$dns_port
|
|
EOF
|
|
|
|
# <family> <set_name> <src> <dst> [yaml]
|
|
write_ipset_file() {
|
|
local family=$1
|
|
local set_name=$2
|
|
local src="$3"
|
|
local dst="$4"
|
|
local yaml="$5"
|
|
|
|
if [ -n "$yaml" ]; then
|
|
yq '.[] |= with(select(. == null); . = []) | .FQDN[]' "$src" | \
|
|
sed "s|^|nftset=/|;s|$|/${family}#inet#fchomo#${set_name}|" > "$dst"
|
|
else
|
|
sed "s|^|nftset=/|;s|$|/${family}#inet#fchomo#${set_name}|" "$src" > "$dst"
|
|
fi
|
|
}
|
|
|
|
# IP-sets
|
|
if [ -n "$(opmc list-installed dnsmasq-full)" ]; then
|
|
write_ipset_file 4 inet4_wan_direct_addr "$HM_DIR/resources/direct_list.yaml" "$DNSMASQ_DIR/direct_list.conf" yaml
|
|
[ "$global_ipv6" != "1" ] || \
|
|
write_ipset_file 6 inet6_wan_direct_addr "$HM_DIR/resources/direct_list.yaml" "$DNSMASQ_DIR/direct_list6.conf" yaml
|
|
|
|
write_ipset_file 4 inet4_wan_proxy_addr "$HM_DIR/resources/proxy_list.yaml" "$DNSMASQ_DIR/proxy_list.conf" yaml
|
|
[ "$global_ipv6" != "1" ] || \
|
|
write_ipset_file 6 inet6_wan_proxy_addr "$HM_DIR/resources/proxy_list.yaml" "$DNSMASQ_DIR/proxy_list6.conf" yaml
|
|
|
|
if [ "$routing_domain" = "1" ]; then
|
|
case "$routing_mode" in
|
|
bypass_cn)
|
|
write_ipset_file 4 inet4_china_list_addr "$HM_DIR/resources/china_list.txt" "$DNSMASQ_DIR/china_list.conf"
|
|
[ "$global_ipv6" != "1" ] || \
|
|
write_ipset_file 6 inet6_china_list_addr "$HM_DIR/resources/china_list.txt" "$DNSMASQ_DIR/china_list6.conf"
|
|
;;
|
|
routing_gfw)
|
|
write_ipset_file 4 inet4_gfw_list_addr "$HM_DIR/resources/gfw_list.txt" "$DNSMASQ_DIR/gfw_list.conf"
|
|
[ "$global_ipv6" != "1" ] || \
|
|
write_ipset_file 6 inet6_gfw_list_addr "$HM_DIR/resources/gfw_list.txt" "$DNSMASQ_DIR/gfw_list6.conf"
|
|
;;
|
|
esac
|
|
fi
|
|
fi
|
|
|
|
/etc/init.d/dnsmasq reload >/dev/null 2>&1
|
|
|
|
# Setup routing table
|
|
local proxy_mode table_id rule_pref
|
|
config_get proxy_mode "inbound" "proxy_mode" "redir_tproxy"
|
|
config_get table_id "config" "route_table_id" "2022"
|
|
config_get rule_pref "config" "route_rule_pref" "9000"
|
|
case "$proxy_mode" in
|
|
"redir_tproxy")
|
|
local tproxy_mark
|
|
config_get tproxy_mark "config" "tproxy_mark" "201"
|
|
|
|
ip rule add fwmark "$tproxy_mark" pref "$rule_pref" table "$table_id"
|
|
ip route add local default dev lo table "$table_id"
|
|
|
|
if [ "$global_ipv6" = "1" ]; then
|
|
ip -6 rule add fwmark "$tproxy_mark" pref "$rule_pref" table "$table_id"
|
|
ip -6 route add local default dev lo table "$table_id"
|
|
fi
|
|
;;
|
|
"redir_tun"|"tun")
|
|
local tun_name tun_mark
|
|
config_get tun_name "config" "tun_name" "hmtun0"
|
|
config_get tun_mark "config" "tun_mark" "202"
|
|
|
|
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_id"
|
|
ip rule add fwmark "$tun_mark" pref "$rule_pref" table "$table_id"
|
|
|
|
if [ "$global_ipv6" = "1" ]; then
|
|
ip -6 route replace default dev "$tun_name" table "$table_id"
|
|
ip -6 rule add fwmark "$tun_mark" pref "$rule_pref" table "$table_id"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# mihomo (client)
|
|
procd_open_instance "mihomo-c"
|
|
|
|
procd_set_param command /bin/sh
|
|
procd_append_param command -c "'$PROG' -d '$HM_DIR' -f '$RUN_DIR/mihomo-c.yaml' >> '$RUN_DIR/mihomo-c.log' 2>&1"
|
|
|
|
# Only supports `Global`` and does not support `Proxy Group` and `Proxy Node`
|
|
local bind_ifname
|
|
config_get bind_ifname "routing" "bind_interface"
|
|
|
|
procd_set_param netdev "br-lan"
|
|
if [ -n "$bind_ifname" ]; then
|
|
procd_append_param netdev "$bind_ifname"
|
|
else
|
|
local ifname
|
|
network_get_device ifname "$DEF_WAN" && procd_append_param netdev "$ifname"
|
|
network_get_device ifname "$DEF_WAN6" && procd_append_param netdev "$ifname"
|
|
fi
|
|
|
|
#procd_set_param capabilities "/etc/capabilities/fchomo.json"
|
|
#procd_set_param user mihomo
|
|
#procd_set_param group mihomo
|
|
|
|
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
|
|
fi
|
|
|
|
# Server
|
|
if [ "$server_enabled" = "1" ]; then
|
|
if [ -z "$1" -o "$1" = "mihomo-s" ]; then
|
|
# Generate/Validate server config
|
|
ucode -S "$SDL_DIR/generate_server.uc" 2>>"$LOG_PATH" | yq -Poy > "$RUN_DIR/mihomo-s.yaml"
|
|
|
|
if [ ! -e "$RUN_DIR/mihomo-s.yaml" ]; then
|
|
log "Error: failed to generate server configuration."
|
|
return 1
|
|
elif ! "$PROG" -t -d "$HM_DIR" -f "$RUN_DIR/mihomo-s.yaml" >/dev/null 2>>"$LOG_PATH"; then
|
|
log "Error: wrong server configuration detected."
|
|
return 1
|
|
fi
|
|
echo > "$RUN_DIR/mihomo-s.log"
|
|
|
|
# mihomo (server)
|
|
procd_open_instance "mihomo-s"
|
|
|
|
procd_set_param command /bin/sh
|
|
procd_append_param command -c "'$PROG' -d '$HM_DIR' -f '$RUN_DIR/mihomo-s.yaml' >> '$RUN_DIR/mihomo-s.log' 2>&1"
|
|
|
|
#procd_set_param capabilities "/etc/capabilities/fchomo.json"
|
|
#procd_set_param user mihomo
|
|
#procd_set_param group mihomo
|
|
|
|
procd_set_param limits core="unlimited"
|
|
procd_set_param limits nofile="1000000 1000000"
|
|
procd_set_param stderr 1
|
|
procd_set_param respawn
|
|
|
|
if [ "$server_auto_firewall" = "1" ]; then
|
|
add_firewall() {
|
|
local enabled listen port
|
|
config_get_bool enabled "$1" "enabled" "1"
|
|
config_get listen "$1" "listen" "::"
|
|
config_get port "$1" "port"
|
|
|
|
[ "$enabled" = "0" ] && return 0
|
|
|
|
json_add_object ''
|
|
json_add_string type rule
|
|
json_add_string target ACCEPT
|
|
json_add_string name "$1"
|
|
#json_add_string family '' # '' = IPv4 and IPv6
|
|
json_add_string proto 'tcp udp'
|
|
json_add_string src "*"
|
|
#json_add_string dest '' # '' = input
|
|
json_add_string dest_ip "$(echo "$listen" | grep -vE '^(0\.\d+\.\d+\.\d+|::)$')"
|
|
json_add_string dest_port "$port"
|
|
json_close_object
|
|
}
|
|
|
|
procd_open_data
|
|
# configure firewall
|
|
json_add_array firewall
|
|
# meta l4proto %s th dport %s counter accept comment "!%s: accept server instance [%s]"
|
|
config_foreach add_firewall "server"
|
|
json_close_array
|
|
procd_close_data
|
|
fi
|
|
|
|
procd_close_instance
|
|
fi
|
|
fi
|
|
|
|
# log-cleaner
|
|
procd_open_instance "log-cleaner"
|
|
procd_set_param command "$EXE_DIR/clean_log.sh"
|
|
procd_set_param respawn
|
|
procd_close_instance
|
|
|
|
# Setup firewall
|
|
utpl -S "$SDL_DIR/firewall_pre.ut" > "$RUN_DIR/fchomo_pre.nft"
|
|
# Setup Nftables rules
|
|
if [ "$client_enabled" = "1" ]; then
|
|
[ -z "$1" -o "$1" = "mihomo-c" ] && utpl -S "$SDL_DIR/firewall_post.ut" > "$RUN_DIR/fchomo_post.nft"
|
|
fi
|
|
|
|
log "$(mihomo -v | awk 'NR==1{print $1,$3}') started."
|
|
}
|
|
|
|
service_started() { procd_set_config_changed firewall; }
|
|
|
|
stop_service() {
|
|
# Client
|
|
[ -z "$1" -o "$1" = "mihomo-c" ] && stop_client
|
|
# Server
|
|
[ -z "$1" -o "$1" = "mihomo-s" ] && stop_server
|
|
# Setup firewall
|
|
echo 2>"/dev/null" > "$RUN_DIR/fchomo_pre.nft"
|
|
return 0
|
|
}
|
|
|
|
stop_client() {
|
|
# Load config
|
|
local table_id tproxy_mark tun_mark tun_name
|
|
config_get table_id "cofnig" "route_table_id" "2022"
|
|
config_get rule_pref "config" "route_rule_pref" "9000"
|
|
config_get tproxy_mark "cofnig" "tproxy_mark" "201"
|
|
config_get tun_mark "cofnig" "tun_mark" "202"
|
|
config_get tun_name "cofnig" "tun_name" "hmtun0"
|
|
|
|
# Remove routing table
|
|
# Tproxy
|
|
ip rule del pref "$rule_pref" table "$table_id" 2>"/dev/null"
|
|
ip route del local default dev lo table "$table_id" 2>"/dev/null"
|
|
|
|
ip -6 rule del pref "$rule_pref" table "$table_id" 2>"/dev/null"
|
|
ip -6 route del local default dev lo table "$table_id" 2>"/dev/null"
|
|
|
|
# TUN
|
|
ip route del default dev "$tun_name" table "$table_id" 2>"/dev/null"
|
|
ip rule del pref "$rule_pref" table "$table_id" 2>"/dev/null"
|
|
|
|
ip -6 route del default dev "$tun_name" table "$table_id" 2>"/dev/null"
|
|
ip -6 rule del pref "$rule_pref" table "$table_id" 2>"/dev/null"
|
|
|
|
# Remove Nftables rules
|
|
nft flush table inet fchomo 2>"/dev/null"
|
|
nft delete table inet fchomo 2>"/dev/null"
|
|
echo 2>"/dev/null" > "$RUN_DIR/fchomo_post.nft"
|
|
|
|
# Remove DNSMasq servers
|
|
rm -rf "$DNSMASQ_DIR/../dnsmasq-fchomo.conf" "$DNSMASQ_DIR"
|
|
/etc/init.d/dnsmasq reload >/dev/null 2>&1
|
|
|
|
# Remove Clash API Dashboard
|
|
rm -rf "$RUN_DIR/ui"
|
|
|
|
# Remove client config
|
|
rm -f "$RUN_DIR/mihomo-c.yaml" "$RUN_DIR/mihomo-c.log"
|
|
|
|
log "Service mihomo-c stopped."
|
|
}
|
|
|
|
stop_server() {
|
|
# Remove server config
|
|
rm -f "$RUN_DIR/mihomo-s.yaml" "$RUN_DIR/mihomo-s.log"
|
|
|
|
log "Service mihomo-s stopped."
|
|
}
|
|
|
|
service_stopped() {
|
|
sleep 1s # Wait for procd_kill complete
|
|
# Client
|
|
[ -n "$(/etc/init.d/$CONF info | jsonfilter -q -e '@.'"$CONF"'.instances["mihomo-c"]')" ] || client_stopped
|
|
# Server
|
|
|
|
procd_set_config_changed firewall
|
|
}
|
|
|
|
client_stopped() {
|
|
# Load config
|
|
local tun_name
|
|
config_get tun_name "config" "tun_name" "hmtun0"
|
|
|
|
# TUN
|
|
ip link set "$tun_name" down 2>"/dev/null"
|
|
ip tuntap del mode tun name "$tun_name" 2>"/dev/null"
|
|
|
|
ip -6 rule del oif "$tun_name" 2>"/dev/null"
|
|
}
|
|
|
|
server_stopped() {
|
|
return 0
|
|
}
|
|
|
|
reload_service() {
|
|
log "Reloading service${1:+ $1}..."
|
|
|
|
stop "$@"
|
|
start "$@"
|
|
}
|
|
|
|
service_triggers() {
|
|
procd_add_reload_trigger "$CONF" 'network'
|
|
|
|
local interfaces
|
|
|
|
# Only supports `Global`` and does not support `Proxy Group` and `Proxy Node`
|
|
load_interfaces 'routing'
|
|
[ -n "$interfaces" ] && {
|
|
for n in $interfaces; do
|
|
procd_add_reload_interface_trigger $n
|
|
done
|
|
} || {
|
|
for n in $DEF_WAN $DEF_WAN6; do
|
|
procd_add_reload_interface_trigger $n
|
|
done
|
|
}
|
|
|
|
interfaces=$(uci show network|grep "device='br-lan'"|cut -f2 -d'.')
|
|
[ -n "$interfaces" ] && {
|
|
for n in $interfaces; do
|
|
procd_add_reload_interface_trigger $n
|
|
done
|
|
}
|
|
}
|