ipf/ipfn1.0
2026-01-22 10:50:27 +09:00

291 lines
10 KiB
Bash
Executable file
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.

#!/bin/bash
# Version: 1.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
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生成 (ルールセットをグループ化するため)
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