#!/bin/sh # Copyright (C) 2022-2025 xiaorouji # Copyright (C) 2026 Openwrt-Passwall Organization CONFIG=passwall APP_PATH=/usr/share/${CONFIG} TMP_PATH=/tmp/etc/${CONFIG} TMP_PATH2=${TMP_PATH}_tmp LOCK_PATH=/var/lock LOG_FILE=/tmp/log/${CONFIG}.log TMP_ACL_PATH=${TMP_PATH}/acl TMP_BIN_PATH=${TMP_PATH}/bin TMP_IFACE_PATH=${TMP_PATH}/iface TMP_ROUTE_PATH=${TMP_PATH}/route TMP_SCRIPT_FUNC_PATH=${TMP_PATH}/script_func RULES_PATH=/usr/share/${CONFIG}/rules USER_RULES_PATH=/etc/${CONFIG}/rules IPv6_REGEX="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" IPv6_REGEX="${IPv6_REGEX}([0-9a-fA-F]{1,4}:){1,7}:|" IPv6_REGEX="${IPv6_REGEX}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" IPv6_REGEX="${IPv6_REGEX}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" IPv6_REGEX="${IPv6_REGEX}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" IPv6_REGEX="${IPv6_REGEX}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" IPv6_REGEX="${IPv6_REGEX}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" IPv6_REGEX="${IPv6_REGEX}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" IPv6_REGEX="${IPv6_REGEX}:((:[0-9a-fA-F]{1,4}){1,7}|:)|" IPv6_REGEX="${IPv6_REGEX}fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" IPv6_REGEX="${IPv6_REGEX}::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|" IPv6_REGEX="${IPv6_REGEX}([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" IPv4_REGEX="((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" . /lib/functions/network.sh echolog() { local d="$(date "+%Y-%m-%d %H:%M:%S")" echo -e "$d: $*" >>$LOG_FILE } clean_log() { logsnum=$(cat $LOG_FILE 2>/dev/null | wc -l) [ "$logsnum" -gt 1000 ] && { echo "" > $LOG_FILE echolog "日志文件过长,清空处理!" } } config_get_type() { local ret=$(uci -q get "${CONFIG}.${1}" 2>/dev/null) printf '%s' "${ret:=$2}" } config_n_get() { local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null) printf '%s' "${ret:=$3}" } config_t_get() { local index=${4:-0} local ret=$(uci -q get "${CONFIG}.@${1}[${index}].${2}" 2>/dev/null) printf '%s' "${ret:=${3}}" } eval_set_val() { local param for param in "$@"; do eval "${param%%=*}=\${param#*=}" done } eval_unset_val() { for i in $@; do for j in $i; do eval unset $j done done } lua_api() { local func=${1} [ -z "${func}" ] && { echo "nil" return } echo $(lua -e "local api = require 'luci.passwall.api' print(api.${func})") } eval_cache_var() { [ -s "$TMP_PATH/var" ] && . "$TMP_PATH/var" } del_cache_var() { local key="${1}" [ -n "${key}" ] && [ -f "${TMP_PATH}/var" ] && { sed -i "/^${key}=/d" "$TMP_PATH/var" >/dev/null 2>&1 } } set_cache_var() { local key="${1}" shift 1 [ -n "${key}" ] && { del_cache_var ${key} local val="$@" [ -n "${val}" ] && { [ ! -d $TMP_PATH ] && mkdir -p $TMP_PATH printf '%s=%s\n' "$key" "$(shell_quote "$val")" >> "$TMP_PATH/var" eval "$key=\$val" } } } get_cache_var() { [ -n "$1" ] && [ -s "$TMP_PATH/var" ] && ( . "$TMP_PATH/var" eval "printf '%s' \"\${$1-}\"" ) } first_type() { [ "${1#/}" != "$1" ] && [ -x "$1" ] && echo "$1" && return for p in "/bin/$1" "/usr/bin/$1" "${TMP_BIN_PATH:-/tmp}/$1"; do [ -x "$p" ] && echo "$p" && return done command -v "$1" 2>/dev/null || command -v "$2" 2>/dev/null } get_enabled_anonymous_secs() { uci -q show "${CONFIG}" | grep "${1}\[.*\.enabled='1'" | cut -d '.' -sf2 } get_geoip() { local geo_output_path="$TMP_PATH2/geo_output" mkdir -p ${geo_output_path} local geoip_code="$1" local geoip_type_flag="" local output_path="${geo_output_path}/geoip-${geoip_code}-$2" [ ! -s "${output_path}" ] && { local geoip_path="$(config_n_get @global_rules[0] v2ray_location_asset)" geoip_path="${geoip_path%*/}/geoip.dat" local bin="$(first_type $(config_n_get @global_app[0] geoview_file) geoview)" [ -n "$bin" ] && [ -s "$geoip_path" ] || { echo ""; return; } case "$2" in "ipv4") geoip_type_flag="-ipv6=false" ;; "ipv6") geoip_type_flag="-ipv4=false" ;; esac "$bin" -input "$geoip_path" -list "$geoip_code" $geoip_type_flag -lowmem=true -output ${output_path} } [ -s "${output_path}" ] && cat "${output_path}" } get_host_ip() { local host=$2 local count=$3 [ -z "$count" ] && count=3 local isip="" local ip="" if [ "$1" = "ipv6" ]; then host=${host#[} host=${host%]} isip=$(printf '%s' "$host" | grep -E "^($IPv6_REGEX)$") [ -n "$isip" ] && ip=$isip else isip=$(printf '%s' "$host" | grep -E "^($IPv4_REGEX)$") [ -n "$isip" ] && ip=$isip fi [ -z "$isip" ] && { local t=4 [ "$1" = "ipv6" ] && t=6 local vpsrip=$(resolveip -$t -t "$count" "$host" | awk 'NR==1{print}') ip=$vpsrip } [ -n "$ip" ] && echo "$ip" } get_node_host_ip() { local ip local address=$(config_n_get $1 address) [ -n "$address" ] && { local use_ipv6=$(config_n_get $1 use_ipv6) local network_type="ipv4" [ "$use_ipv6" = "1" ] && network_type="ipv6" ip=$(get_host_ip $network_type $address) } echo $ip } get_ip_port_from() { local __host=${1}; shift 1 local __ipv=${1}; shift 1 local __portv=${1}; shift 1 local __ucipriority=${1}; shift 1 local val1="$__host" val2 case "$__host" in *\#*) val1=${__host%#*}; val2=${__host##*#} ;; \[*\]:*) val1=${__host%\]:*}; val2=${__host##*:} ;; *:*:*) ;; *:*) val1=${__host%:*}; val2=${__host##*:} ;; esac if [ -n "${__ucipriority}" ]; then val2=$(config_n_get "$__host" port "$val2") val1=$(config_n_get "$__host" address "$val1") fi val1=${val1#[} val1=${val1%]} eval "${__ipv}=\$val1; ${__portv}=\$val2" } parse_doh() { local __doh=$1 __url_var=$2 __host_var=$3 __port_var=$4 __bootstrap_var=$5 __doh=$(printf '%s' "$__doh" | tr -d ' \t\n') local __url=${__doh%%,*} local __bootstrap=${__doh#*,} [ "$__bootstrap" = "$__doh" ] && __bootstrap="" local __host_port=$(lua_api "get_domain_from_url(\"${__url}\")") local __host __port if echo "${__host_port}" | grep -q '^\[.*\]:[0-9]\+$'; then __host=${__host_port%%]:*}] __port=${__host_port##*:} elif echo "${__host_port}" | grep -q ':[0-9]\+$'; then __host=${__host_port%:*} __port=${__host_port##*:} else __host=${__host_port} __port=443 fi __host=${__host#[} __host=${__host%]} if [ "$(lua_api "is_ip(\"${__host}\")")" = "true" ]; then __bootstrap=${__host} fi __bootstrap=${__bootstrap#[} __bootstrap=${__bootstrap%]} eval "${__url_var}='${__url}' ${__host_var}='${__host}' ${__port_var}='${__port}' ${__bootstrap_var}='${__bootstrap}'" } host_from_url(){ local f="${1}" f=${f#*://} f=${f%%[/?#]*} f=${f##*@} case "$f" in \[*\]*) f=${f#\[}; f=${f%%\]*} ;; *) f=${f%%:*} ;; esac printf '%s' "$f" } hosts_foreach() { local __hosts eval "__hosts=\$${1}"; shift 1 local __func=${1}; shift 1 local __default_port=${1}; shift 1 local __ret=1 [ -z "${__hosts}" ] && return 0 local __ip __port for __host in $(echo $__hosts | sed 's/[ ,]/\n/g'); do get_ip_port_from "$__host" "__ip" "__port" eval "$__func \"${__host}\" \"\${__ip}\" \"\${__port:-${__default_port}}\" \"$@\"" __ret=$? [ ${__ret} -ge ${ERROR_NO_CATCH:-1} ] && return ${__ret} done return 0 } check_host() { local f=${1} a=$(echo $f | grep "\/") [ -n "$a" ] && return 1 # 判断是否包含汉字~ local tmp=$(echo -n $f | awk '{print gensub(/[!-~]/,"","g",$0)}') [ -n "$tmp" ] && return 1 return 0 } normalize_dns() { local s="$1" local addr port="${2-}" case "$s" in \[*\]:*) # [ip6]:port addr="${s%\]:*}" addr="${addr#\[}" port="${s##*:}" ;; *\#*) # ip4#port or ip6#port addr="${s%\#*}" port="${s##*\#}" ;; *.*:*) # ip4:port addr="${s%:*}" port="${s##*:}" ;; \[*\]) # [ip6] addr="${s#\[}" addr="${addr%\]}" ;; *) addr="$s" ;; esac [ -n "$port" ] && echo "${addr}#${port}" || echo "$addr" } format_dns() { local dns="${1%%#*}" local port="${1#*#}" [ "$port" = "$1" ] && port="${2-53}" case "$dns" in *:*) echo "[$dns]:$port" ;; *) echo "$dns:$port" ;; esac } check_port_exists() { local port=$1 local protocol=$2 [ -n "$protocol" ] || protocol="tcp,udp" local result= if [ "$protocol" = "tcp" ]; then result=$(netstat -tln | grep -c ":$port ") elif [ "$protocol" = "udp" ]; then result=$(netstat -uln | grep -c ":$port ") elif [ "$protocol" = "tcp,udp" ]; then result=$(netstat -tuln | grep -c ":$port ") fi echo "${result}" } get_new_port() { local default_start_port=2001 local min_port=1025 local max_port=49151 local port="$1" # Required parameter; please pass "auto" if you want it to be automatic. local protocol=$(echo "$2" | tr 'A-Z' 'a-z') local is_auto if [ "$port" = "auto" ]; then is_auto=1 local last_get_new_port_auto=$(get_cache_var "last_get_new_port_auto") if [ -n "$last_get_new_port_auto" ]; then port=$(expr "$last_get_new_port_auto" + 1) else port=$default_start_port fi fi ([ "$port" -lt "$min_port" ] || [ "$port" -gt "$max_port" ]) && port=$default_start_port local start_port=$port while :; do local result=$(check_port_exists "$port" "$protocol") if [ "$is_auto" = "1" ] && [ -n "$(get_cache_var "get_port_${port}")" ]; then # The port has already been allocated, continue to the next port. result=1 fi [ "$result" = "0" ] && break if [ "$port" -lt "$max_port" ]; then # If the port is smaller than the maximum port, increment by 1 and continue. port=$(expr "$port" + 1) else port=$min_port fi [ "$port" -eq "$start_port" ] && return 1 done if [ "$is_auto" = "1" ]; then # Set cache to prevent the port from being allocated again. set_cache_var "get_port_${port}" "1" set_cache_var "last_get_new_port_auto" "$port" fi echo "$port" } check_ver() { local version1="$1" local version2="$2" local i v1 v1_1 v1_2 v1_3 v2 v2_1 v2_2 v2_3 local old_ifs="$IFS" IFS='.' set -- $version1; v1_1=${1:-0}; v1_2=${2:-0}; v1_3=${3:-0} set -- $version2; v2_1=${1:-0}; v2_2=${2:-0}; v2_3=${3:-0} IFS="$old_ifs" for i in 1 2 3; do eval v1=\$v1_$i eval v2=\$v2_$i if [ "$v1" -gt "$v2" ]; then # $1 大于 $2 echo 0 return elif [ "$v1" -lt "$v2" ]; then # $1 小于 $2 echo 1 return fi done # $1 等于 $2 echo 255 } has_1_65535() { local val="$1" val=${val//:/-} case ",$val," in *,1-65535,*) return 0 ;; *) return 1 ;; esac } add_ip2route() { local ip=$(get_host_ip "ipv4" $1) [ -z "$ip" ] && { echolog " - 无法解析[${1}],路由表添加失败!" return 1 } local remarks="${1}" [ "$remarks" != "$ip" ] && remarks="${1}(${ip})" local gateway device network_get_gateway gateway "$2" network_get_device device "$2" [ -z "${device}" ] && device=$(ubus call "network.interface.$2" status 2>/dev/null | jsonfilter -e '@.device' 2>/dev/null) [ -z "${device}" ] && [ -d "/sys/class/net/$2" ] && device="$2" [ -z "${device}" ] && device="$2" if [ -n "${gateway}" ]; then if route add -host "$ip" gw "$gateway" dev "$device" >/dev/null 2>&1; then printf '%s\n' "$ip" >> "$TMP_ROUTE_PATH/$device" echolog " - [${remarks}]添加到接口[${device}]路由表成功!" else echolog " - [${remarks}]添加到接口[${device}]路由表失败!" return 1 fi else echolog " - [${remarks}]添加到接口[${device}]路由表失败!原因是找不到[${device}]网关。" return 1 fi } delete_ip2route() { [ -d "${TMP_ROUTE_PATH}" ] && { local interface for interface in $(ls ${TMP_ROUTE_PATH}); do for ip in $(cat ${TMP_ROUTE_PATH}/${interface}); do route del -host ${ip} dev ${interface} >/dev/null 2>&1 done done } } shell_quote() { printf "'" printf '%s' "$1" | sed "s/'/'\\\\''/g" printf "'" } get_process_start_time() { sed 's/.*) //' "/proc/$1/stat" 2>/dev/null | awk '{print $20}' } ln_run() { local file_func=${1} local ln_name=${2} local output=${3} shift 3; case "$ln_name" in dnsmasq*) set -- -k "$@" ;; haproxy) set -- -db "$@" ;; esac if [ "${file_func%%/*}" != "${file_func}" ]; then [ ! -L "${file_func}" ] && { ln -s "${file_func}" "${TMP_BIN_PATH}/${ln_name}" >/dev/null 2>&1 file_func="${TMP_BIN_PATH}/${ln_name}" } [ -x "${file_func}" ] || echolog " - $(readlink ${file_func}) 没有执行权限,无法启动:${file_func} $*" fi #echo "${file_func} $*" >&2 [ -n "${file_func}" ] || { echolog " - 找不到 ${ln_name},无法启动..." return 1 } "$file_func" "$@" >"$output" 2>&1 & local pid=$! [ "$NO_REC_PROCESS" = "1" ] && return local process_file=$(mktemp "$TMP_SCRIPT_FUNC_PATH/XXXXXX") local arg { printf '%s\n' "$pid" "$(get_process_start_time "$pid")" printf '%s' "$(shell_quote "$file_func")" for arg in "$@"; do printf ' %s' "$(shell_quote "$arg")" done printf ' >%s 2>&1\n' "$(shell_quote "$output")" } > "$process_file" } kill_all() { kill -9 $(pidof "$@") >/dev/null 2>&1 } get_subscribe_host(){ local line uci show "${CONFIG}" | grep "=subscribe_list" | while read -r line; do local section="$(echo "$line" | cut -d '.' -sf 2 | cut -d '=' -sf 1)" local url="$(config_n_get $section url)" [ -n "$url" ] || continue url="$(host_from_url "$url")" echo "$url" done } gen_lanlist() { cat $USER_RULES_PATH/lanlist_ipv4 | tr -s '\n' | grep -v "^#" } gen_lanlist_6() { cat $USER_RULES_PATH/lanlist_ipv6 | tr -s '\n' | grep -v "^#" } get_wan_ips() { local family="$1" local NET_ADDR local iface addr addrs local INTERFACES=$(ubus call network.interface dump | jsonfilter -e \ '@.interface[!(@.interface ~ /lan/) && @.route[0]].interface') for iface in $INTERFACES; do if [ "$family" = "ip6" ]; then network_get_ipaddrs6 addrs "$iface" else network_get_ipaddrs addrs "$iface" fi for addr in $addrs; do case "$addr" in ""|0.0.0.0|::|fe80:*) continue ;; esac case " $NET_ADDR " in *" $addr "*) ;; *) NET_ADDR="${NET_ADDR:+$NET_ADDR }$addr" ;; esac done done echo "$NET_ADDR" } get_local_ips() { local family="$1" local ALL_IPS WAN_IPS ip NET_ADDR if [ "$family" = "ip6" ]; then ALL_IPS=$(ip -o -6 addr show scope global | awk '{print $4}' | cut -d/ -f1) WAN_IPS=$(get_wan_ips ip6) else ALL_IPS=$(ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1) WAN_IPS=$(get_wan_ips ip4) fi # 补充回环(scope global 不包含) [ "$family" = "ip6" ] && ALL_IPS="$ALL_IPS ::1" [ "$family" != "ip6" ] && ALL_IPS="$ALL_IPS 127.0.0.1" for ip in $ALL_IPS; do case "$ip" in ""|0.0.0.0|::) continue ;; esac case " $WAN_IPS " in *" $ip "*) continue ;; esac case " $NET_ADDR " in *" $ip "*) ;; *) NET_ADDR="${NET_ADDR:+$NET_ADDR }$ip" ;; esac done for ip in $NET_ADDR; do echo "$ip" done }