#!/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 '-' /dev/null 2>&1; then printf '✓\n' else printf '!\n' fi } 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 printf '%-3s %-10s %-10s %-32s %-10s %-12s %-4s %s\n' '#' '协议' '本地端口' '目标地址' '目标端口' '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 "${uuid}") printf '%-3s %-10s %-10s %-32s %-10s %-12s %-4s %s\n' \ "$((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 '[✓] 已在 iptables 中找到规则;[!] 仅存在于数据库中。\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 '添加新的转发规则' proto_choice=$(prompt_select '请选择协议' 'TCP' 'UDP' 'TCP + UDP') case ${proto_choice} in 0) proto=tcp ;; 1) proto=udp ;; 2) proto=both ;; *) return 1 ;; esac while true; do lport=$(prompt_input '请输入本地监听端口' '') validate_port "${lport}" && break log_warn '端口无效,请重新输入。' done ipver_choice=$(prompt_select '请选择 IP 版本' '仅 IPv4' '仅 IPv6' '同时 IPv4 + IPv6') case ${ipver_choice} in 0) ipver=4 ;; 1) ipver=6 ;; 2) ipver=both ;; *) return 1 ;; esac while true; do tip=$(prompt_input "$(rule_target_hint "${ipver}")" '') rule_validate_target "${tip}" "${ipver}" && break log_warn '目标地址无效,请重新输入。' done while true; do tport=$(prompt_input '请输入目标端口' '') validate_port "${tport}" && break log_warn '目标端口无效,请重新输入。' done desc=$(prompt_input '请输入描述(可留空)' '') 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 answer=$(prompt_input '请输入要删除的规则编号' '') 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 }