diff --git a/ipfn b/ipfn index 59d133d..c0be79c 100755 --- a/ipfn +++ b/ipfn @@ -1,20 +1,21 @@ #!/bin/bash # Name: ipfn -# Version: 1.2.8 +# Version: 1.3.5 # Date: 2026-01-22 -# Description: Fixed logical fall-through after all-clear to prevent grep errors. +# Description: Smart testing for multi-target ports and fixed UUID listing format. if [ "$(id -u)" -ne 0 ]; then exec sudo "$0" "$@" fi TABLE_NAME="ipf_wrapper" +VERSION="1.3.5" PROTO="tcp" FORCE_FLAG=false SKIP_TEST=false QUIET_MODE=false -# --- 1. Utility & Core Setup --- +# --- 1. Utility & Core --- msg() { [[ "$QUIET_MODE" == false ]] && echo -e "$@"; } @@ -26,29 +27,6 @@ init_nft() { nft add chain inet "${TABLE_NAME}" forward { type filter hook forward priority 0 \; policy accept \; } 2>/dev/null } -enable_forwarding() { - msg "Optimizing kernel parameters for VM/LXD/Docker..." - sysctl -w net.ipv4.ip_forward=1 >/dev/null - sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null - sysctl -w net.ipv4.conf.all.route_localnet=1 >/dev/null - sysctl -w net.bridge.bridge-nf-call-iptables=1 2>/dev/null - sysctl -w net.bridge.bridge-nf-call-ip6tables=1 2>/dev/null - msg "\e[32mForwarding and Bridge-NF enabled.\e[0m" -} - -show_help() { - echo "Usage: ipfn [OPTIONS] [RULES]" - echo "" - echo "Options:" - echo " -l, -L List all rules" - echo " -d HANDLE/:PORT/all Delete by handle, port (:80), or '*' for all" - echo " -R Reset: Clear ALL rules immediately" - echo " -t [HANDLE / PORT:] Test connectivity (e.g., -t 33, -t 80: or -t :80)" - echo " -f Enable IP forward & Bridge tuning / Skip test" - echo " -q Quiet mode (No output, Auto-yes)" - echo " -h Show this help" -} - list_rules() { [[ "$QUIET_MODE" == true ]] && return local highlight_uuid=$1 @@ -62,102 +40,69 @@ list_rules() { target=$(echo "$line" | grep -oE '([0-9.]+|\[[0-9a-fA-F:]+\]):[0-9]+' | head -n 1) lport=$(echo "$line" | grep -o 'dport [0-9]*' | awk '{print $2}') full_uuid=$(echo "$line" | grep -o 'ipf-id:[a-z0-9-]*' | cut -d':' -f2) + + # 表示のズレを修正 (スペース調整) if [[ -n "$highlight_uuid" && "$full_uuid" == "$highlight_uuid" ]]; then printf "\e[1;36m*%-9s %-6s %-20s %-20s %-10s\e[0m\n" "$handle" "$proto" ":$lport" "$target" "${full_uuid:0:8}" else - printf "%-10s %-6s %-20s %-20s %-10s\n" "$handle" "$proto" ":$lport" "$target" "${full_uuid:0:8}" + printf " %-9s %-6s %-20s %-20s %-10s\n" "$handle" "$proto" ":$lport" "$target" "${full_uuid:0:8}" fi done } -test_rule_by_port() { - [[ "$QUIET_MODE" == true ]] && return - local lp=$1 - echo -n "Testing connectivity to :$lp... " - if nc -z -v -w 2 127.0.0.1 "$lp" 2>&1 | grep -iqE "succeeded|connected|open"; then +get_total_packets() { + local uuid=$1 + local total=0 + local counts=$(nft list table inet "${TABLE_NAME}" 2>/dev/null | grep "$uuid" | grep -o 'packets [0-9]*' | awk '{print $2}') + for c in $counts; do total=$((total + c)); done + echo "$total" +} + +test_port_simple() { + local p=$1 + echo -n "Testing :$p... " + if nc -z -v -w 2 127.0.0.1 "$p" 2>&1 | grep -iqE "succeeded|connected|open"; then echo -e "\e[32mOK\e[0m" else echo -e "\e[31mFAILED\e[0m" fi } -# --- 2. Deletion & Reset --- - -validate_rule_handle() { - nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "handle $1" | grep -q "dnat" +test_specific_rule() { + local lp=$1 + local full_id=$2 + local short_id="${full_id#*:}" + echo -n "Verifying specific rule [${short_id:0:8}] on :$lp... " + local count_before=$(get_total_packets "$full_id") + nc -z -v -w 1 127.0.0.1 "$lp" >/dev/null 2>&1 + sleep 0.1 + local count_after=$(get_total_packets "$full_id") + if (( count_after > count_before )); then + echo -e "\e[32mPASSED\e[0m (Rule matched)" + else + echo -e "\e[31mSKIPPED\e[0m (Blocked by prior rule)" + fi } +# --- 2. Deletion & Reset --- + all_clear() { if [[ "$FORCE_FLAG" == false ]]; then - msg "\e[33mWarning: This will delete ALL forwarding rules.\e[0m" + echo -e "\e[33mWarning: This will delete ALL forwarding rules.\e[0m" read -p "Are you sure? (y/N): " confirm [[ ! "$confirm" =~ ^[yY]$ ]] && exit 0 fi nft delete table inet "${TABLE_NAME}" 2>/dev/null - init_nft - msg "All rules cleared successfully." - list_rules - exit 0 # 確実にここで終わらせる + init_nft; msg "All rules cleared successfully."; list_rules; exit 0 } -# --- 3. Parsing Logic --- - -parse_delete_targets() { - local input="$1" - local -a final_handles=() - - # 'all' や '*' なら即座に全削除へ(戻ってこない) - [[ "$input" == "*" || "$input" == "all" ]] && all_clear - - IFS=',' read -r -a parts <<< "$input" - for part in "${parts[@]}"; do - if [[ "$part" =~ ^:([0-9]+)$ ]]; then - local target_port="${BASH_REMATCH[1]}" - local found_h=$(nft -a list chain inet "${TABLE_NAME}" prerouting | grep "dport $target_port" | grep -o 'handle [0-9]*' | awk '{print $2}') - for h in $found_h; do final_handles+=("$h"); done - elif [[ "$part" =~ ^([0-9]+)-([0-9]+)$ ]]; then - for ((i=${BASH_REMATCH[1]}; i<=${BASH_REMATCH[2]}; i++)); do - validate_rule_handle "$i" && final_handles+=("$i") - done - else - validate_rule_handle "$part" && final_handles+=("$part") - fi - done - echo "${final_handles[@]}" -} - -add_rule() { - init_nft - local raw_input="$1" - local lp tip tp - if [[ "$raw_input" =~ ^([0-9]+):([0-9\.]+):([0-9]+)$ ]]; then - lp=${BASH_REMATCH[1]}; tip=${BASH_REMATCH[2]}; tp=${BASH_REMATCH[3]} - elif [[ "$raw_input" =~ ^([0-9]+):([0-9]+)$ ]]; then - lp=${BASH_REMATCH[1]}; tip="127.0.0.1"; tp=${BASH_REMATCH[2]} - elif [[ "$raw_input" =~ ^([0-9]+)$ ]]; then - lp=${BASH_REMATCH[1]}; tip="127.0.0.1"; tp=${BASH_REMATCH[1]} - else - msg "\e[31mError: Invalid rule format '$raw_input'\e[0m"; exit 1 - fi - local raw_uuid=$(cat /proc/sys/kernel/random/uuid) - local uuid="ipf-id:$raw_uuid" - local family="ip"; [[ "$tip" =~ : ]] && family="ip6" - nft add rule inet "${TABLE_NAME}" prerouting "$PROTO" dport "$lp" dnat $family to "$tip:$tp" comment "\"$uuid\"" - nft add rule inet "${TABLE_NAME}" output "$PROTO" dport "$lp" dnat $family to "$tip:$tp" comment "\"$uuid\"" - nft add rule inet "${TABLE_NAME}" forward $family daddr "$tip" "$PROTO" dport "$tp" ct state new,established,related accept comment "\"$uuid\"" - nft add rule inet "${TABLE_NAME}" forward $family saddr "$tip" "$PROTO" sport "$tp" ct state established,related accept comment "\"$uuid\"" - nft add rule inet "${TABLE_NAME}" forward iifname "lo" accept comment "\"$uuid\"" - nft add rule inet "${TABLE_NAME}" postrouting $family daddr "$tip" "$PROTO" dport "$tp" masquerade comment "\"$uuid\"" - list_rules "$raw_uuid" - [[ "$SKIP_TEST" == false ]] && { echo ""; test_rule_by_port "$lp"; } -} - -# --- 4. Main Parsing Loop --- +# --- 3. Main Loop --- for arg in "$@"; do [[ "$arg" =~ q ]] && QUIET_MODE=true && FORCE_FLAG=true && SKIP_TEST=true [[ "$arg" =~ f ]] && FORCE_FLAG=true && SKIP_TEST=true [[ "$arg" == "-R" ]] && RESET_MODE=true + [[ "$arg" == "--version" ]] && { echo "ipfn version ${VERSION}"; exit 0; } done if [[ "$RESET_MODE" == true ]]; then all_clear; fi @@ -166,58 +111,69 @@ if [[ $# -eq 0 ]]; then list_rules; exit 0; fi while [[ $# -gt 0 ]]; do case "$1" in -h|--help) show_help; exit 0 ;; - -v|--verbose) nft list table inet "${TABLE_NAME}"; exit 0 ;; -L|-l) list_rules; exit 0 ;; - -f) enable_forwarding; [[ $# -eq 1 ]] && exit 0 ;; - -t*) - h_str="${1#-t}" - if [[ -z "$h_str" && -n "$2" ]]; then h_str="$2"; shift; fi - target_port="" - if [[ "$h_str" =~ ^:?([0-9]+):?$ ]]; then - target_port="${BASH_REMATCH[1]}" - elif [[ -n "$h_str" ]]; then - target_port=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "handle $h_str" | grep -o 'dport [0-9]*' | awk '{print $2}') - else - count=$(nft list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep -c "dnat") - if [ "$count" -eq 1 ]; then - target_port=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "dnat" | grep -o 'dport [0-9]*' | awk '{print $2}') - fi - fi - [[ -n "$target_port" ]] && test_rule_by_port "$target_port" || msg "Error: Port/Handle not found." - exit 0 ;; -*[d]*) h_str=$(echo "$1" | sed -E 's/^-q?d?f?//') [[ -z "$h_str" ]] && { h_str="$2"; shift; } - - # parse_delete_targets の中で 'all' 判定があれば exit するので、ここには戻らない - handles=($(parse_delete_targets "$h_str")) - - [[ ${#handles[@]} -eq 0 ]] && { msg "No valid targets."; exit 1; } - - if [[ "$FORCE_FLAG" == false ]]; then - msg "Review rules to delete (Top-level only):" - for h in "${handles[@]}"; do - nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "handle $h" | sed 's/^/ /' - done - read -p "Delete these rules and their sub-rules? (y/N): " confirm - [[ ! "$confirm" =~ ^[yY]$ ]] && exit 0 - fi - + if [[ "$h_str" == "all" || "$h_str" == "*" ]]; then all_clear; fi + handles=() + IFS=',' read -r -a parts <<< "$h_str" + for part in "${parts[@]}"; do + if [[ "$part" =~ ^:([0-9]+)$ ]]; then + p="${BASH_REMATCH[1]}" + for h in $(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "dport $p" | grep -o 'handle [0-9]*' | awk '{print $2}'); do handles+=("$h"); done + else handles+=("$part"); fi + done for h in "${handles[@]}"; do - ri=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "handle $h") - [[ -z "$ri" ]] && continue - uuid=$(echo "$ri" | grep -o 'ipf-id:[a-z0-9-]*' | head -n 1) + uuid=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "handle $h" | grep -o 'ipf-id:[a-z0-9-]*') + [[ -z "$uuid" ]] && continue for c in prerouting output forward postrouting; do nft -a list chain inet "${TABLE_NAME}" "$c" 2>/dev/null | grep "$uuid" | grep -o 'handle [0-9]*' | awk '{print $2}' | while read -r rh; do nft delete rule inet "${TABLE_NAME}" "$c" handle "$rh" done done - msg "Deleted associated with handle $h" + msg "Deleted handle $h" done list_rules; exit 0 ;; + -t*) + h_str="${1#-t}"; [[ -z "$h_str" && -n "$2" ]] && { h_str="$2"; shift; } + tp="" + # スマート・テストロジック + if [[ "$h_str" =~ ^:?([0-9]+):?$ ]]; then + tp="${BASH_REMATCH[1]}" + elif [[ -n "$h_str" ]]; then + tp=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "handle $h_str" | grep -o 'dport [0-9]*' | awk '{print $2}') + else + # 引数なしの場合は一番上のルールのポートを取得 + tp=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "dnat" | head -n 1 | grep -o 'dport [0-9]*' | awk '{print $2}') + fi + + [[ -n "$tp" ]] && test_port_simple "$tp" || msg "Error: No active rules found." + exit 0 ;; *) - if [[ "$1" =~ ^[0-9] ]]; then add_rule "$1"; exit 0 - else msg "\e[31mError: Unknown option '$1'\e[0m\n"; show_help; exit 1; fi ;; + if [[ "$1" =~ ^[0-9] ]]; then + init_nft + raw="$1" + if [[ "$raw" =~ ^([0-9]+):([0-9\.]+):([0-9]+)$ ]]; then lp=${BASH_REMATCH[1]}; tip=${BASH_REMATCH[2]}; tp=${BASH_REMATCH[3]} + elif [[ "$raw" =~ ^([0-9]+):([0-9]+)$ ]]; then lp=${BASH_REMATCH[1]}; tip="127.0.0.1"; tp=${BASH_REMATCH[2]} + elif [[ "$raw" =~ ^([0-9]+)$ ]]; then lp=${BASH_REMATCH[1]}; tip="127.0.0.1"; tp=${BASH_REMATCH[1]} + fi + + nft list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep -q "dport $lp" && multi_mode=true || multi_mode=false + u_raw=$(cat /proc/sys/kernel/random/uuid); u="ipf-id:$u_raw"; fam="ip"; [[ "$tip" =~ : ]] && fam="ip6" + + nft add rule inet "${TABLE_NAME}" prerouting "$PROTO" dport "$lp" counter dnat $fam to "$tip:$tp" comment "\"$u\"" + nft add rule inet "${TABLE_NAME}" output "$PROTO" dport "$lp" counter dnat $fam to "$tip:$tp" comment "\"$u\"" + nft add rule inet "${TABLE_NAME}" forward $fam daddr "$tip" "$PROTO" dport "$tp" ct state new,established,related accept comment "\"$u\"" + nft add rule inet "${TABLE_NAME}" forward $fam saddr "$tip" "$PROTO" sport "$tp" ct state established,related accept comment "\"$u\"" + nft add rule inet "${TABLE_NAME}" forward iifname "lo" accept comment "\"$u\"" + nft add rule inet "${TABLE_NAME}" postrouting $fam daddr "$tip" "$PROTO" dport "$tp" masquerade comment "\"$u\"" + + list_rules "$u_raw" + [[ "$multi_mode" == true ]] && msg "\e[33mNote: Port :$lp is now multi-targeted.\e[0m" + [[ "$SKIP_TEST" == false ]] && { echo ""; test_specific_rule "$lp" "$u"; } + exit 0 + fi ;; esac shift done