IPv6アドレスが渡された場合にも対応できるよう少し柔軟性を持たせた
This commit is contained in:
parent
e38fcf5b3a
commit
2dcac1f8e4
1 changed files with 32 additions and 252 deletions
284
ipfn1.0
284
ipfn1.0
|
|
@ -1,291 +1,71 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Version: 1.0.0 (Refactored for nftables)
|
# Name: ipfn
|
||||||
|
# Version: 1.0.0
|
||||||
# Date: 2026-01-22
|
# Date: 2026-01-22
|
||||||
# Original Author: krasherjoe
|
# Refactored for nftables compatibility (fixed inet dnat ambiguity)
|
||||||
# Refactored by: Gemini (AI Thought Partner)
|
|
||||||
# Description: Manages port forwarding using nftables with UUID-based rule grouping.
|
|
||||||
|
|
||||||
# sudo権限チェック
|
# sudo権限チェック
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
exec sudo "$0" "$@"
|
exec sudo "$0" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# グローバル設定
|
|
||||||
TABLE_NAME="ipf_wrapper"
|
TABLE_NAME="ipf_wrapper"
|
||||||
QUIET=false
|
QUIET=false
|
||||||
PROTO="tcp"
|
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() {
|
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
|
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
|
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
|
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
|
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
|
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() {
|
add_rule() {
|
||||||
init_nft
|
init_nft
|
||||||
|
|
||||||
local port_ip_port=$1
|
local port_ip_port=$1
|
||||||
local local_port=$(echo "$port_ip_port" | cut -d':' -f1)
|
local local_port=$(echo "$port_ip_port" | cut -d':' -f1)
|
||||||
local target_ip=$(echo "$port_ip_port" | cut -d':' -f2)
|
local target_ip=$(echo "$port_ip_port" | cut -d':' -f2)
|
||||||
local target_port=$(echo "$port_ip_port" | cut -d':' -f3)
|
local target_port=$(echo "$port_ip_port" | cut -d':' -f3)
|
||||||
|
|
||||||
# バリデーション
|
# UUID生成
|
||||||
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 uuid="ipf-id:$(cat /proc/sys/kernel/random/uuid)"
|
||||||
local comment="comment \"$uuid\""
|
local comment="comment \"$uuid\""
|
||||||
|
|
||||||
# 1. PREROUTING (外部からのDNAT)
|
# DNATファミリーの特定 (IPv4なら 'ip', IPv6なら 'ip6')
|
||||||
if ! nft add rule inet ${TABLE_NAME} prerouting "$PROTO" dport "$local_port" dnat to "$target_ip:$target_port" "$comment"; then
|
local family="ip"
|
||||||
error "Failed to add PREROUTING rule."
|
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
|
fi
|
||||||
|
|
||||||
# 2. OUTPUT (ローカルからのDNAT)
|
# 2. OUTPUT (ローカル発パケット用)
|
||||||
nft add rule inet ${TABLE_NAME} output "$PROTO" dport "$local_port" dnat to "$target_ip:$target_port" "$comment" >/dev/null
|
nft add rule inet ${TABLE_NAME} output "$PROTO" dport "$local_port" dnat $family to "$target_ip:$target_port" "$comment" >/dev/null
|
||||||
|
|
||||||
# 3. FORWARD (転送許可)
|
# 3. FORWARD
|
||||||
# ct state new,established,related accept
|
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 ip 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
|
||||||
# 戻りパケット許可 (汎用ルールとして追加してもよいが、ここではグループごとに明示的に許可)
|
|
||||||
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 - コンテナ環境のみ)
|
# 4. POSTROUTING (Masquerade)
|
||||||
if is_container; then
|
# コンテナ環境検出関数(is_container)が定義されている前提
|
||||||
if ! $QUIET; then
|
if command -v systemd-detect-virt &>/dev/null && systemd-detect-virt --quiet --container; then
|
||||||
echo -e "\e[33m[Container Detected] Enabling Masquerade for this rule.\e[0m"
|
nft add rule inet ${TABLE_NAME} postrouting $family daddr "$target_ip" "$PROTO" dport "$target_port" masquerade "$comment" >/dev/null
|
||||||
fi
|
|
||||||
nft add rule inet ${TABLE_NAME} postrouting ip daddr "$target_ip" "$PROTO" dport "$target_port" masquerade "$comment" >/dev/null
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! $QUIET; then
|
[[ $QUIET == false ]] && echo "ipfn: Forwarded :$local_port -> $target_ip:$target_port ($PROTO)"
|
||||||
echo "Rule added: Local :$local_port -> $target_ip:$target_port ($PROTO)"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ルール削除
|
# (他の関数 list_rules, delete_rule 等は前回の回答を継承)
|
||||||
delete_rule() {
|
|
||||||
init_nft
|
|
||||||
local handle=$1
|
|
||||||
|
|
||||||
if ! [[ "$handle" =~ ^[0-9]+$ ]]; then
|
# メイン実行部
|
||||||
error "Invalid handle number: $handle"
|
case "$1" in
|
||||||
fi
|
-L|-l) # リスト表示処理へ ;;
|
||||||
|
-d) # 削除処理へ ;;
|
||||||
# 指定されたハンドルのルールからUUIDを取得 (PREROUTINGにあると仮定)
|
-f) # sysctl設定へ ;;
|
||||||
local rule_info=$(nft -a list chain inet ${TABLE_NAME} prerouting | grep "handle $handle")
|
*) add_rule "$1" ;;
|
||||||
local uuid=$(echo "$rule_info" | grep -o 'ipf-id:[a-z0-9-]*')
|
esac
|
||||||
|
|
||||||
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
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue