Files
IPTables-Management/lib/rules_mgr.sh

392 lines
10 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
if [[ -n ${IPF_RULES_MGR_SH_LOADED:-} ]]; then
return 0
fi
IPF_RULES_MGR_SH_LOADED=1
rules_new_uuid() {
local raw
if [[ -r /proc/sys/kernel/random/uuid ]]; then
raw=$(tr -d '-' </proc/sys/kernel/random/uuid)
printf '%s\n' "${raw:0:8}"
return 0
fi
date '+%s%N' | tail -c 9
}
rules_now_iso() {
date '+%Y-%m-%dT%H:%M:%S%z'
}
rule_sanitize_desc() {
local desc=${1-}
desc=${desc//$'\n'/ }
desc=${desc//|//}
printf '%s\n' "${desc}"
}
rule_target_hint() {
case ${1-} in
4) printf '请输入目标 IPv4 地址' ;;
6) printf '请输入目标 IPv6 地址' ;;
both) printf '请输入目标地址,格式为 IPv4,IPv6' ;;
*) printf '请输入目标地址' ;;
esac
}
rule_validate_target() {
local tip=${1-}
local ipver=${2-}
case ${ipver} in
4)
validate_ipv4 "${tip}"
;;
6)
validate_ipv6 "${tip}"
;;
both)
[[ ${tip} == *,* ]] || return 1
validate_ipv4 "${tip%%,*}" || return 1
validate_ipv6 "${tip#*,}" || return 1
;;
*)
return 1
;;
esac
}
rule_validate_definition() {
local proto=${1-}
local lport=${2-}
local tip=${3-}
local tport=${4-}
local ipver=${5-}
validate_proto "${proto}" || {
log_err '协议无效,仅支持 tcp/udp/both。'
return 1
}
validate_port "${lport}" || {
log_err '本地端口无效,应为 1-65535。'
return 1
}
validate_port "${tport}" || {
log_err '目标端口无效,应为 1-65535。'
return 1
}
validate_ipver "${ipver}" || {
log_err 'IP 版本无效,仅支持 4/6/both。'
return 1
}
rule_validate_target "${tip}" "${ipver}" || {
log_err '目标地址与 IP 版本不匹配。若选择 both请使用 IPv4,IPv6。'
return 1
}
}
rule_proto_label() {
case ${1-} in
tcp) printf 'TCP' ;;
udp) printf 'UDP' ;;
both) printf 'TCP/UDP' ;;
*) printf '%s' "${1-}" ;;
esac
}
rule_ipver_label() {
case ${1-} in
4) printf 'IPv4' ;;
6) printf 'IPv6' ;;
both) printf 'IPv4+IPv6' ;;
*) printf '%s' "${1-}" ;;
esac
}
rule_target_label() {
local tip=${1-}
if [[ ${tip} == *,* ]]; then
printf '%s | %s' "${tip%%,*}" "${tip#*,}"
else
printf '%s' "${tip}"
fi
}
rule_build_line() {
local uuid=$1 proto=$2 lport=$3 tip=$4 tport=$5 ipver=$6 desc=${7-}
local created
created=$(rules_now_iso)
desc=$(rule_sanitize_desc "${desc}")
printf 'uuid=%s|proto=%s|lport=%s|tip=%s|tport=%s|ipver=%s|desc=%s|created=%s\n' \
"${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" "${desc}" "${created}"
}
rule_field() {
storage_parse "$1" "$2"
}
rules_load_lines() {
mapfile -t RULES_CACHE < <(storage_list)
}
rule_health_mark() {
local line=${1-}
local uuid proto lport tip tport ipver
uuid=$(rule_field "${line}" uuid)
proto=$(rule_field "${line}" proto)
lport=$(rule_field "${line}" lport)
tip=$(rule_field "${line}" tip)
tport=$(rule_field "${line}" tport)
ipver=$(rule_field "${line}" ipver)
if ipt_rule_healthy "${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}"; then
printf '✓\n'
else
printf '!\n'
fi
}
render_rules_row() {
printf '%s %s %s %s %s %s %s %s\n' \
"$(pad_right "${1-}" 3)" "$(pad_right "${2-}" 10)" "$(pad_right "${3-}" 10)" "$(pad_right "${4-}" 32)" \
"$(pad_right "${5-}" 10)" "$(pad_right "${6-}" 12)" "$(pad_right "${7-}" 4)" "${8-}"
}
render_rules_plain() {
local -a lines=("$@")
local line idx uuid proto lport tip tport ipver desc health
if ((${#lines[@]} == 0)); then
printf '当前没有规则。\n'
return 0
fi
render_rules_row '#' '协议' '本地端口' '目标地址' '目标端口' 'IP版本' '状态' '描述'
for ((idx = 0; idx < ${#lines[@]}; idx++)); do
line=${lines[idx]}
uuid=$(rule_field "${line}" uuid)
proto=$(rule_proto_label "$(rule_field "${line}" proto)")
lport=$(rule_field "${line}" lport)
tip=$(rule_target_label "$(rule_field "${line}" tip)")
tport=$(rule_field "${line}" tport)
ipver=$(rule_ipver_label "$(rule_field "${line}" ipver)")
desc=$(rule_field "${line}" desc || true)
health=$(rule_health_mark "${line}")
render_rules_row "$((idx + 1))" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" "${health}" "${desc}"
done
}
cmd_list() {
local pause=${1:-1}
rules_load_lines
render_rules_plain "${RULES_CACHE[@]}"
printf '[✓] 运行态规则完整;[!] 至少缺少一条运行态规则或仅存在于数据库中。\n'
if [[ ${pause} == 1 ]]; then
pause_return
fi
}
_cmd_add_batch_locked() {
local proto=${1-}
local lport=${2-}
local tip=${3-}
local tport=${4-}
local ipver=${5-}
local desc=${6-}
local uuid line
rule_validate_definition "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" || return 1
uuid=$(rules_new_uuid)
line=$(rule_build_line "${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" "${desc}")
if ! ipt_apply_rule "${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}"; then
log_err 'iptables 规则写入失败。'
return 1
fi
if ! storage_add_unlocked "${line}"; then
ipt_remove_rule "${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" >/dev/null 2>&1 || true
log_err '写入规则数据库失败。'
return 1
fi
if ! persist_save; then
storage_delete_unlocked "${uuid}" >/dev/null 2>&1 || true
ipt_remove_rule "${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" >/dev/null 2>&1 || true
log_err '保存持久化规则失败,已回滚。'
return 1
fi
log_ok "规则已添加UUID=${uuid}"
printf '%s\n' "${uuid}"
}
cmd_add_batch() {
storage_with_lock _cmd_add_batch_locked "$@"
}
cmd_add() {
local proto_choice ipver_choice proto ipver lport tip tport desc
print_section '添加新的转发规则'
prompt_select_capture '请选择协议' 'TCP' 'UDP' 'TCP + UDP'
proto_choice=${PROMPT_SELECT_RESULT}
case ${proto_choice} in
0) proto=tcp ;;
1) proto=udp ;;
2) proto=both ;;
*) return 1 ;;
esac
while true; do
prompt_input_capture '请输入本地监听端口' ''
lport=${PROMPT_INPUT_RESULT}
validate_port "${lport}" && break
log_warn '端口无效,请重新输入。'
done
prompt_select_capture '请选择 IP 版本' '仅 IPv4' '仅 IPv6' '同时 IPv4 + IPv6'
ipver_choice=${PROMPT_SELECT_RESULT}
case ${ipver_choice} in
0) ipver=4 ;;
1) ipver=6 ;;
2) ipver=both ;;
*) return 1 ;;
esac
while true; do
prompt_input_capture "$(rule_target_hint "${ipver}")" ''
tip=${PROMPT_INPUT_RESULT}
rule_validate_target "${tip}" "${ipver}" && break
log_warn '目标地址无效,请重新输入。'
done
while true; do
prompt_input_capture '请输入目标端口' ''
tport=${PROMPT_INPUT_RESULT}
validate_port "${tport}" && break
log_warn '目标端口无效,请重新输入。'
done
prompt_input_capture '请输入描述(可留空)' ''
desc=${PROMPT_INPUT_RESULT}
desc=$(rule_sanitize_desc "${desc}")
printf '协议: %s\n' "$(rule_proto_label "${proto}")"
printf '监听端口: %s\n' "${lport}"
printf '目标地址: %s\n' "$(rule_target_label "${tip}")"
printf '目标端口: %s\n' "${tport}"
printf 'IP 版本: %s\n' "$(rule_ipver_label "${ipver}")"
printf '描述: %s\n' "${desc:-(空)}"
if ! prompt_confirm '确认添加该规则?' y; then
log_warn '已取消添加。'
return 1
fi
cmd_add_batch "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" "${desc}" >/dev/null
}
_cmd_delete_uuid_locked() {
local uuid=${1-}
local line proto lport tip tport ipver
line=$(storage_get "${uuid}") || {
log_err '未找到指定 UUID 的规则。'
return 1
}
proto=$(rule_field "${line}" proto)
lport=$(rule_field "${line}" lport)
tip=$(rule_field "${line}" tip)
tport=$(rule_field "${line}" tport)
ipver=$(rule_field "${line}" ipver)
if ! ipt_remove_rule "${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}"; then
log_err '删除 iptables 规则失败。'
return 1
fi
if ! storage_delete_unlocked "${uuid}"; then
ipt_apply_rule "${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" >/dev/null 2>&1 || true
log_err '删除规则数据库记录失败,已尝试回滚。'
return 1
fi
if ! persist_save; then
storage_add_unlocked "${line}" >/dev/null 2>&1 || true
ipt_apply_rule "${uuid}" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" >/dev/null 2>&1 || true
log_err '持久化保存失败,已尝试回滚。'
return 1
fi
log_ok "规则 ${uuid} 已删除。"
}
cmd_delete_uuid() {
storage_with_lock _cmd_delete_uuid_locked "$@"
}
cmd_delete() {
local index answer line uuid
rules_load_lines
if ((${#RULES_CACHE[@]} == 0)); then
log_info '当前没有可删除的规则。'
pause_return
return 0
fi
cmd_list 0
while true; do
prompt_input_capture '请输入要删除的规则编号' ''
answer=${PROMPT_INPUT_RESULT}
if [[ ${answer} =~ ^[0-9]+$ ]] && (( answer >= 1 && answer <= ${#RULES_CACHE[@]} )); then
index=$((answer - 1))
break
fi
log_warn '编号无效,请重新输入。'
done
line=${RULES_CACHE[index]}
uuid=$(rule_field "${line}" uuid)
printf '即将删除: %s -> %s:%s (%s / %s)\n' \
"$(rule_field "${line}" lport)" \
"$(rule_target_label "$(rule_field "${line}" tip)")" \
"$(rule_field "${line}" tport)" \
"$(rule_proto_label "$(rule_field "${line}" proto)")" \
"$(rule_ipver_label "$(rule_field "${line}" ipver)")"
if ! prompt_confirm '确认删除该规则?' n; then
log_warn '已取消删除。'
return 1
fi
cmd_delete_uuid "${uuid}"
}
cmd_show_env_status() {
print_section '环境状态'
env_check_collect_issues
env_check_print_report
printf '规则总数: %s\n' "$(storage_count)"
if persist_available; then
printf '持久化: 可用\n'
else
printf '持久化: 不可用\n'
fi
pause_return
}
_cmd_save_rules_locked() {
if persist_save; then
log_ok '规则已保存到磁盘。'
else
log_err '规则保存失败。'
return 1
fi
}
cmd_save_rules() {
storage_with_lock _cmd_save_rules_locked
}