#!/bin/sh
# Copyright (C) 2022-2025 xiaorouji
# Copyright (C) 2026 Openwrt-Passwall Organization

. /lib/functions.sh
. /lib/functions/service.sh
. /usr/share/libubox/jshn.sh

. /usr/share/passwall2/utils.sh
GLOBAL_ACL_PATH=${TMP_ACL_PATH}/default
LUA_UTIL_PATH=/usr/lib/lua/luci/passwall2
UTIL_SINGBOX=$LUA_UTIL_PATH/util_sing-box.lua
UTIL_SS=$LUA_UTIL_PATH/util_shadowsocks.lua
UTIL_XRAY=$LUA_UTIL_PATH/util_xray.lua
UTIL_NAIVE=$LUA_UTIL_PATH/util_naiveproxy.lua
UTIL_HYSTERIA2=$LUA_UTIL_PATH/util_hysteria2.lua
UTIL_TUIC=$LUA_UTIL_PATH/util_tuic.lua
SINGBOX_BIN=$(first_type $(config_t_get global_app sing_box_file) sing-box)
XRAY_BIN=$(first_type $(config_t_get global_app xray_file) xray)

check_run_environment() {
	local prefer_nft=$(config_t_get global_forwarding prefer_nft 1)
	local dnsmasq_info=$(dnsmasq -v 2>/dev/null)
	local dnsmasq_ver=$(echo "$dnsmasq_info" | sed -n '1s/.*version \([0-9.]*\).*/\1/p')
	# local dnsmasq_opts=$(echo "$dnsmasq_info" | grep -i "Compile time options")
	local dnsmasq_ipset=0; echo "$dnsmasq_info" | grep -qw "ipset" && dnsmasq_ipset=1
	local dnsmasq_nftset=0; echo "$dnsmasq_info" | grep -qw "nftset" && dnsmasq_nftset=1
	local has_ipt=0; { command -v iptables-legacy || command -v iptables; } >/dev/null && has_ipt=1
	local has_ipset=$(command -v ipset >/dev/null && echo 1 || echo 0)
	local has_fw4=$(command -v fw4 >/dev/null && echo 1 || echo 0)
	if [ "$prefer_nft" = "1" ]; then
		if [ "$dnsmasq_nftset" -eq 1 ] && [ "$has_fw4" -eq 1 ]; then
			USE_TABLES="nftables"
		elif [ "$has_ipset" -eq 1 ] && [ "$has_ipt" -eq 1 ] && [ "$dnsmasq_ipset" -eq 1 ]; then
			log_i18n 0 "Warning: The %s application environment is incomplete. Switch to %s. (%s)" "nftables (fw4)" "iptables" "has_fw4:$has_fw4/dnsmasq_nftset:$dnsmasq_nftset"
			USE_TABLES="iptables"
		fi
	else
		if [ "$has_ipset" -eq 1 ] && [ "$has_ipt" -eq 1 ] && [ "$dnsmasq_ipset" -eq 1 ]; then
			USE_TABLES="iptables"
		elif [ "$dnsmasq_nftset" -eq 1 ] && [ "$has_fw4" -eq 1 ]; then
			log_i18n 0 "Warning: The %s application environment is incomplete. Switch to %s. (%s)" "iptables (fw3)" "nftables" "has_ipt:$has_ipt/has_ipset:$has_ipset/dnsmasq_ipset:$dnsmasq_ipset"
			USE_TABLES="nftables"
		fi
	fi

	if [ -n "$USE_TABLES" ]; then
		local dep_list
		local file_path="/usr/lib/opkg/info"
		local file_ext=".control"
		[ -d "/lib/apk/packages" ] && { file_path="/lib/apk/packages"; file_ext=".list"; }

		if [ "$USE_TABLES" = "iptables" ]; then
			dep_list="iptables-mod-tproxy iptables-mod-socket iptables-mod-iprange iptables-mod-conntrack-extra kmod-ipt-nat"
		else
			dep_list="kmod-nft-socket kmod-nft-tproxy kmod-nft-nat"
			nftflag=1
			local v_num=$(echo "$dnsmasq_ver" | tr -cd '0-9')
			if [ "${v_num:-0}" -lt 290 ]; then
				log_i18n 0 "Note: Dnsmasq (%s) is below 2.90. Upgrading is recommended to improve stability." "${dnsmasq_ver}"
			fi
		fi
		local pkg
		for pkg in $dep_list; do
			if [ ! -s "${file_path}/${pkg}${file_ext}" ]; then
				log_i18n 0 "Warning: %s transparent proxy is missing basic dependency %s!" "${USE_TABLES}" "${pkg}"
			fi
		done
	else
		log 0 "$(i18n "Warning: Not compatible with any transparent proxy system environment.") (has_fw4:$has_fw4/has_ipt:$has_ipt/has_ipset:$has_ipset/dnsmasq_nftset:$dnsmasq_nftset/dnsmasq_ipset:$dnsmasq_ipset)"
	fi
}

run_xray() {
	local flag node redir_port tcp_proxy_way socks_address socks_port socks_username socks_password http_address http_port http_username http_password
	local dns_listen_port direct_dns_query_strategy remote_dns_protocol remote_dns_udp_server remote_dns_tcp_server remote_dns_doh remote_dns_client_ip remote_dns_detour remote_fakedns remote_dns_query_strategy dns_cache write_ipset_direct
	local loglevel log_file config_file
	eval_set_val $@
	[ -n "$log_file" ] || local log_file="/dev/null"
	[ -z "$loglevel" ] && local loglevel=$(config_t_get global loglevel "warning")

	json_init
	json_add_string "loglevel" "${loglevel}"

	[ -n "$flag" ] && {
		pgrep -af "$TMP_BIN_PATH" | awk -v P1="${flag}" 'BEGIN{IGNORECASE=1}$0~P1{print $1}' | xargs kill -9 >/dev/null 2>&1
		json_add_string "flag" "${flag}"
	}
	[ -n "$socks_address" ] && [ -n "$socks_port" ] && {
		json_add_string "local_socks_address" "${socks_address}"
		json_add_string "local_socks_port" "${socks_port}"
		[ -n "$socks_username" ] && [ -n "$socks_password" ] && {
			json_add_string "local_socks_username" "${socks_username}"
			json_add_string "local_socks_password" "${socks_password}"
		}
	}
	[ -n "$http_address" ] && [ -n "$http_port" ] && {
		json_add_string "local_http_address" "${http_address}"
		json_add_string "local_http_port" "${http_port}"
		[ -n "$http_username" ] && [ -n "$http_password" ] && {
			json_add_string "local_http_username" "${http_username}"
			json_add_string "local_http_password" "${http_password}"
		}
	}
	[ -n "$dns_listen_port" ] && {
		json_add_string "dns_listen_port" "${dns_listen_port}"
		[ -n "$dns_cache" ] && json_add_string "dns_cache" "${dns_cache}"

		local _dns=$(get_first_dns AUTO_DNS 53 | sed 's/#/:/g')
		local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
		local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')

		DIRECT_DNS_UDP_SERVER=${_dns_address}
		DIRECT_DNS_UDP_PORT=${_dns_port}

		[ "${write_ipset_direct}" = "1" ] && {
			direct_dnsmasq_listen_port=$(get_new_port $(expr $dns_listen_port + 1) udp)
			local set_flag="${flag}"
			local direct_ipset_conf=${GLOBAL_ACL_PATH}/dns_${flag}_direct.conf
			[ -n "$(echo ${flag} | grep '^acl')" ] && {
				direct_ipset_conf=${TMP_ACL_PATH}/${sid}/dns_${flag}_direct.conf
				set_flag=$(echo ${flag} | awk -F '_' '{print $2}')
			}
			if [ "${nftflag}" = "1" ]; then
				local direct_nftset="4#inet#passwall2#passwall2_${set_flag}_white,6#inet#passwall2#passwall2_${set_flag}_white6"
			else
				local direct_ipset="passwall2_${set_flag}_white,passwall2_${set_flag}_white6"
			fi
			run_ipset_dns_server listen_port=${direct_dnsmasq_listen_port} server_dns=${AUTO_DNS} ipset="${direct_ipset}" nftset="${direct_nftset}" config_file=${direct_ipset_conf}
			DIRECT_DNS_UDP_PORT=${direct_dnsmasq_listen_port}
			DIRECT_DNS_UDP_SERVER="127.0.0.1"
			[ -n "${direct_ipset}" ] && json_add_string "direct_ipset" "${direct_ipset}"
			[ -n "${direct_nftset}" ] && json_add_string "direct_nftset" "${direct_nftset}"
		}
		json_add_string "direct_dns_udp_port" "${DIRECT_DNS_UDP_PORT}"
		json_add_string "direct_dns_udp_server" "${DIRECT_DNS_UDP_SERVER}"
		json_add_string "direct_dns_query_strategy" "${direct_dns_query_strategy}"
		[ "$remote_fakedns" = "1" ] && {
			json_add_string "remote_dns_fake" "1"
			json_add_string "remote_dns_fake_strategy" "${remote_dns_query_strategy}"
		}
		local _json1_arg="$(json_dump)"

		json_init
		case "$remote_dns_protocol" in
			udp)
				local _dns=$(get_first_dns remote_dns_udp_server 53 | sed 's/#/:/g')
				local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
				local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')
				json_add_string "remote_dns_udp_port" "${_dns_port}"
				json_add_string "remote_dns_udp_server" "${_dns_address}"
			;;
			tcp)
				local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g')
				local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
				local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')
				json_add_string "remote_dns_tcp_port" "${_dns_port}"
				json_add_string "remote_dns_tcp_server" "${_dns_address}"
			;;
			doh)
				local _doh_url=$(echo $remote_dns_doh | awk -F ',' '{print $1}')
				local _doh_host_port=$(lua_api "get_domain_from_url(\"${_doh_url}\")")
				#local _doh_host_port=$(echo $_doh_url | sed "s/https:\/\///g" | awk -F '/' '{print $1}')
				local _doh_host=$(echo $_doh_host_port | awk -F ':' '{print $1}')
				local is_ip=$(lua_api "is_ip(\"${_doh_host}\")")
				local _doh_port=$(echo $_doh_host_port | awk -F ':' '{print $2}')
				[ -z "${_doh_port}" ] && _doh_port=443
				local _doh_bootstrap=$(echo $remote_dns_doh | cut -d ',' -sf 2-)
				[ "${is_ip}" = "true" ] && _doh_bootstrap=${_doh_host}
				json_add_string "remote_dns_doh_port" "${_doh_port}"
				json_add_string "remote_dns_doh_url" "${_doh_url}"
				json_add_string "remote_dns_doh_host" "${_doh_host}"
				[ -n "$_doh_bootstrap" ] && json_add_string "remote_dns_doh_ip" "${_doh_bootstrap}"
			;;
		esac
		[ -n "$remote_dns_detour" ] && json_add_string "remote_dns_detour" "${remote_dns_detour}"
		[ -n "$remote_dns_query_strategy" ] && json_add_string "remote_dns_query_strategy" "${remote_dns_query_strategy}"
		[ -n "$remote_dns_client_ip" ] && json_add_string "remote_dns_client_ip" "${remote_dns_client_ip}"
		local _json2_arg="$(json_dump)"

		local independent_dns
		if [ -z "${independent_dns}" ]; then
			local _json2_keys key
			json_load "${_json2_arg}"
			json_get_keys _json2_keys
			for key in ${_json2_keys}; do
				json_get_var "_json2_$key" "$key"
			done
			json_load "${_json1_arg}"
			for key in ${_json2_keys}; do
				eval "local _v=\$_json2_$key"
				json_add_string "$key" "$_v"
			done
		else
			dns_remote_listen_port=$(get_new_port $(expr ${direct_dnsmasq_listen_port:-${dns_listen_port}} + 1) udp)
			V2RAY_DNS_REMOTE_CONFIG="${TMP_PATH}/${flag}_dns_remote.json"
			V2RAY_DNS_REMOTE_LOG="${TMP_PATH}/${flag}_dns_remote.log"
			V2RAY_DNS_REMOTE_LOG="/dev/null"
			json_load "${_json2_arg}"
			json_add_string "dns_out_tag" "remote"
			json_add_string "dns_listen_port" "${dns_remote_listen_port}"
			json_add_string "remote_dns_outbound_socks_address" "127.0.0.1"
			json_add_string "remote_dns_outbound_socks_port" "${socks_port}"
			_json2_arg="$(json_dump)"
			lua $UTIL_XRAY gen_dns_config "${_json2_arg}" > $V2RAY_DNS_REMOTE_CONFIG
			ln_run "$XRAY_BIN" "xray" $V2RAY_DNS_REMOTE_LOG run -c "$V2RAY_DNS_REMOTE_CONFIG"

			json_load "${_json1_arg}"
			json_add_string "remote_dns_udp_port" "${dns_remote_listen_port}"
			json_add_string "remote_dns_udp_server" "127.0.0.1"
			json_add_string "remote_dns_query_strategy" "${remote_dns_query_strategy}"
		fi
	}

	[ -n "${redir_port}" ] && {
		json_add_string "redir_port" "${redir_port}"
		set_cache_var "node_${node}_redir_port" "${redir_port}"
		[ -n "${tcp_proxy_way}" ] && json_add_string "tcp_proxy_way" "${tcp_proxy_way}"
	}

	json_add_string "node" "${node}"

	local _json_arg="$(json_dump)"
	lua $UTIL_XRAY gen_config "${_json_arg}" > $config_file

	test_log_file=$log_file
	[ "$test_log_file" = "/dev/null" ] && test_log_file="${TMP_PATH}/test.log"

	$XRAY_BIN run -test -c "$config_file" > $test_log_file; local status=$?
	if [ "${status}" == 0 ]; then
		ln_run "$XRAY_BIN" xray $log_file run -c "$config_file"
	else
		_error_log_file=$test_log_file
		return ${status}
	fi
}

run_singbox() {
	local flag node redir_port tcp_proxy_way socks_address socks_port socks_username socks_password http_address http_port http_username http_password
	local dns_listen_port direct_dns_query_strategy remote_dns_protocol remote_dns_udp_server remote_dns_tcp_server remote_dns_doh remote_dns_client_ip remote_dns_detour remote_fakedns remote_dns_query_strategy dns_cache write_ipset_direct
	local loglevel log_file config_file
	eval_set_val $@
	local type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z')
	[ -z "$type" ] && return 1
	[ -n "$log_file" ] || local log_file="/dev/null"
	[ -z "$loglevel" ] && local loglevel=$(config_t_get global loglevel "warn")
	[ "$loglevel" = "warning" ] && loglevel="warn"
	local singbox_tag=$($SINGBOX_BIN version | grep 'Tags:' | awk '{print $2}')

	json_init
	json_add_string "tags" "${singbox_tag}"

	if [ "$log_file" = "/dev/null" ]; then
		json_add_string "log" "0"
	else
		json_add_string "log" "1"
		json_add_string "logfile" "${log_file}"
	fi
	json_add_string "loglevel" "${loglevel}"

	[ -n "$flag" ] && {
		pgrep -af "$TMP_BIN_PATH" | awk -v P1="${flag}" 'BEGIN{IGNORECASE=1}$0~P1{print $1}' | xargs kill -9 >/dev/null 2>&1
		json_add_string "flag" "${flag}"
	}
	[ -n "$socks_address" ] && [ -n "$socks_port" ] && {
		json_add_string "local_socks_address" "${socks_address}"
		json_add_string "local_socks_port" "${socks_port}"
		[ -n "$socks_username" ] && [ -n "$socks_password" ] && {
			json_add_string "local_socks_username" "${socks_username}"
			json_add_string "local_socks_password" "${socks_password}"
		}
	}
	[ -n "$http_address" ] && [ -n "$http_port" ] && {
		json_add_string "local_http_address" "${http_address}"
		json_add_string "local_http_port" "${http_port}"
		[ -n "$http_username" ] && [ -n "$http_password" ] && {
			json_add_string "local_http_username" "${http_username}"
			json_add_string "local_http_password" "${http_password}"
		}
	}
	[ -n "$dns_listen_port" ] && {
		local _dns=$(get_first_dns AUTO_DNS 53 | sed 's/#/:/g')
		local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
		local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')

		DIRECT_DNS_UDP_SERVER=${_dns_address}
		DIRECT_DNS_UDP_PORT=${_dns_port}

		[ "${write_ipset_direct}" = "1" ] && {
			direct_dnsmasq_listen_port=$(get_new_port $(expr $dns_listen_port + 1) udp)
			local set_flag="${flag}"
			local direct_ipset_conf=${GLOBAL_ACL_PATH}/dns_${flag}_direct.conf
			[ -n "$(echo ${flag} | grep '^acl')" ] && {
				direct_ipset_conf=${TMP_ACL_PATH}/${sid}/dns_${flag}_direct.conf
				set_flag=$(echo ${flag} | awk -F '_' '{print $2}')
			}
			if [ "${nftflag}" = "1" ]; then
				local direct_nftset="4#inet#passwall2#passwall2_${set_flag}_white,6#inet#passwall2#passwall2_${set_flag}_white6"
			else
				local direct_ipset="passwall2_${set_flag}_white,passwall2_${set_flag}_white6"
			fi
			run_ipset_dns_server listen_port=${direct_dnsmasq_listen_port} server_dns=${AUTO_DNS} ipset="${direct_ipset}" nftset="${direct_nftset}" config_file=${direct_ipset_conf}
			DIRECT_DNS_UDP_PORT=${direct_dnsmasq_listen_port}
			DIRECT_DNS_UDP_SERVER="127.0.0.1"
			[ -n "${direct_ipset}" ] && json_add_string "direct_ipset" "${direct_ipset}"
			[ -n "${direct_nftset}" ] && json_add_string "direct_nftset" "${direct_nftset}"
		}
		json_add_string "direct_dns_udp_port" "${DIRECT_DNS_UDP_PORT}"
		json_add_string "direct_dns_udp_server" "${DIRECT_DNS_UDP_SERVER}"
		json_add_string "direct_dns_query_strategy" "${direct_dns_query_strategy}"

		case "$remote_dns_protocol" in
			udp)
				local _dns=$(get_first_dns remote_dns_udp_server 53 | sed 's/#/:/g')
				local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
				local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')
				json_add_string "remote_dns_udp_port" "${_dns_port}"
				json_add_string "remote_dns_udp_server" "${_dns_address}"
			;;
			tcp)
				local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g')
				local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
				local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')
				json_add_string "remote_dns_tcp_port" "${_dns_port}"
				json_add_string "remote_dns_tcp_server" "${_dns_address}"
			;;
			doh)
				local _doh_url=$(echo $remote_dns_doh | awk -F ',' '{print $1}')
				local _doh_host_port=$(lua_api "get_domain_from_url(\"${_doh_url}\")")
				#local _doh_host_port=$(echo $_doh_url | sed "s/https:\/\///g" | awk -F '/' '{print $1}')
				local _doh_host=$(echo $_doh_host_port | awk -F ':' '{print $1}')
				local is_ip=$(lua_api "is_ip(\"${_doh_host}\")")
				local _doh_port=$(echo $_doh_host_port | awk -F ':' '{print $2}')
				[ -z "${_doh_port}" ] && _doh_port=443
				local _doh_bootstrap=$(echo $remote_dns_doh | cut -d ',' -sf 2-)
				[ "${is_ip}" = "true" ] && _doh_bootstrap=${_doh_host}
				[ -n "$_doh_bootstrap" ] && json_add_string "remote_dns_doh_ip" "${_doh_bootstrap}"
				json_add_string "remote_dns_doh_port" "${_doh_port}"
				json_add_string "remote_dns_doh_url" "${_doh_url}"
				json_add_string "remote_dns_doh_host" "${_doh_host}"
			;;
		esac

		[ -n "$remote_dns_detour" ] && json_add_string "remote_dns_detour" "${remote_dns_detour}"
		[ -n "$remote_dns_query_strategy" ] && json_add_string "remote_dns_query_strategy" "${remote_dns_query_strategy}"
		[ -n "$remote_dns_client_ip" ] && json_add_string "remote_dns_client_ip" "${remote_dns_client_ip}"

		[ -n "$dns_listen_port" ] && json_add_string "dns_listen_port" "${dns_listen_port}"
		[ -n "$dns_cache" ] && json_add_string "dns_cache" "${dns_cache}"
		[ "$remote_fakedns" = "1" ] && json_add_string "remote_dns_fake" "1"
	}

	[ -n "${redir_port}" ] && {
		json_add_string "redir_port" "${redir_port}"
		set_cache_var "node_${node}_redir_port" "${redir_port}"
		[ -n "${tcp_proxy_way}" ] && json_add_string "tcp_proxy_way" "${tcp_proxy_way}"
	}

	json_add_string "node" "${node}"

	local _json_arg="$(json_dump)"
	lua $UTIL_SINGBOX gen_config "${_json_arg}" > $config_file

	test_log_file=$log_file
	[ "$test_log_file" = "/dev/null" ] && test_log_file="${TMP_PATH}/test.log"

	$SINGBOX_BIN check -c "$config_file" > $test_log_file 2>&1; local status=$?
	if [ "${status}" == 0 ]; then
		ln_run "$SINGBOX_BIN" "sing-box" "${log_file}" run -c "$config_file"
	else
		_error_log_file=$test_log_file
		return ${status}
	fi
}

run_socks() {
	local flag node bind socks_port config_file http_port http_config_file relay_port log_file no_run
	eval_set_val $@
	[ -n "$config_file" ] && [ -z "$(echo ${config_file} | grep $TMP_PATH)" ] && config_file=$TMP_PATH/$config_file
	[ -n "$http_port" ] || http_port=0
	[ -n "$http_config_file" ] && [ -z "$(echo ${http_config_file} | grep $TMP_PATH)" ] && http_config_file=$TMP_PATH/$http_config_file
	if [ -n "$log_file" ] && [ -z "$(echo ${log_file} | grep $TMP_PATH)" ]; then
		log_file=$TMP_PATH/$log_file
	else
		log_file="/dev/null"
	fi
	local type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z')
	local remarks=$(config_n_get $node remarks)
	local server_host=$(config_n_get $node address)
	local server_port=$(config_n_get $node port)
	[ -n "$relay_port" ] && {
		server_host="127.0.0.1"
		server_port=$relay_port
	}
	local error_msg tmp

	if [ -n "$server_host" ] && [ -n "$server_port" ]; then
		check_host $server_host
		[ $? != 0 ] && {
			log 1 "$(i18n "Socks node: [%s]%s is an invalid server address and cannot be started!" "${$remarks}" "${server_host}")"
			return 1
		}
		tmp="${server_host}:${server_port}"
	else
		error_msg="$(i18n "For some reason, the configuration for this Socks service has been lost, and its startup has been aborted!")"
	fi

	if [ "$type" == "sing-box" ] || [ "$type" == "xray" ]; then
		local protocol=$(config_n_get $node protocol)
		if [ "$protocol" == "_balancing" ] || [ "$protocol" == "_shunt" ] || [ "$protocol" == "_iface" ] || [ "$protocol" == "_urltest" ]; then
			unset error_msg
		fi
	fi

	[ -n "${error_msg}" ] && {
		[ "$bind" != "127.0.0.1" ] && log 1 "$(i18n "Socks node: [%s]%s, start failed %s:%s %s" "${remarks}" "${tmp}" "${bind}" "${socks_port}" "${error_msg}")"
		return 1
	}
	[ "$bind" != "127.0.0.1" ] && log 1 "$(i18n "Socks node: [%s]%s, starting %s:%s" "${remarks}" "${tmp}" "${bind}" "${socks_port}")"

	json_init
	json_add_string "node" "${node}"
	json_add_string "server_host" "${server_host}"
	json_add_string "server_port" "${server_port}"
	case "$type" in
	sing-box)
		[ "$http_port" != "0" ] && {
			http_flag=1
			config_file="${config_file//SOCKS/HTTP_SOCKS}"
			json_add_string "local_http_address" "${bind}"
			json_add_string "local_http_port" "${http_port}"
		}
		[ -z "$relay_port" ] && {
			json_add_null "server_host"
			json_add_null "server_port"
		}
		[ "${log_file}" != "/dev/null" ] && {
			local loglevel=$(config_t_get global loglevel "warn")
			[ "$loglevel" = "warning" ] && loglevel="warn"
			json_add_string "log" "1"
			json_add_string "loglevel" "${loglevel}"
			json_add_string "logfile" "${log_file}"
		}
		[ -n "$no_run" ] && json_add_string "no_run" "1"
		json_add_string "flag" "SOCKS_${flag}"
		json_add_string "local_socks_address" "${bind}"
		json_add_string "local_socks_port" "${socks_port}"
		local _json_arg="$(json_dump)"
		lua $UTIL_SINGBOX gen_config "${_json_arg}" > $config_file
		[ -z "$no_run" ] && ln_run "$SINGBOX_BIN" "sing-box" /dev/null run -c "$config_file"
	;;
	xray)
		[ "$http_port" != "0" ] && {
			http_flag=1
			config_file="${config_file//SOCKS/HTTP_SOCKS}"
			json_add_string "local_http_address" "${bind}"
			json_add_string "local_http_port" "${http_port}"
		}
		[ -z "$relay_port" ] && {
			json_add_null "server_host"
			json_add_null "server_port"
		}
		[ -n "$no_run" ] && json_add_string "no_run" "1"
		json_add_string "flag" "SOCKS_${flag}"
		json_add_string "local_socks_address" "${bind}"
		json_add_string "local_socks_port" "${socks_port}"
		local _json_arg="$(json_dump)"
		lua $UTIL_XRAY gen_config "${_json_arg}" > $config_file
		[ -z "$no_run" ] && ln_run "$XRAY_BIN" "xray" $log_file run -c "$config_file"
	;;
	naiveproxy)
		json_add_string "local_addr" "${bind}"
		json_add_string "local_port" "${socks_port}"
		json_add_string "run_type" "socks"
		local _json_arg="$(json_dump)"
		lua $UTIL_NAIVE gen_config "${_json_arg}" > $config_file
		[ -z "$no_run" ] && ln_run "$(first_type naive)" naive $log_file "$config_file"
	;;
	ssr)
		json_add_string "local_addr" "${bind}"
		json_add_string "local_port" "${socks_port}"
		local _json_arg="$(json_dump)"
		lua $UTIL_SS gen_config "${_json_arg}" > $config_file
		[ -z "$no_run" ] && ln_run "$(first_type ssr-local)" "ssr-local" $log_file -c "$config_file" -v -u
	;;
	ss)
		json_add_string "local_addr" "${bind}"
		json_add_string "local_port" "${socks_port}"
		json_add_string "mode" "tcp_and_udp"
		[ -z "$no_run" ] && {
			local plugin_sh="${config_file%.json}_plugin.sh"
			json_add_string "plugin_sh" "${plugin_sh}"
		}
		local _json_arg="$(json_dump)"
		lua $UTIL_SS gen_config "${_json_arg}" > $config_file
		[ -z "$no_run" ] && ln_run "$(first_type ss-local)" "ss-local" $log_file -c "$config_file" -v
	;;
	ss-rust)
		json_add_string "local_socks_address" "${bind}"
		json_add_string "local_socks_port" "${socks_port}"
		[ "$http_port" != "0" ] && {
			http_flag=1
			config_file="${config_file//SOCKS/HTTP_SOCKS}"
			json_add_string "local_http_address" "${bind}"
			json_add_string "local_http_port" "${http_port}"
		}
		[ -z "$no_run" ] && {
			local plugin_sh="${config_file%.json}_plugin.sh"
			json_add_string "plugin_sh" "${plugin_sh}"
		}
		local _json_arg="$(json_dump)"
		lua $UTIL_SS gen_config "${_json_arg}" > $config_file
		[ -z "$no_run" ] && ln_run "$(first_type sslocal)" "sslocal" $log_file -c "$config_file" -v
	;;
	hysteria2)
		json_add_string "local_socks_address" "${bind}"
		json_add_string "local_socks_port" "${socks_port}"
		[ "$http_port" != "0" ] && {
			http_flag=1
			config_file="${config_file//SOCKS/HTTP_SOCKS}"
			json_add_string "local_http_address" "${bind}"
			json_add_string "local_http_port" "${http_port}"
		}
		local _json_arg="$(json_dump)"
		lua $UTIL_HYSTERIA2 gen_config "${_json_arg}" > $config_file
		[ -z "$no_run" ] && ln_run "$(first_type $(config_t_get global_app hysteria_file))" "hysteria" $log_file -c "$config_file" client
	;;
	tuic)
		json_add_string "local_addr" "${bind}"
		json_add_string "local_port" "${socks_port}"
		local _json_arg="$(json_dump)"
		lua $UTIL_TUIC gen_config "${_json_arg}" > $config_file
		[ -z "$no_run" ] && ln_run "$(first_type tuic-client)" "tuic-client" $log_file -c "$config_file"
	;;
	esac

	# http to socks
	[ -z "$http_flag" ] && [ "$http_port" != "0" ] && [ -n "$http_config_file" ] && [ "$type" != "sing-box" ] && [ "$type" != "xray" ] && [ "$type" != "socks" ] && {
		json_init
		json_add_string "local_http_port" "${http_port}"
		json_add_string "server_proto" "socks"
		json_add_string "server_address" "127.0.0.1"
		json_add_string "server_port" "${socks_port}"
		json_add_string "server_username" "${_username}"
		json_add_string "server_password" "${_password}"
		local _json_arg="$(json_dump)"
		if [ -n "${SINGBOX_BIN}" ]; then
			type="sing-box"
			local bin="${SINGBOX_BIN}"
			local util="${UTIL_SINGBOX}"
		elif [ -n "${XRAY_BIN}" ]; then
			type="xray"
			local bin="${XRAY_BIN}"
			local util="${UTIL_XRAY}"
		fi
		[ -n "${bin}" ] && [ -n "${util}" ] && {
			lua ${util} gen_proto_config "${_json_arg}" > ${http_config_file}
			[ -z "$no_run" ] && ln_run "${bin}" ${type} /dev/null run -c ${http_config_file}
		}
		unset bin util
	}
	unset http_flag

	[ -z "$no_run" ] && [ "${server_host}" != "127.0.0.1" ] && [ "$type" != "sing-box" ] && [ "$type" != "xray" ] && echo "${node}" >> $TMP_PATH/direct_node_list
}

socks_node_switch() {
	local flag new_node
	eval_set_val $@
	[ -n "$flag" ] && [ -n "$new_node" ] && {
		local prefix pf filename
		# Kill the SS plugin process
		for prefix in "" "HTTP_"; do
			pf="$TMP_PATH/${prefix}SOCKS_${flag}_plugin.pid"
			[ -s "$pf" ] && kill -9 "$(head -n1 "$pf")" >/dev/null 2>&1
		done

		pgrep -af "$TMP_BIN_PATH" | awk -v P1="${flag}" 'BEGIN{IGNORECASE=1}$0~P1 && !/acl\/|acl_/{print $1}' | xargs kill -9 >/dev/null 2>&1
		for prefix in "" "HTTP_" "HTTP2"; do
			rm -rf "$TMP_PATH/${prefix}SOCKS_${flag}"*
		done

		for filename in $(ls ${TMP_SCRIPT_FUNC_PATH}); do
			cmd=$(cat ${TMP_SCRIPT_FUNC_PATH}/${filename})
			[ -n "$(echo $cmd | grep "${flag}")" ] && rm -f ${TMP_SCRIPT_FUNC_PATH}/${filename}
		done
		local bind_local=$(config_n_get $flag bind_local 0)
		local bind="0.0.0.0"
		[ "$bind_local" = "1" ] && bind="127.0.0.1"
		local port=$(config_n_get $flag port)
		local config_file="SOCKS_${flag}.json"
		local log_file="SOCKS_${flag}.log"
		local log=$(config_n_get $flag log 1)
		[ "$log" == "0" ] && log_file=""
		local http_port=$(config_n_get $flag http_port 0)
		local http_config_file="HTTP2SOCKS_${flag}.json"
		LOG_FILE="/dev/null"
		run_socks flag=$flag node=$new_node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file
		set_cache_var "socks_${flag}" "$new_node"
		local USE_TABLES=$(get_cache_var "USE_TABLES")
		[ -n "$USE_TABLES" ] && source $APP_PATH/${USE_TABLES}.sh filter_direct_node_list
	}
}

run_global() {
	[ -z "$NODE" ] && return 1
	TYPE=$(echo $(config_n_get $NODE type) | tr 'A-Z' 'a-z')
	[ -z "$TYPE" ] && return 1
	mkdir -p ${GLOBAL_ACL_PATH}

	if [ $PROXY_IPV6 == "1" ]; then
		log_i18n 0 "To enable experimental IPv6 transparent proxy (TProxy), please ensure your node and type support IPv6!"
	fi

	TUN_DNS_PORT=15353
	TUN_DNS="127.0.0.1#${TUN_DNS_PORT}"

	V2RAY_ARGS="flag=global node=$NODE redir_port=$REDIR_PORT tcp_proxy_way=${TCP_PROXY_WAY}"
	V2RAY_ARGS="${V2RAY_ARGS} dns_listen_port=${TUN_DNS_PORT} direct_dns_query_strategy=${DIRECT_DNS_QUERY_STRATEGY} remote_dns_query_strategy=${REMOTE_DNS_QUERY_STRATEGY} dns_cache=${DNS_CACHE}"
	local dns_msg="DNS: ${TUN_DNS} （$(i18n "Direct DNS: %s" "${AUTO_DNS}")"

	[ -n "$REMOTE_DNS_PROTOCOL" ] && {
		V2RAY_ARGS="${V2RAY_ARGS} remote_dns_protocol=${REMOTE_DNS_PROTOCOL} remote_dns_detour=${REMOTE_DNS_DETOUR}"
		case "$REMOTE_DNS_PROTOCOL" in
			udp*)
				V2RAY_ARGS="${V2RAY_ARGS} remote_dns_udp_server=${REMOTE_DNS}"
				dns_msg="${dns_msg} $(i18n "Remote DNS: %s" "${REMOTE_DNS}")"
			;;
			tcp)
				V2RAY_ARGS="${V2RAY_ARGS} remote_dns_tcp_server=${REMOTE_DNS}"
				dns_msg="${dns_msg} $(i18n "Remote DNS: %s" "${REMOTE_DNS}")"
			;;
			doh)
				REMOTE_DNS_DOH=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
				V2RAY_ARGS="${V2RAY_ARGS} remote_dns_doh=${REMOTE_DNS_DOH}"
				dns_msg="${dns_msg} $(i18n "Remote DNS: %s" "${REMOTE_DNS_DOH}")"
			;;
		esac
		[ "$REMOTE_FAKEDNS" = "1" ] && {
			V2RAY_ARGS="${V2RAY_ARGS} remote_fakedns=1"
			dns_msg="${dns_msg} + FakeDNS "
		}
		
		local _remote_dns_client_ip=$(config_t_get global remote_dns_client_ip)
		[ -n "${_remote_dns_client_ip}" ] && V2RAY_ARGS="${V2RAY_ARGS} remote_dns_client_ip=${_remote_dns_client_ip}"
	}
	dns_msg="${dns_msg}）"

	V2RAY_CONFIG=${GLOBAL_ACL_PATH}/global.json
	V2RAY_LOG=${GLOBAL_ACL_PATH}/global.log
	[ "$(config_t_get global log_node 1)" != "1" ] && V2RAY_LOG="/dev/null"
	V2RAY_ARGS="${V2RAY_ARGS} log_file=${V2RAY_LOG} config_file=${V2RAY_CONFIG}"

	node_socks_port=$(config_t_get global node_socks_port 1070)
	node_socks_bind_local=$(config_t_get global node_socks_bind_local 1)
	node_socks_bind="127.0.0.1"
	[ "${node_socks_bind_local}" != "1" ] && node_socks_bind="0.0.0.0"
	V2RAY_ARGS="${V2RAY_ARGS} socks_address=${node_socks_bind} socks_port=${node_socks_port}"
	set_cache_var "GLOBAL_SOCKS_server" "127.0.0.1:$node_socks_port"

	node_http_port=$(config_t_get global node_http_port 0)
	[ "$node_http_port" != "0" ] && V2RAY_ARGS="${V2RAY_ARGS} http_port=${node_http_port}"

	V2RAY_ARGS="${V2RAY_ARGS} write_ipset_direct=${WRITE_IPSET_DIRECT}"

	local run_func
	[ -n "${XRAY_BIN}" ] && run_func="run_xray"
	[ -n "${SINGBOX_BIN}" ] && run_func="run_singbox"
	if [ "${TYPE}" = "xray" ] && [ -n "${XRAY_BIN}" ]; then
		run_func="run_xray"
	elif [ "${TYPE}" = "sing-box" ] && [ -n "${SINGBOX_BIN}" ]; then
		run_func="run_singbox"
	fi
	
	${run_func} ${V2RAY_ARGS}; local status=$?

	if [ "$status" == 0 ]; then
		log 0 ${dns_msg}
	else
		log_i18n 0 "[%s] process %s error, skip this transparent proxy!" $(i18n "Global") "${V2RAY_CONFIG}"
		cat ${_error_log_file} >> ${LOG_FILE}
		unset _error_log_file
		ENABLED_DEFAULT_ACL=0
		return 1
	fi

	local RUN_NEW_DNSMASQ=1
	RUN_NEW_DNSMASQ=${DNS_REDIRECT}
	if [ "${RUN_NEW_DNSMASQ}" == "0" ]; then
		#The old logic will be removed in the future.
		#Run a copy dnsmasq instance, DNS hijack that don't need a proxy devices.
		[ "1" = "0" ] && {
			DIRECT_DNSMASQ_PORT=$(get_new_port 11400)
			DIRECT_DNSMASQ_CONF=${GLOBAL_ACL_PATH}/direct_dnsmasq.conf
			DIRECT_DNSMASQ_CONF_PATH=${GLOBAL_ACL_PATH}/direct_dnsmasq.d
			mkdir -p ${DIRECT_DNSMASQ_CONF_PATH}
			json_init
			json_add_string "LISTEN_PORT" "${DIRECT_DNSMASQ_PORT}"
			json_add_string "DNSMASQ_CONF" "${DIRECT_DNSMASQ_CONF}"
			json_add_string "TMP_DNSMASQ_PATH" "${DIRECT_DNSMASQ_CONF_PATH}"
			lua $APP_PATH/helper_dnsmasq.lua copy_instance "$(json_dump)"
			ln_run "$(first_type dnsmasq)" "dnsmasq_direct" "/dev/null" -C ${DIRECT_DNSMASQ_CONF} -x ${GLOBAL_ACL_PATH}/direct_dnsmasq.pid
			set_cache_var "DIRECT_DNSMASQ_PORT" "${DIRECT_DNSMASQ_PORT}"
		}
		
		#Rewrite the default DNS service configuration
		#Modify the default dnsmasq service
		lua $APP_PATH/helper_dnsmasq.lua stretch
		json_init
		json_add_string "FLAG" "default"
		json_add_string "TMP_DNSMASQ_PATH" "${GLOBAL_DNSMASQ_CONF_PATH}"
		json_add_string "DNSMASQ_CONF_FILE" "${GLOBAL_DNSMASQ_CONF}"
		json_add_string "DEFAULT_DNS" "${AUTO_DNS}"
		json_add_string "LOCAL_DNS" "${LOCAL_DNS:-${AUTO_DNS}}"
		json_add_string "TUN_DNS" "${TUN_DNS}"
		json_add_string "NFTFLAG" "${nftflag:-0}"
		json_add_string "NO_LOGIC_LOG" "${NO_LOGIC_LOG:-0}"
		lua $APP_PATH/helper_dnsmasq.lua add_rule "$(json_dump)"
		uci -q add_list dhcp.@dnsmasq[0].addnmount=${GLOBAL_DNSMASQ_CONF_PATH}
		uci -q commit dhcp

		json_init
		json_add_string "LOG" "1"
		lua $APP_PATH/helper_dnsmasq.lua logic_restart "$(json_dump)"
	else
		#Run a copy dnsmasq instance, DNS hijack for that need proxy devices.
		GLOBAL_DNSMASQ_PORT=$(get_new_port 11400)
		run_copy_dnsmasq flag="default" listen_port=$GLOBAL_DNSMASQ_PORT tun_dns="${TUN_DNS}"
		DNS_REDIRECT_PORT=${GLOBAL_DNSMASQ_PORT}
		#dhcp.leases to hosts
		$APP_PATH/lease2hosts.sh > /dev/null 2>&1 &
	fi

	set_cache_var "ACL_GLOBAL_node" "$NODE"
	set_cache_var "ACL_GLOBAL_redir_port" "$REDIR_PORT"
}

start_socks() {
	[ "$SOCKS_ENABLED" = "1" ] && {
		local ids=$(uci show $CONFIG | grep "=socks" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
		[ -n "$ids" ] && {
			log_i18n 0 "Analyzing the node configuration of the Socks service..."
			for id in $ids; do
				local enabled=$(config_n_get $id enabled 0)
				[ "$enabled" == "0" ] && continue
				local node=$(config_n_get $id node)
				[ -z "$node" ] && continue
				local bind_local=$(config_n_get $id bind_local 0)
				local bind="0.0.0.0"
				[ "$bind_local" = "1" ] && bind="127.0.0.1"
				local port=$(config_n_get $id port)
				local config_file="SOCKS_${id}.json"
				local log_file="SOCKS_${id}.log"
				local log=$(config_n_get $id log 1)
				[ "$log" == "0" ] && log_file=""
				local http_port=$(config_n_get $id http_port 0)
				local http_config_file="HTTP2SOCKS_${id}.json"
				run_socks flag=$id node=$node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file
				set_cache_var "socks_${id}" "$node"

				# Auto switch logic
				local enable_autoswitch=$(config_n_get $id enable_autoswitch 0)
				[ "$enable_autoswitch" = "1" ] && $APP_PATH/socks_auto_switch.sh ${id} > /dev/null 2>&1 &
			done
		}
	}
}

clean_crontab() {
	[ -f "/tmp/lock/${CONFIG}_cron.lock" ] && return
	touch /etc/crontabs/root
	#sed -i "/${CONFIG}/d" /etc/crontabs/root >/dev/null 2>&1
	sed -i "/$(echo "/etc/init.d/${CONFIG}" | sed 's#\/#\\\/#g')/d" /etc/crontabs/root >/dev/null 2>&1
	sed -i "/$(echo "lua ${APP_PATH}/rule_update.lua log" | sed 's#\/#\\\/#g')/d" /etc/crontabs/root >/dev/null 2>&1
	sed -i "/$(echo "lua ${APP_PATH}/subscribe.lua start" | sed 's#\/#\\\/#g')/d" /etc/crontabs/root >/dev/null 2>&1

	pgrep -af "${CONFIG}/" | awk '/tasks\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
	rm -rf /tmp/lock/${CONFIG}_tasks.lock
}

start_crontab() {
	if [ "$ENABLED_DEFAULT_ACL" == 1 ] || [ "$ENABLED_ACLS" == 1 ]; then
		start_daemon=$(config_t_get global_delay start_daemon 0)
		[ "$start_daemon" = "1" ] && $APP_PATH/monitor.sh > /dev/null 2>&1 &
	fi

	[ -f "/tmp/lock/${CONFIG}_cron.lock" ] && {
		rm -rf "/tmp/lock/${CONFIG}_cron.lock"
		log_i18n 0 "The task is currently running automatically as a scheduled task; no reconfiguration of the scheduled task is required."
		return
	}

	clean_crontab

	[ "$ENABLED" != 1 ] && {
		/etc/init.d/cron restart
		return
	}

	stop_week_mode=$(config_t_get global_delay stop_week_mode)
	stop_time_mode=$(config_t_get global_delay stop_time_mode)
	if [ -n "$stop_week_mode" ]; then
		local t="0 $stop_time_mode * * $stop_week_mode"
		[ "$stop_week_mode" = "7" ] && t="0 $stop_time_mode * * *"
		if [ "$stop_week_mode" = "8" ]; then
			update_loop=1
		else
			echo "$t /etc/init.d/$CONFIG stop > /dev/null 2>&1 &" >>/etc/crontabs/root
		fi
		log_i18n 0 "Scheduled tasks: Auto stop service."
	fi

	start_week_mode=$(config_t_get global_delay start_week_mode)
	start_time_mode=$(config_t_get global_delay start_time_mode)
	if [ -n "$start_week_mode" ]; then
		local t="0 $start_time_mode * * $start_week_mode"
		[ "$start_week_mode" = "7" ] && t="0 $start_time_mode * * *"
		if [ "$start_week_mode" = "8" ]; then
			update_loop=1
		else
			echo "$t /etc/init.d/$CONFIG start > /dev/null 2>&1 &" >>/etc/crontabs/root
		fi
		log_i18n 0 "Scheduled tasks: Auto start service."
	fi

	restart_week_mode=$(config_t_get global_delay restart_week_mode)
	restart_time_mode=$(config_t_get global_delay restart_time_mode)
	if [ -n "$restart_week_mode" ]; then
		local t="0 $restart_time_mode * * $restart_week_mode"
		[ "$restart_week_mode" = "7" ] && t="0 $restart_time_mode * * *"
		if [ "$restart_week_mode" = "8" ]; then
			update_loop=1
		else
			echo "$t /etc/init.d/$CONFIG restart > /dev/null 2>&1 &" >>/etc/crontabs/root
		fi
		log_i18n 0 "Scheduled tasks: Auto restart service."
	fi

	autoupdate=$(config_t_get global_rules auto_update)
	weekupdate=$(config_t_get global_rules week_update)
	dayupdate=$(config_t_get global_rules time_update)
	if [ "$autoupdate" = "1" ]; then
		local t="0 $dayupdate * * $weekupdate"
		[ "$weekupdate" = "7" ] && t="0 $dayupdate * * *"
		if [ "$weekupdate" = "8" ]; then
			update_loop=1
		else
			echo "$t lua $APP_PATH/rule_update.lua log all cron > /dev/null 2>&1 &" >>/etc/crontabs/root
		fi
		log_i18n 0 "Scheduled tasks: Auto update rules."
	fi

	TMP_SUB_PATH=$TMP_PATH/sub_crontabs
	mkdir -p $TMP_SUB_PATH
	for item in $(uci show ${CONFIG} | grep "=subscribe_list" | cut -d '.' -sf 2 | cut -d '=' -sf 1); do
		if [ "$(config_n_get $item auto_update 0)" = "1" ]; then
			cfgid=$(uci show ${CONFIG}.$item | head -n 1 | cut -d '.' -sf 2 | cut -d '=' -sf 1)
			remark=$(config_n_get $item remark)
			week_update=$(config_n_get $item week_update)
			time_update=$(config_n_get $item time_update)
			echo "$cfgid" >> $TMP_SUB_PATH/${week_update}_${time_update}
			log_i18n 0 "Scheduled tasks: Auto update [%s] subscription." "${remark}"
		fi
	done

	[ -d "${TMP_SUB_PATH}" ] && {
		for name in $(ls ${TMP_SUB_PATH}); do
			week_update=$(echo $name | awk -F '_' '{print $1}')
			time_update=$(echo $name | awk -F '_' '{print $2}')
			cfgids=$(echo -n $(cat ${TMP_SUB_PATH}/${name}) | sed 's# #,#g')
			local t="0 $time_update * * $week_update"
			[ "$week_update" = "7" ] && t="0 $time_update * * *"
			if [ "$week_update" = "8" ]; then
				update_loop=1
			else
				echo "$t lua $APP_PATH/subscribe.lua start $cfgids cron > /dev/null 2>&1 &" >>/etc/crontabs/root
			fi
		done
		rm -rf $TMP_SUB_PATH
	}

	if [ "$ENABLED_DEFAULT_ACL" == 1 ] || [ "$ENABLED_ACLS" == 1 ]; then
		[ "$update_loop" = "1" ] && {
			$APP_PATH/tasks.sh > /dev/null 2>&1 &
			log_i18n 0 "Auto updates: Starts a cyclical update process."
		}
	else
		log_i18n 0 "Running in no proxy mode, it only allows scheduled tasks for starting and stopping services."
	fi

	/etc/init.d/cron restart
}

stop_crontab() {
	[ -f "/tmp/lock/${CONFIG}_cron.lock" ] && return
	clean_crontab
	/etc/init.d/cron restart
	#log_i18n 0 "Clear scheduled commands."
}

start_haproxy() {
	[ "$(config_t_get global_haproxy balancing_enable 0)" != "1" ] && return
	haproxy_path=$TMP_PATH/haproxy
	haproxy_conf="config.cfg"
	lua $APP_PATH/haproxy.lua -path ${haproxy_path} -conf ${haproxy_conf} -dns ${LOCAL_DNS:-${AUTO_DNS}}
	ln_run "$(first_type haproxy)" haproxy "/dev/null" -f "${haproxy_path}/${haproxy_conf}"
}

run_copy_dnsmasq() {
	local flag listen_port tun_dns
	eval_set_val $@
	local dnsmasq_conf=$TMP_ACL_PATH/$flag/dnsmasq.conf
	local dnsmasq_conf_path=$TMP_ACL_PATH/$flag/dnsmasq.d
	mkdir -p $dnsmasq_conf_path

	json_init
	json_add_string "LISTEN_PORT" "${listen_port}"
	json_add_string "DNSMASQ_CONF" "${dnsmasq_conf}"
	lua $APP_PATH/helper_dnsmasq.lua copy_instance "$(json_dump)"

	json_init
	json_add_string "FLAG" "${flag}"
	json_add_string "TMP_DNSMASQ_PATH" "${dnsmasq_conf_path}"
	json_add_string "DNSMASQ_CONF_FILE" "${dnsmasq_conf}"
	json_add_string "DEFAULT_DNS" "${AUTO_DNS}"
	json_add_string "LOCAL_DNS" "${LOCAL_DNS:-${AUTO_DNS}}"
	json_add_string "TUN_DNS" "${tun_dns}"
	json_add_string "NFTFLAG" "${nftflag:-0}"
	json_add_string "NO_LOGIC_LOG" "${NO_LOGIC_LOG:-0}"
	lua $APP_PATH/helper_dnsmasq.lua add_rule "$(json_dump)"

	ln_run "$(first_type dnsmasq)" "dnsmasq_${flag}" "/dev/null" -C $dnsmasq_conf -x $TMP_ACL_PATH/$flag/dnsmasq.pid
	set_cache_var "ACL_${flag}_dns_port" "${listen_port}"
}

run_ipset_dns_server() {
	if [ -n "$(first_type chinadns-ng)" ]; then
		run_ipset_chinadns_ng $@
	else
		run_ipset_dnsmasq $@
	fi
}

run_ipset_chinadns_ng() {
	local listen_port server_dns ipset nftset config_file
	eval_set_val $@
	[ ! -s "$TMP_ACL_PATH/vpslist" ] && {
		node_servers=$(uci show "${CONFIG}" | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2)
		hosts_foreach "node_servers" host_from_url | grep '[a-zA-Z]$' | sort -u | grep -v "engage.cloudflareclient.com" > $TMP_ACL_PATH/vpslist
	}
	
	[ -n "${ipset}" ] && {
		set_names=$ipset
		vps_set_names="passwall2_vps,passwall2_vps6"
	}
	[ -n "${nftset}" ] && {
		set_names=$(echo ${nftset} | awk -F, '{printf "%s,%s", substr($1,3), substr($2,3)}' | sed 's/#/@/g')
		vps_set_names="inet@passwall2@passwall2_vps,inet@passwall2@passwall2_vps6"
	}
	cat <<-EOF > $config_file
		bind-addr 127.0.0.1
		bind-port ${listen_port}
		china-dns ${server_dns}
		trust-dns ${server_dns}
		filter-qtype 65
		add-tagchn-ip ${set_names}
		default-tag chn
		group vpslist
		group-dnl $TMP_ACL_PATH/vpslist
		group-upstream ${server_dns}
		group-ipset ${vps_set_names}
	EOF
	ln_run "$(first_type chinadns-ng)" "chinadns-ng" "/dev/null" -C $config_file -v
}

run_ipset_dnsmasq() {
	local listen_port server_dns ipset nftset cache_size dns_forward_max config_file
	eval_set_val $@
	cat <<-EOF > $config_file
		port=${listen_port}
		no-poll
		no-resolv
		strict-order
		cache-size=${cache_size:-0}
		dns-forward-max=${dns_forward_max:-1000}
	EOF
	for i in $(echo ${server_dns} | sed "s#,# #g"); do
		echo "server=${i}" >> $config_file
	done
	[ -n "${ipset}" ] && echo "ipset=${ipset}" >> $config_file
	[ -n "${nftset}" ] && echo "nftset=${nftset}" >> $config_file
	ln_run "$(first_type dnsmasq)" "dnsmasq" "/dev/null" -C $config_file
}

acl_app() {
	local items=$(uci show ${CONFIG} | grep "=acl_rule" | cut -d '.' -sf 2 | cut -d '=' -sf 1)
	[ -n "$items" ] && {
		local index=0
		local item
		local redir_port dns_port dnsmasq_port
		local ipt_tmp msg msg2
		redir_port=11200
		dns_port=11300
		dnsmasq_port=${GLOBAL_DNSMASQ_PORT:-11400}
		for item in $items; do
			index=$(expr $index + 1)
			local enabled sid remarks sources interface tcp_no_redir_ports udp_no_redir_ports node direct_dns_query_strategy write_ipset_direct remote_dns_protocol remote_dns remote_dns_doh remote_dns_client_ip remote_dns_detour remote_fakedns remote_dns_query_strategy
			local _ip _mac _iprange _ipset _ip_or_mac source_list config_file
			local sid=$(uci -q show "${CONFIG}.${item}" | grep "=acl_rule" | awk -F '=' '{print $1}' | awk -F '.' '{print $2}')
			[ "$(config_n_get $sid enabled)" = "1" ] || continue
			eval $(uci -q show "${CONFIG}.${item}" | cut -d'.' -sf 3-)

			if [ -n "${sources}" ]; then
				for s in $sources; do
					local s2
					is_iprange=$(lua_api "iprange(\"${s}\")")
					if [ "${is_iprange}" = "true" ]; then
						s2="iprange:${s}"
					elif [ -n "$(echo ${s} | grep '^ipset:')" ]; then
						s2="ipset:${s}"
					else
						_ip_or_mac=$(lua_api "ip_or_mac(\"${s}\")")
						if [ "${_ip_or_mac}" = "ip" ]; then
							s2="ip:${s}"
						elif [ "${_ip_or_mac}" = "mac" ]; then
							s2="mac:${s}"
						fi
					fi
					[ -n "${s2}" ] && source_list="${source_list}\n${s2}"
					unset s2
				done
			else
				source_list="any"
			fi

			local acl_path=${TMP_ACL_PATH}/$sid
			mkdir -p ${acl_path}
			[ -n "${source_list}" ] && echo -e "${source_list}" | sed '/^$/d' > ${acl_path}/source_list

			node=${node:-default}
			tcp_no_redir_ports=${tcp_no_redir_ports:-default}
			udp_no_redir_ports=${udp_no_redir_ports:-default}
			[ "$tcp_no_redir_ports" = "default" ] && tcp_no_redir_ports=$TCP_NO_REDIR_PORTS
			[ "$udp_no_redir_ports" = "default" ] && udp_no_redir_ports=$UDP_NO_REDIR_PORTS
			if has_1_65535 "$tcp_no_redir_ports" && has_1_65535 "$udp_no_redir_ports"; then
				unset node
			fi

			[ -n "$node" ] && {
				tcp_proxy_mode="global"
				udp_proxy_mode="global"
				direct_dns_query_strategy=${direct_dns_query_strategy:-UseIP}
				write_ipset_direct=${write_ipset_direct:-1}
				remote_dns_protocol=${remote_dns_protocol:-tcp}
				remote_dns=${remote_dns:-1.1.1.1}
				[ "$remote_dns_protocol" = "doh" ] && remote_dns=${remote_dns_doh:-https://1.1.1.1/dns-query}
				remote_dns_detour=${remote_dns_detour:-remote}
				remote_fakedns=${remote_fakedns:-0}
				remote_dns_query_strategy=${remote_dns_query_strategy:-UseIPv4}

				local GLOBAL_node=$(get_cache_var "ACL_GLOBAL_node")
				[ -n "${GLOBAL_node}" ] && GLOBAL_redir_port=$(get_cache_var "ACL_GLOBAL_redir_port")

				if [ "$node" = "default" ]; then
					if [ -n "${GLOBAL_node}" ]; then
						set_cache_var "ACL_${sid}_node" "${GLOBAL_node}"
						set_cache_var "ACL_${sid}_redir_port" "${GLOBAL_redir_port}"
						set_cache_var "ACL_${sid}_dns_port" "${GLOBAL_DNSMASQ_PORT}"
						set_cache_var "ACL_${sid}_default" "1"
					else
						log 1 "$(i18n "Global nodes are not enabled, skip [%s]." "${remarks}")"
					fi
				else
					[ "$(config_get_type $node)" = "nodes" ] && {
						if [ -n "${GLOBAL_node}" ] && [ "$node" = "${GLOBAL_node}" ]; then
							set_cache_var "ACL_${sid}_node" "${GLOBAL_node}"
							set_cache_var "ACL_${sid}_redir_port" "${GLOBAL_redir_port}"
							set_cache_var "ACL_${sid}_dns_port" "${GLOBAL_DNSMASQ_PORT}"
							set_cache_var "ACL_${sid}_default" "1"
						else
							redir_port=$(get_new_port $(expr $redir_port + 1))

							local type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z')
							if [ -n "${type}" ]; then
								config_file=$TMP_ACL_PATH/${node}_TCP_UDP_DNS_${redir_port}.json
								dns_port=$(get_new_port $(expr $dns_port + 1))
								local acl_socks_port=$(get_new_port $(expr $redir_port + $index))
								local run_func
								[ -n "${XRAY_BIN}" ] && run_func="run_xray"
								[ -n "${SINGBOX_BIN}" ] && run_func="run_singbox"
								if [ "${type}" = "xray" ] && [ -n "${XRAY_BIN}" ]; then
									run_func="run_xray"
								elif [ "${type}" = "sing-box" ] && [ -n "${SINGBOX_BIN}" ]; then
									run_func="run_singbox"
								fi
								${run_func} flag=acl_$sid node=$node redir_port=$redir_port tcp_proxy_way=${TCP_PROXY_WAY} \
											socks_address=127.0.0.1 socks_port=$acl_socks_port \
											dns_listen_port=${dns_port} \
											direct_dns_query_strategy=${direct_dns_query_strategy} \
											remote_dns_protocol=${remote_dns_protocol} remote_dns_tcp_server=${remote_dns} remote_dns_udp_server=${remote_dns} remote_dns_doh="${remote_dns}" \
											remote_dns_client_ip=${remote_dns_client_ip} remote_dns_detour=${remote_dns_detour} remote_fakedns=${remote_fakedns} remote_dns_query_strategy=${remote_dns_query_strategy} \
											write_ipset_direct=${write_ipset_direct} config_file=${config_file}
								local status=$?
								if [ "$status" != 0 ]; then
									log_i18n 2 "[%s] process %s error, skip this transparent proxy!" "${remarks}" "${config_file}"
									cat ${_error_log_file} >> ${LOG_FILE}
									unset _error_log_file
									continue
								fi
							fi
							dnsmasq_port=$(get_new_port $(expr $dnsmasq_port + 1))
							run_copy_dnsmasq flag="$sid" listen_port=$dnsmasq_port tun_dns="127.0.0.1#${dns_port}"
							#dhcp.leases to hostsMore actions
							$APP_PATH/lease2hosts.sh > /dev/null 2>&1 &

							set_cache_var "ACL_${sid}_node" "$node"
							set_cache_var "ACL_${sid}_redir_port" "$redir_port"
						fi
					}
				fi
			}
			unset enabled sid remarks sources interface tcp_no_redir_ports udp_no_redir_ports node direct_dns_query_strategy write_ipset_direct remote_dns_protocol remote_dns remote_dns_doh remote_dns_client_ip remote_dns_detour remote_fakedns remote_dns_query_strategy 
			unset _ip _mac _iprange _ipset _ip_or_mac source_list config_file
		done
		unset redir_port dns_port dnsmasq_port
	}
}

start() {
	pgrep -f /tmp/etc/passwall2/bin > /dev/null 2>&1 && {
		#log_i18n 0 "The program has started. Please stop it and then restart it!"
		stop
	}
	mkdir -p /tmp/etc /tmp/log $TMP_PATH $TMP_BIN_PATH $TMP_SCRIPT_FUNC_PATH $TMP_ROUTE_PATH $TMP_ACL_PATH $TMP_PATH2
	get_config
	export V2RAY_LOCATION_ASSET=$(config_t_get global_rules v2ray_location_asset "/usr/share/v2ray/")
	export XRAY_LOCATION_ASSET=$V2RAY_LOCATION_ASSET
	export ENABLE_DEPRECATED_GEOSITE=true
	export ENABLE_DEPRECATED_GEOIP=true
	ulimit -n 65535
	start_haproxy
	start_socks
	nftflag=0
	USE_TABLES=""
	check_run_environment
	if [ "$ENABLED_DEFAULT_ACL" == 1 ] || [ "$ENABLED_ACLS" == 1 ]; then
		[ "$(uci -q get dhcp.@dnsmasq[0].dns_redirect)" == "1" ] && {
			uci -q set ${CONFIG}.@global[0].dnsmasq_dns_redirect='1'
			uci -q commit ${CONFIG}
			uci -q set dhcp.@dnsmasq[0].dns_redirect='0'
			uci -q commit dhcp

			json_init
			json_add_string "LOG" "0"
			lua $APP_PATH/helper_dnsmasq.lua restart "$(json_dump)"
		}
	fi
	[ "$ENABLED_DEFAULT_ACL" == 1 ] && run_global
	[ -n "$USE_TABLES" ] && source $APP_PATH/${USE_TABLES}.sh start
	set_cache_var "USE_TABLES" "$USE_TABLES"
	if [ "$ENABLED_DEFAULT_ACL" == 1 ] || [ "$ENABLED_ACLS" == 1 ]; then
		bridge_nf_ipt=$(sysctl -e -n net.bridge.bridge-nf-call-iptables)
		set_cache_var "bak_bridge_nf_ipt" "$bridge_nf_ipt"
		sysctl -w net.bridge.bridge-nf-call-iptables=0 >/dev/null 2>&1
		[ "$PROXY_IPV6" == "1" ] && {
			bridge_nf_ip6t=$(sysctl -e -n net.bridge.bridge-nf-call-ip6tables)
			set_cache_var "bak_bridge_nf_ip6t" "$bridge_nf_ip6t"
			sysctl -w net.bridge.bridge-nf-call-ip6tables=0 >/dev/null 2>&1
		}
	fi
	start_crontab
	log_i18n 0 "Running complete!"
	echolog "\n"
}

stop() {
	clean_log
	eval_cache_var
	[ -n "$USE_TABLES" ] && source $APP_PATH/${USE_TABLES}.sh stop
	delete_ip2route
	# Kill the SS plugin process
	# kill_all xray-plugin v2ray-plugin obfs-local shadow-tls
	local pid_file pid
	find "$TMP_PATH" -type f -name '*_plugin.pid' 2>/dev/null | while read -r pid_file; do
		read -r pid < "$pid_file"
		if [ -n "$pid" ]; then
			kill -9 "$pid" >/dev/null 2>&1
		fi
	done
	pgrep -f "sleep.*(6s|9s|58s)" | xargs kill -9 >/dev/null 2>&1
	pgrep -af "${CONFIG}/" | awk '! /app\.sh|subscribe\.lua|rule_update\.lua|tasks\.sh|ujail/{print $1}' | xargs kill -9 >/dev/null 2>&1
	unset V2RAY_LOCATION_ASSET
	unset XRAY_LOCATION_ASSET
	stop_crontab
	rm -rf $GLOBAL_DNSMASQ_CONF
	rm -rf $GLOBAL_DNSMASQ_CONF_PATH
	[ "1" = "1" ] && {
		#restore logic
		bak_dnsmasq_dns_redirect=$(config_t_get global dnsmasq_dns_redirect)
		[ -n "${bak_dnsmasq_dns_redirect}" ] && {
			uci -q set dhcp.@dnsmasq[0].dns_redirect="${bak_dnsmasq_dns_redirect}"
			uci -q commit dhcp
			uci -q delete ${CONFIG}.@global[0].dnsmasq_dns_redirect
			uci -q commit ${CONFIG}
		}
		if [ -z "${ACL_default_dns_port}" ] || [ -n "${bak_dnsmasq_dns_redirect}" ]; then
			uci -q del_list dhcp.@dnsmasq[0].addnmount="${GLOBAL_DNSMASQ_CONF_PATH}"
			uci -q commit dhcp

			json_init
			json_add_string "LOG" "0"
			lua $APP_PATH/helper_dnsmasq.lua restart "$(json_dump)"
		fi
		[ -n "${bak_bridge_nf_ipt}" ] && sysctl -w net.bridge.bridge-nf-call-iptables=${bak_bridge_nf_ipt} >/dev/null 2>&1
		[ -n "${bak_bridge_nf_ip6t}" ] && sysctl -w net.bridge.bridge-nf-call-ip6tables=${bak_bridge_nf_ip6t} >/dev/null 2>&1
	}
	rm -rf $TMP_PATH
	rm -rf /tmp/lock/${CONFIG}_socks_auto_switch*
	rm -rf /tmp/lock/${CONFIG}_lease2hosts*
	log_i18n 0 "Clearing and closing related programs and cache complete."
	exit 0
}

get_config() {
	ENABLED_DEFAULT_ACL=0
	ENABLED=$(config_t_get global enabled 0)
	NODE=$(config_t_get global node)
	[ "$ENABLED" == 1 ] && {
		[ -n "$NODE" ] && [ "$(config_get_type $NODE)" == "nodes" ] && ENABLED_DEFAULT_ACL=1
	}
	ENABLED_ACLS=$(config_t_get global acl_enable 0)
	[ "$ENABLED_ACLS" == 1 ] && {
		[ "$(uci show ${CONFIG} | grep "@acl_rule" | grep "enabled='1'" | wc -l)" == 0 ] && ENABLED_ACLS=0
	}
	SOCKS_ENABLED=$(config_t_get global socks_enabled 0)
	REDIR_PORT=$(echo $(get_new_port 1041 tcp,udp))
	TCP_PROXY_WAY=$(config_t_get global_forwarding tcp_proxy_way redirect)
	TCP_NO_REDIR_PORTS=$(config_t_get global_forwarding tcp_no_redir_ports 'disable')
	UDP_NO_REDIR_PORTS=$(config_t_get global_forwarding udp_no_redir_ports 'disable')
	TCP_REDIR_PORTS=$(config_t_get global_forwarding tcp_redir_ports '22,25,53,143,465,587,853,993,995,80,443')
	UDP_REDIR_PORTS=$(config_t_get global_forwarding udp_redir_ports '1:65535')
	PROXY_IPV6=$(config_t_get global_forwarding ipv6_tproxy 0)
	TCP_PROXY_MODE="global"
	UDP_PROXY_MODE="global"
	LOCALHOST_PROXY=$(config_t_get global localhost_proxy '1')
	CLIENT_PROXY=$(config_t_get global client_proxy '1')
	DIRECT_DNS_QUERY_STRATEGY=$(config_t_get global direct_dns_query_strategy UseIP)
	REMOTE_DNS_PROTOCOL=$(config_t_get global remote_dns_protocol tcp)
	REMOTE_DNS_DETOUR=$(config_t_get global remote_dns_detour remote)
	REMOTE_DNS=$(config_t_get global remote_dns 1.1.1.1:53 | sed 's/#/:/g' | sed -E 's/\:([^:]+)$/#\1/g')
	REMOTE_FAKEDNS=$(config_t_get global remote_fakedns '0')
	REMOTE_DNS_QUERY_STRATEGY=$(config_t_get global remote_dns_query_strategy UseIPv4)
	WRITE_IPSET_DIRECT=$(config_t_get global write_ipset_direct 1)
	DNS_CACHE=$(config_t_get global dns_cache 1)
	DNS_REDIRECT=$(config_t_get global dns_redirect 1)

	RESOLVFILE=/tmp/resolv.conf.d/resolv.conf.auto
	[ -f "${RESOLVFILE}" ] && [ -s "${RESOLVFILE}" ] || RESOLVFILE=/tmp/resolv.conf.auto

	ISP_DNS=$(cat $RESOLVFILE 2>/dev/null | grep -E -o "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | grep -v -E '^(0\.0\.0\.0|127\.0\.0\.1)$' | awk '!seen[$0]++')
	ISP_DNS6=$(cat $RESOLVFILE 2>/dev/null | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | awk -F % '{print $1}' | awk -F " " '{print $2}' | grep -v -Fx ::1 | grep -v -Fx :: | awk '!seen[$0]++')

	DEFAULT_DNSMASQ_CFGID=$(uci show dhcp.@dnsmasq[0] |  awk -F '.' '{print $2}' | awk -F '=' '{print $1}'| head -1)
	DEFAULT_DNS=$(uci show dhcp.@dnsmasq[0] | grep "\.server=" | awk -F '=' '{print $2}' | sed "s/'//g" | tr ' ' '\n' | grep -v "\/" | head -2 | sed ':label;N;s/\n/,/;b label')
	[ -z "${DEFAULT_DNS}" ] && DEFAULT_DNS=$(echo -n $ISP_DNS | tr ' ' '\n' | head -2 | tr '\n' ',' | sed 's/,$//')
	AUTO_DNS=${DEFAULT_DNS:-119.29.29.29}

	DNSMASQ_CONF_DIR=/tmp/dnsmasq.d
	DEFAULT_DNSMASQ_CFGID="$(uci -q show "dhcp.@dnsmasq[0]" | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')"
	if [ -f "/tmp/etc/dnsmasq.conf.$DEFAULT_DNSMASQ_CFGID" ]; then
		DNSMASQ_CONF_DIR="$(awk -F '=' '/^conf-dir=/ {print $2}' "/tmp/etc/dnsmasq.conf.$DEFAULT_DNSMASQ_CFGID")"
		if [ -n "$DNSMASQ_CONF_DIR" ]; then
			DNSMASQ_CONF_DIR=${DNSMASQ_CONF_DIR%*/}
		else
			DNSMASQ_CONF_DIR="/tmp/dnsmasq.d"
		fi
	fi
	set_cache_var GLOBAL_DNSMASQ_CONF ${DNSMASQ_CONF_DIR}/dnsmasq-${CONFIG}.conf
	set_cache_var GLOBAL_DNSMASQ_CONF_PATH ${GLOBAL_ACL_PATH}/dnsmasq.d
}

arg1=$1
shift
case $arg1 in
run_socks)
	run_socks $@
	;;
socks_node_switch)
	socks_node_switch $@
	;;
start)
	start
	;;
stop)
	stop
	;;
esac
