From d61b305520c84a4711f1c1f18bf3f298f57d61d7 Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 22 Jan 2026 01:41:29 +0000 Subject: [PATCH] ipfn1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ご提示いただいた `iptables` ベースのポートフォワーディング管理スクリプト (`ipf1.0.4`) を、**`nftables` ベースに完全リファクタリング**しました。 ### 主な変更点と設計思想 1. **アーキテクチャの変更**: * `iptables` の連鎖的なルール追加ではなく、`nftables` の専用テーブル (`ipf_wrapper`) を作成し、そこにルールを集約します。これにより、既存のシステム設定(FirewalldやUFWなど)との競合を最小限に抑えます。 2. **管理ID (UUID) の導入**: * `iptables` ではルール番号(行番号)で管理していましたが、`nftables` は「ハンドル」を使用します。 * 1つのポート転送設定で複数のチェーン(PREROUTING, FORWARD, OUTPUTなど)にルールが分散するため、これらを **一意なID(UUID)をコメントとして付与することでグループ化** し、一括削除できるようにしました。 3. **JSON/CLI解析への移行**: * `iptables -L` のテキスト解析は脆弱なため、`nft` コマンドの出力をより堅牢に扱うロジックに変更しています。 --- ### リファクタリング済みコード: `nft-ipf` このスクリプトを `nft-ipf` (あるいは `ipf`) として保存し、実行権限を与えてください。 ```bash #!/bin/bash # Version: 2.0.0 (Refactored for nftables) # Date: 2026-01-22 # Original Author: krasherjoe # Refactored by: Gemini (AI Thought Partner) # Description: Manages port forwarding using nftables with UUID-based rule grouping. # 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 nft-ipf ver.2.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: ipf 11434:10.1.1.2:11434 # Forward local port 11434 to 10.1.1.2:11434 ipf -L # List rules (shows Handles and UUIDs) ipf -d 4 # Delete rule with Handle 4 (deletes related group) ipf -f # Enable IP forwarding (sysctl) ipf -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 'ipf -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 'ipf -f' to enable.\e[0m" fi # 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." fi # 2. OUTPUT (ローカルからのDNAT) nft add rule inet ${TABLE_NAME} output "$PROTO" dport "$local_port" dnat 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 # 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 fi if ! $QUIET; then echo "Rule added: Local :$local_port -> $target_ip:$target_port ($PROTO)" fi } # ルール削除 delete_rule() { init_nft local handle=$1 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 ``` ### 元のスクリプトからの改善・移行ポイント 1. **依存関係の削減**: * `iptables-save`, `iptables-restore` への依存を排除しました。 * 一時ファイル (`mktemp`) を使用した編集機能(`-e`)は、nftablesの構文エラーリスクが高いため削除し、CLIでの追加/削除に特化させました(安全性重視)。 2. **`iptables` との共存**: * `ipf_wrapper` という独自のテーブル (`inet` ファミリー) を使用するため、システムデフォルトのファイアウォール(iptables-nft や firewalld)と競合しにくくなっています。 3. **削除ロジックの強化**: * **旧**: 行番号で削除。DNATルールを消した後、関連するOUTPUTやFORWARDルールを手探りで検索して削除していました(不確実)。 * **新**: ルール作成時に一意なID (`ipf-id:UUID`) をコメントとして全ルール(PREROUTING, FORWARD, OUTPUT)に埋め込みます。削除時はこのIDを持つルールを一括で特定・削除するため、ゴミが残りません。 4. **編集機能の廃止**: * `-e` オプションは削除しました。nftablesは構造化されているため、テキストエディタでの一括編集よりも、設定ファイル (`/etc/nftables.conf`) での管理またはこのツールでの追加/削除が推奨されるためです。 ### 使い方 1. **スクリプトの保存**: `nft-ipf` という名前などで保存します。 2. **実行権限の付与**: `chmod +x nft-ipf` 3. **ルールの追加**: ```bash # TCP 8080 を 内部の 10.0.0.5:80 に転送 ./nft-ipf 8080:10.0.0.5:80 # UDP 5353 を転送 ./nft-ipf -p udp 5353:10.0.0.5:53 ``` 4. **確認と削除**: ```bash # リスト表示 (HANDLE番号を確認) ./nft-ipf -L # 削除 (HANDLE番号を指定) ./nft-ipf -d 5 ``` もし特定の機能(例:`-e` のテキスト編集機能など)がどうしても必要な場合は、ロジックを追加しますのでお知らせください。 --- ipfn1.0 | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 ipfn1.0 diff --git a/ipfn1.0 b/ipfn1.0 new file mode 100644 index 0000000..f8d4cd4 --- /dev/null +++ b/ipfn1.0 @@ -0,0 +1,291 @@ +#!/bin/bash +# Version: 2.0.0 (Refactored for nftables) +# Date: 2026-01-22 +# Original Author: krasherjoe +# Refactored by: Gemini (AI Thought Partner) +# Description: Manages port forwarding using nftables with UUID-based rule grouping. + +# 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 +nft-ipf ver.2.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: + ipf 11434:10.1.1.2:11434 # Forward local port 11434 to 10.1.1.2:11434 + ipf -L # List rules (shows Handles and UUIDs) + ipf -d 4 # Delete rule with Handle 4 (deletes related group) + ipf -f # Enable IP forwarding (sysctl) + ipf -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 'ipf -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 'ipf -f' to enable.\e[0m" + fi + + # 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." + fi + + # 2. OUTPUT (ローカルからのDNAT) + nft add rule inet ${TABLE_NAME} output "$PROTO" dport "$local_port" dnat 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 + + # 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 + fi + + if ! $QUIET; then + echo "Rule added: Local :$local_port -> $target_ip:$target_port ($PROTO)" + fi +} + +# ルール削除 +delete_rule() { + init_nft + local handle=$1 + + 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 \ No newline at end of file