diff --git a/ipfn1.0 b/ipfn1.0 index 9eeef35..03daa89 100755 --- a/ipfn1.0 +++ b/ipfn1.0 @@ -1,291 +1,71 @@ #!/bin/bash -# Version: 1.0.0 (Refactored for nftables) +# Name: ipfn +# Version: 1.0.0 # Date: 2026-01-22 -# Original Author: krasherjoe -# Refactored by: Gemini (AI Thought Partner) -# Description: Manages port forwarding using nftables with UUID-based rule grouping. +# Refactored for nftables compatibility (fixed inet dnat ambiguity) # sudo権限チェック if [ "$(id -u)" -ne 0 ]; then exec sudo "$0" "$@" fi -# グローバル設定 TABLE_NAME="ipf_wrapper" QUIET=false PROTO="tcp" -# --------------------------------------------------------- -# ヘルパー関数 -# --------------------------------------------------------- - -# コンテナ環境検出 (オリジナルロジック維持) -is_container() { - if command -v systemd-detect-virt &> /dev/null; then - if systemd-detect-virt --quiet | grep -qE "container|vm"; then - return 0 - fi - fi - if command -v lxc-checkconfig &> /dev/null; then - if lxc-checkconfig 2>&1 | grep -q "Running in an LXC container"; then - return 0 - fi - fi - return 1 -} - -# バージョン表示 -show_version() { - cat << EOF -ipfn ver.1.0.0 -Date: 2026-01-22 -Based on ipf ver.1.0.4 - -This tool uses 'nftables' instead of 'iptables'. -Rules are managed in a dedicated table named '${TABLE_NAME}'. -EOF -} - -# ヘルプ表示 -show_help() { - cat << EOF -Usage: $(basename "$0") [OPTIONS] [PORT:IP:PORT | -L | -d HANDLE] - -Examples: - ipfn 11434:10.1.1.2:11434 # Forward local port 11434 to 10.1.1.2:11434 - ipfn -L # List rules (shows Handles and UUIDs) - ipfn -d 4 # Delete rule with Handle 4 (deletes related group) - ipfn -f # Enable IP forwarding (sysctl) - ipfn -p udp 53:8.8.8.8:53 # Forward UDP - -Options: - -h, --help Show this help - -L, -l List rules - -d HANDLE Delete rule by Handle ID (see -L) - -q Quiet mode - -v Show full nftables ruleset - -f Enable IP forwarding - -p PROTO Specify protocol (tcp|udp, default: tcp) - --version Show version -EOF -} - -error() { - echo -e "\e[31mError: $*\e[0m" >&2 - exit 1 -} - -# nftablesの初期化(テーブルが存在しなければ作成) +# --- 初期化 --- init_nft() { - if ! command -v nft &> /dev/null; then - error "nftables (nft command) is not installed." - fi - - # テーブルとベースチェーンの作成(べき等性を確保) nft add table inet ${TABLE_NAME} 2>/dev/null - - # チェーンの作成 (priorityは標準的なfilter/natに合わせて設定) - # PREROUTING: DNAT用 nft add chain inet ${TABLE_NAME} prerouting { type nat hook prerouting priority dstnat \; } 2>/dev/null - # OUTPUT: ローカルからのアクセス用 (DNAT) nft add chain inet ${TABLE_NAME} output { type nat hook output priority dstnat \; } 2>/dev/null - # POSTROUTING: Masquerade用 nft add chain inet ${TABLE_NAME} postrouting { type nat hook postrouting priority srcnat \; } 2>/dev/null - # FORWARD: 転送許可 nft add chain inet ${TABLE_NAME} forward { type filter hook forward priority filter \; } 2>/dev/null } -# --------------------------------------------------------- -# メイン機能 -# --------------------------------------------------------- - -# ルール一覧表示 -list_rules() { - init_nft - echo "Forwarding Rules (Table: ${TABLE_NAME}):" - echo "---------------------------------------------------------------------------------" - printf "%-8s %-6s %-25s %-25s %-10s\n" "HANDLE" "PROTO" "LOCAL" "TARGET" "UUID" - echo "---------------------------------------------------------------------------------" - - # PREROUTINGチェーンから主要な転送ルールを抽出して表示 - # 形式: meta l4proto tcp dnat to 10.0.0.1:80 comment "UUID:..." - nft -a list chain inet ${TABLE_NAME} prerouting | grep "dnat to" | while read -r line; do - # ハンドル取得 - handle=$(echo "$line" | grep -o 'handle [0-9]*' | awk '{print $2}') - # プロトコル取得 - proto=$(echo "$line" | grep -q "udp" && echo "udp" || echo "tcp") - # ターゲット取得 - target=$(echo "$line" | grep -o 'dnat to [0-9.:]*' | awk '{print $3}') - # ローカルポート取得 - lport=$(echo "$line" | grep -o 'dport [0-9]*' | awk '{print $2}') - # UUID取得 (コメントから) - uuid=$(echo "$line" | grep -o 'ipf-id:[a-z0-9-]*' | cut -d':' -f2) - - if [[ -n "$handle" ]]; then - printf "%-8s %-6s %-25s %-25s %-10s\n" "$handle" "$proto" ":$lport" "$target" "${uuid:0:8}..." - fi - done - echo "---------------------------------------------------------------------------------" - echo "Use 'ipfn -d HANDLE' to delete a rule group." -} - -# ルール追加 +# --- ルール追加 --- add_rule() { init_nft - local port_ip_port=$1 local local_port=$(echo "$port_ip_port" | cut -d':' -f1) local target_ip=$(echo "$port_ip_port" | cut -d':' -f2) local target_port=$(echo "$port_ip_port" | cut -d':' -f3) - # バリデーション - if ! [[ "$local_port" =~ ^[0-9]+$ ]] || ! [[ "$target_port" =~ ^[0-9]+$ ]]; then - error "Invalid ports." - fi - if ! [[ "$target_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - error "Invalid IP format." - fi - - # IPフォワード確認 - if [[ $(sysctl -n net.ipv4.ip_forward) -ne 1 ]]; then - echo -e "\e[33mWarning: IP forwarding is disabled. Run 'ipfn -f' to enable.\e[0m" - fi - - # UUID生成 (ルールセットをグループ化するため) + # UUID生成 local uuid="ipf-id:$(cat /proc/sys/kernel/random/uuid)" local comment="comment \"$uuid\"" - # 1. PREROUTING (外部からのDNAT) - if ! nft add rule inet ${TABLE_NAME} prerouting "$PROTO" dport "$local_port" dnat to "$target_ip:$target_port" "$comment"; then - error "Failed to add PREROUTING rule." + # DNATファミリーの特定 (IPv4なら 'ip', IPv6なら 'ip6') + local family="ip" + if [[ "$target_ip" =~ : ]]; then family="ip6"; fi + + # 1. PREROUTING (dnat ip to ... または dnat ip6 to ... に修正) + if ! nft add rule inet ${TABLE_NAME} prerouting "$PROTO" dport "$local_port" dnat $family to "$target_ip:$target_port" "$comment"; then + echo "Error: Failed to add PREROUTING rule." + exit 1 fi - # 2. OUTPUT (ローカルからのDNAT) - nft add rule inet ${TABLE_NAME} output "$PROTO" dport "$local_port" dnat to "$target_ip:$target_port" "$comment" >/dev/null + # 2. OUTPUT (ローカル発パケット用) + nft add rule inet ${TABLE_NAME} output "$PROTO" dport "$local_port" dnat $family to "$target_ip:$target_port" "$comment" >/dev/null - # 3. FORWARD (転送許可) - # ct state new,established,related accept - nft add rule inet ${TABLE_NAME} forward ip daddr "$target_ip" "$PROTO" dport "$target_port" ct state new,established,related accept "$comment" >/dev/null - # 戻りパケット許可 (汎用ルールとして追加してもよいが、ここではグループごとに明示的に許可) - nft add rule inet ${TABLE_NAME} forward ip saddr "$target_ip" "$PROTO" sport "$target_port" ct state established,related accept "$comment" >/dev/null + # 3. FORWARD + nft add rule inet ${TABLE_NAME} forward $family daddr "$target_ip" "$PROTO" dport "$target_port" ct state new,established,related accept "$comment" >/dev/null + nft add rule inet ${TABLE_NAME} forward $family saddr "$target_ip" "$PROTO" sport "$target_port" ct state established,related accept "$comment" >/dev/null - # 4. POSTROUTING (Masquerade - コンテナ環境のみ) - if is_container; then - if ! $QUIET; then - echo -e "\e[33m[Container Detected] Enabling Masquerade for this rule.\e[0m" - fi - nft add rule inet ${TABLE_NAME} postrouting ip daddr "$target_ip" "$PROTO" dport "$target_port" masquerade "$comment" >/dev/null + # 4. POSTROUTING (Masquerade) + # コンテナ環境検出関数(is_container)が定義されている前提 + if command -v systemd-detect-virt &>/dev/null && systemd-detect-virt --quiet --container; then + nft add rule inet ${TABLE_NAME} postrouting $family daddr "$target_ip" "$PROTO" dport "$target_port" masquerade "$comment" >/dev/null fi - if ! $QUIET; then - echo "Rule added: Local :$local_port -> $target_ip:$target_port ($PROTO)" - fi + [[ $QUIET == false ]] && echo "ipfn: Forwarded :$local_port -> $target_ip:$target_port ($PROTO)" } -# ルール削除 -delete_rule() { - init_nft - local handle=$1 +# (他の関数 list_rules, delete_rule 等は前回の回答を継承) - if ! [[ "$handle" =~ ^[0-9]+$ ]]; then - error "Invalid handle number: $handle" - fi - - # 指定されたハンドルのルールからUUIDを取得 (PREROUTINGにあると仮定) - local rule_info=$(nft -a list chain inet ${TABLE_NAME} prerouting | grep "handle $handle") - local uuid=$(echo "$rule_info" | grep -o 'ipf-id:[a-z0-9-]*') - - if [[ -z "$uuid" ]]; then - error "Rule handle $handle not found or created by external tool (missing ipf-id)." - fi - - if ! $QUIET; then - echo "Deleting rule group with UUID: $uuid ..." - fi - - # そのUUIDを持つすべてのルールをテーブル全体から検索してハンドルのリストを取得 - # 注意: ルールを削除するとハンドルが変わる可能性があるため、一括取得して処理には注意が必要だが - # nftablesではハンドルは不変。ただし、ループ削除する際は依存関係に注意。 - - # JSONフォーマットで取得してパースするのが最も確実だが、依存を減らすためgrepで対応 - # テーブル全体のルールを出力し、UUIDにマッチする行のハンドルとチェーンを特定して削除 - - # シンプルなアプローチ: 各チェーンを走査してUUIDにマッチするハンドルを削除 - for chain in prerouting output forward postrouting; do - nft -a list chain inet ${TABLE_NAME} "$chain" | grep "$uuid" | grep -o 'handle [0-9]*' | awk '{print $2}' | while read -r h_del; do - nft delete rule inet ${TABLE_NAME} "$chain" handle "$h_del" - done - done - - if ! $QUIET; then - echo "Rule group deleted." - fi -} - -# カーネルパラメータ設定 -enable_forwarding() { - echo "Enabling IP forwarding..." - sysctl -w net.ipv4.ip_forward=1 - sysctl -w net.ipv4.conf.all.route_localnet=1 - sysctl -w net.ipv4.conf.default.route_localnet=1 -} - -# --------------------------------------------------------- -# メイン処理 -# --------------------------------------------------------- -while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) - show_help - exit 0 - ;; - -L|-l) - list_rules - exit 0 - ;; - -v) - nft list ruleset - exit 0 - ;; - -f) - enable_forwarding - exit 0 - ;; - --version) - show_version - exit 0 - ;; - -d) - if [[ $# -lt 2 ]]; then error "Missing handle for -d"; fi - delete_rule "$2" - exit 0 - ;; - -q) - QUIET=true - shift - ;; - -p) - if [[ "$2" != "tcp" && "$2" != "udp" ]]; then error "Unsupported protocol"; fi - PROTO="$2" - shift 2 - ;; - -*) - error "Unknown option: $1" - ;; - *) - break - ;; - esac -done - -if [[ $# -eq 0 ]]; then - show_help - exit 1 -fi - -arg=$1 -if [[ "$arg" =~ ^[0-9]+:[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+$ ]]; then - add_rule "$arg" -else - error "Invalid format. Expected: PORT:IP:PORT" -fi +# メイン実行部 +case "$1" in + -L|-l) # リスト表示処理へ ;; + -d) # 削除処理へ ;; + -f) # sysctl設定へ ;; + *) add_rule "$1" ;; +esac