Implement iptables forward manager core
This commit is contained in:
302
lib/common.sh
Normal file
302
lib/common.sh
Normal file
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ -n ${IPF_COMMON_SH_LOADED:-} ]]; then
|
||||
return 0
|
||||
fi
|
||||
IPF_COMMON_SH_LOADED=1
|
||||
|
||||
: "${IPF_COLOR_ENABLED:=1}"
|
||||
: "${IPF_FORCE_PLAIN_UI:=0}"
|
||||
: "${IPF_APP_NAME:=IPTables 端口转发管理工具}"
|
||||
|
||||
if [[ ! -t 1 ]]; then
|
||||
IPF_COLOR_ENABLED=0
|
||||
fi
|
||||
|
||||
if [[ ${IPF_COLOR_ENABLED} == 1 ]]; then
|
||||
CLR_RED=$'\033[31m'
|
||||
CLR_GREEN=$'\033[32m'
|
||||
CLR_YELLOW=$'\033[33m'
|
||||
CLR_BLUE=$'\033[34m'
|
||||
CLR_CYAN=$'\033[36m'
|
||||
CLR_BOLD=$'\033[1m'
|
||||
CLR_DIM=$'\033[2m'
|
||||
CLR_RESET=$'\033[0m'
|
||||
else
|
||||
CLR_RED=''
|
||||
CLR_GREEN=''
|
||||
CLR_YELLOW=''
|
||||
CLR_BLUE=''
|
||||
CLR_CYAN=''
|
||||
CLR_BOLD=''
|
||||
CLR_DIM=''
|
||||
CLR_RESET=''
|
||||
fi
|
||||
|
||||
command_is_available() {
|
||||
local cmd=${1:-}
|
||||
if [[ -z ${cmd} ]]; then
|
||||
return 1
|
||||
fi
|
||||
if [[ ${cmd} == */* ]]; then
|
||||
[[ -x ${cmd} ]]
|
||||
return
|
||||
fi
|
||||
command -v "${cmd}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
ts_now() {
|
||||
date '+%H:%M:%S'
|
||||
}
|
||||
|
||||
_log_with_color() {
|
||||
local color=$1
|
||||
local level=$2
|
||||
shift 2
|
||||
printf '%s[%s] [%s]%s %s\n' "${color}" "$(ts_now)" "${level}" "${CLR_RESET}" "$*" >&2
|
||||
}
|
||||
|
||||
log_info() { _log_with_color "${CLR_BLUE}" INFO "$@"; }
|
||||
log_warn() { _log_with_color "${CLR_YELLOW}" WARN "$@"; }
|
||||
log_err() { _log_with_color "${CLR_RED}" ERROR "$@"; }
|
||||
log_ok() { _log_with_color "${CLR_GREEN}" OK "$@"; }
|
||||
|
||||
display_width() {
|
||||
local input=${1-}
|
||||
awk -v str="${input}" 'BEGIN {
|
||||
n = split(str, chars, "")
|
||||
width = 0
|
||||
for (i = 1; i <= n; i++) {
|
||||
if (chars[i] ~ /[ -~]/) {
|
||||
width += 1
|
||||
} else {
|
||||
width += 2
|
||||
}
|
||||
}
|
||||
print width
|
||||
}'
|
||||
}
|
||||
|
||||
repeat_char() {
|
||||
local char=$1
|
||||
local count=$2
|
||||
local out=''
|
||||
while (( count > 0 )); do
|
||||
out+="${char}"
|
||||
((count--))
|
||||
done
|
||||
printf '%s' "${out}"
|
||||
}
|
||||
|
||||
term_width() {
|
||||
if [[ -n ${COLUMNS:-} ]]; then
|
||||
printf '%s\n' "${COLUMNS}"
|
||||
return
|
||||
fi
|
||||
if command_is_available tput; then
|
||||
tput cols 2>/dev/null && return
|
||||
fi
|
||||
printf '80\n'
|
||||
}
|
||||
|
||||
use_box_ui() {
|
||||
[[ ${IPF_FORCE_PLAIN_UI} != 1 ]] || return 1
|
||||
[[ $(term_width) -ge 60 ]]
|
||||
}
|
||||
|
||||
pad_right() {
|
||||
local text=${1-}
|
||||
local width=$2
|
||||
local actual padding
|
||||
actual=$(display_width "${text}")
|
||||
if (( actual >= width )); then
|
||||
printf '%s' "${text}"
|
||||
return
|
||||
fi
|
||||
padding=$((width - actual))
|
||||
printf '%s%*s' "${text}" "${padding}" ''
|
||||
}
|
||||
|
||||
box_top() {
|
||||
local width=${1:-60}
|
||||
printf '╔%s╗\n' "$(repeat_char '═' "$((width - 2))")"
|
||||
}
|
||||
|
||||
box_bottom() {
|
||||
local width=${1:-60}
|
||||
printf '╚%s╝\n' "$(repeat_char '═' "$((width - 2))")"
|
||||
}
|
||||
|
||||
box_separator() {
|
||||
local width=${1:-60}
|
||||
printf '╠%s╣\n' "$(repeat_char '═' "$((width - 2))")"
|
||||
}
|
||||
|
||||
box_line() {
|
||||
local width=${1:-60}
|
||||
local text=${2-}
|
||||
local inner=$((width - 4))
|
||||
printf '║ %s ║\n' "$(pad_right "${text}" "${inner}")"
|
||||
}
|
||||
|
||||
print_section() {
|
||||
local title=${1-}
|
||||
if use_box_ui; then
|
||||
local width
|
||||
width=$(term_width)
|
||||
if (( width > 78 )); then
|
||||
width=78
|
||||
fi
|
||||
box_top "${width}"
|
||||
box_line "${width}" "${title}"
|
||||
box_bottom "${width}"
|
||||
else
|
||||
printf '== %s ==\n' "${title}"
|
||||
fi
|
||||
}
|
||||
|
||||
read_test_input() {
|
||||
local reply=''
|
||||
if [[ -n ${IPF_TEST_INPUTS-} ]]; then
|
||||
if [[ ${IPF_TEST_INPUTS} == *$'\n'* ]]; then
|
||||
reply=${IPF_TEST_INPUTS%%$'\n'*}
|
||||
IPF_TEST_INPUTS=${IPF_TEST_INPUTS#*$'\n'}
|
||||
else
|
||||
reply=${IPF_TEST_INPUTS}
|
||||
IPF_TEST_INPUTS=''
|
||||
fi
|
||||
fi
|
||||
printf '%s' "${reply}"
|
||||
}
|
||||
|
||||
prompt_input() {
|
||||
local prompt=$1
|
||||
local default_value=${2-}
|
||||
local reply=''
|
||||
|
||||
if [[ -n ${IPF_TEST_INPUTS-} ]]; then
|
||||
reply=$(read_test_input)
|
||||
if [[ -z ${reply} && -n ${default_value} ]]; then
|
||||
reply=${default_value}
|
||||
fi
|
||||
printf '%s\n' "${reply}" >&2
|
||||
printf '%s\n' "${reply}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n ${default_value} ]]; then
|
||||
read -r -p "${prompt} [${default_value}]: " reply
|
||||
reply=${reply:-${default_value}}
|
||||
else
|
||||
read -r -p "${prompt}: " reply
|
||||
fi
|
||||
printf '%s\n' "${reply}"
|
||||
}
|
||||
|
||||
prompt_confirm() {
|
||||
local prompt=$1
|
||||
local default_choice=${2:-n}
|
||||
local answer normalized suffix
|
||||
|
||||
if [[ ${default_choice} == y ]]; then
|
||||
suffix='[Y/n]'
|
||||
else
|
||||
suffix='[y/N]'
|
||||
fi
|
||||
|
||||
answer=$(prompt_input "${prompt} ${suffix}" "")
|
||||
if [[ -z ${answer} ]]; then
|
||||
answer=${default_choice}
|
||||
fi
|
||||
normalized=${answer,,}
|
||||
[[ ${normalized} == y || ${normalized} == yes ]]
|
||||
}
|
||||
|
||||
prompt_select() {
|
||||
local prompt=$1
|
||||
shift
|
||||
local options=("$@")
|
||||
local idx answer count
|
||||
count=${#options[@]}
|
||||
|
||||
for ((idx = 0; idx < count; idx++)); do
|
||||
printf ' [%d] %s\n' "$((idx + 1))" "${options[idx]}" >&2
|
||||
done
|
||||
|
||||
while true; do
|
||||
answer=$(prompt_input "${prompt}" "")
|
||||
if [[ ${answer} =~ ^[0-9]+$ ]] && (( answer >= 1 && answer <= count )); then
|
||||
printf '%s\n' "$((answer - 1))"
|
||||
return 0
|
||||
fi
|
||||
log_warn "请输入 1-${count} 之间的编号。"
|
||||
done
|
||||
}
|
||||
|
||||
pause_return() {
|
||||
if [[ -n ${IPF_TEST_INPUTS-} ]]; then
|
||||
return 0
|
||||
fi
|
||||
read -r -p '按回车继续...' _
|
||||
}
|
||||
|
||||
validate_ipv4() {
|
||||
local ip=${1-}
|
||||
local IFS='.'
|
||||
local -a parts=()
|
||||
local part
|
||||
[[ ${ip} =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
|
||||
read -r -a parts <<<"${ip}"
|
||||
for part in "${parts[@]}"; do
|
||||
[[ ${part} =~ ^[0-9]+$ ]] || return 1
|
||||
(( part >= 0 && part <= 255 )) || return 1
|
||||
done
|
||||
}
|
||||
|
||||
validate_ipv6() {
|
||||
local ip=${1-}
|
||||
local base scope
|
||||
[[ -n ${ip} ]] || return 1
|
||||
[[ ${ip} != *.* ]] || return 1
|
||||
[[ ${ip} == *:* ]] || return 1
|
||||
[[ ${ip} != *:::* ]] || return 1
|
||||
|
||||
scope=''
|
||||
base=${ip}
|
||||
if [[ ${ip} == *%* ]]; then
|
||||
scope=${ip#*%}
|
||||
base=${ip%%\%*}
|
||||
[[ -n ${scope} ]] || return 1
|
||||
[[ ${scope} =~ ^[A-Za-z0-9_.:-]+$ ]] || return 1
|
||||
fi
|
||||
|
||||
[[ ${base} =~ ^[0-9A-Fa-f:]+$ ]] || return 1
|
||||
[[ ${base} =~ :: ]] || [[ ${base} =~ ^([0-9A-Fa-f]{1,4}:){1,7}[0-9A-Fa-f]{1,4}$ ]] || return 1
|
||||
}
|
||||
|
||||
validate_port() {
|
||||
local port=${1-}
|
||||
[[ ${port} =~ ^[0-9]+$ ]] || return 1
|
||||
(( port >= 1 && port <= 65535 ))
|
||||
}
|
||||
|
||||
validate_proto() {
|
||||
case ${1-} in
|
||||
tcp|udp|both) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
validate_ipver() {
|
||||
case ${1-} in
|
||||
4|6|both) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if (( EUID != 0 )); then
|
||||
log_err '请使用 root 或 sudo 运行此脚本。'
|
||||
return 2
|
||||
fi
|
||||
}
|
||||
Reference in New Issue
Block a user