ipf/ipf1.0.4
2026-01-15 08:25:19 +09:00

395 lines
No EOL
14 KiB
Bash

#!/bin/bash
# sudo権限で実行されているか確認し、そうでなければsudoで再実行
if [ "$(id -u)" -ne 0 ]; then
exec sudo "$0" "$@"
fi
# グローバル変数
RULES_FILE="/tmp/iptables_forward_rules"
QUIET=false
PROTO="tcp" # デフォルトプロトコル
# コンテナ環境検出関数
is_container() {
# systemd-detect-virt を使用してコンテナ環境を検出
if command -v systemd-detect-virt &> /dev/null; then
if systemd-detect-virt --quiet | grep -qE "container|vm"; then
return 0 # コンテナまたは仮想マシン環境
fi
fi
# lxc-checkconfig を使用して LXC コンテナを検出 (systemd-detect-virt がない場合)
if command -v lxc-checkconfig &> /dev/null; then
if lxc-checkconfig 2>&1 | grep -q "Running in an LXC container"; then
return 0 # LXC コンテナ環境
fi
fi
# その他 (LXD など) のコンテナ環境検出方法を追加可能
return 1 # コンテナ環境ではない
}
# ヘルプ表示
show_help() {
cat << EOF
Usage: ipf [OPTIONS] [PORT:IP:PORT | -L | -d RULE_NUMBER]
Examples:
ipf 11434:10.1.1.2:11434 # Forward local port 11434 to 10.1.1.2:11434
ipf -L # List all rules with numbers
ipf -d 1 # Delete rule number 1
ipf -d 1 -q # Delete rule number 1 (quiet mode)
ipf -v # Show current iptables rules by iptables-save
ipf -e # Edit all rules in editor and restore
ipf -f # Enable IP forwarding
ipf -t 1 # Test rule number 1
ipf -t 11434:10.10.1.2:11434 # Test rule by specification
ipf -p udp 11434:10.1.1.2:11434 # Forward using UDP
Options:
-h, --help Show this help message
-L, -l List all rules
-d NUM Delete rule by number
-q Quiet mode (no output)
-v Show current iptables rules (via iptables-save)
-e Edit all rules in editor and restore
-f Enable IP forwarding and localnet routing
-t ARG Test connectivity for rule number or PORT:IP:PORT
-p PROTO Specify protocol (tcp|udp) for the following rule
--version Show version information
NOTE:
All port forwarding is performed using DNAT only. No MASQUERADE (SNAT) is applied,
so the original source IP of the client is preserved. This ensures that
fail2ban logs the correct client IP on the destination server.
EOF
}
# バージョン情報を表示
show_version() {
cat << EOF
ipf ver.1.0.4
Date: 2025-10-26
Created by: qwen3/gpt-oss/gemini-2.5-pro and krasherjoe
---
MIT License
Copyright (c) 2025 krasherjoe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
** WARNING / 注意 **
This script operates with administrative privileges (sudo) to modify your system's
firewall (iptables) rules. Incorrect use, especially with the -e (edit) option,
can disrupt your network connectivity or overwrite existing security rules.
Please use with caution and understand the changes you are making.
このスクリプトは管理者権限(sudo)で動作し、システムのファイアウォール(iptables)ルールを
直接変更します。特に -e (編集) オプションの誤った使用は、ネットワーク接続を中断させたり、
既存のセキュリティルールを上書きする危険性があります。
コンテナ環境ではMASQUERADEを使用するため、クライアントの元のIPアドレスが隠蔽されます。
これにより、fail2banなどのツールが不正な行為者を正しく識別し、ブロックできなくなる可能性があります。
セキュリティへの影響を考慮してください。
内容をよく理解した上で、注意して使用してください。
EOF
}
# エラーメッセージを表示
error() {
echo "Error: $*" >&2
logger -p user.err "ipf: $*"
exit 1
}
# ルールの保存
save_rules() {
iptables-save > "$RULES_FILE"
}
# ルールの読み込み
load_rules() {
if [[ -f "$RULES_FILE" ]]; then
iptables-restore < "$RULES_FILE"
fi
}
# ルール番号と内容をリスト表示
list_rules() {
local i=1
echo "Forwarding rules:"
iptables -t nat -L PREROUTING -n -v --line-numbers | grep -E '^[0-9]+' | while read -r line; do
echo "$i: $line"
((i++))
done
}
# ルールを削除
delete_rule() {
local num=$1
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
error "Invalid rule number: $num"
fi
# ユーザーへの表示用に整形されたルール行を取得
local rule_line_verbose=$(iptables -t nat -L PREROUTING --line-numbers | grep -E "^$num\s+" | head -n 1)
if [[ -z "$rule_line_verbose" ]]; then
error "No rule found with number: $num"
fi
# パース用に-nオプションを付けたルール行を取得
local rule_line_numeric=$(iptables -t nat -L PREROUTING -n --line-numbers | grep -E "^\s*$num\s+" | head -n 1)
if [[ -z "$rule_line_numeric" ]]; then
error "Could not find numeric rule for number: $num" # Should not happen
fi
# ルールから詳細を抽出 (sedを使い、より堅牢に)
local line_details=$(echo "$rule_line_numeric" | sed -n 's/.*\(tcp\|udp\).*dpt:\([0-9]*\).*to:\([0-9.]*\):\([0-9]*\).*/\1 \2 \3 \4/p')
if [[ -z "$line_details" ]]; then
error "Could not parse rule details from line: $rule_line_numeric"
fi
read -r proto local_port target_ip target_port <<< "$line_details"
# 1. PREROUTING ルールを番号で削除
if ! iptables -t nat -D PREROUTING "$num"; then
error "Failed to delete PREROUTING rule $num"
fi
# 2. 対応する OUTPUT ルールをすべて削除
while iptables -t nat -D OUTPUT -p "$proto" -m "$proto" --dport "$local_port" -j DNAT --to-destination "$target_ip:$target_port" >/dev/null 2>&1; do :; done
# 3. 対応する FORWARD ルールをすべて削除
while iptables -D FORWARD -p "$proto" -m "$proto" -d "$target_ip" --dport "$target_port" -j ACCEPT >/dev/null 2>&1; do :; done
# 4. 確立済み通信を許可するルールを削除 (存在確認後に削除)
if iptables -C FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
iptables -D FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT >/dev/null 2>&1
fi
if ! $QUIET; then
echo "Deleted rule $num: $rule_line_verbose"
fi
}
# ポートフォワーディングルールを追加
add_rule() {
# IPフォワーディングが有効かチェック
if [[ $(sysctl -n net.ipv4.ip_forward) -ne 1 ]]; then
error "IP forwarding is disabled. Please enable it by running: ipf -f"
fi
local container_env=$(is_container)
if [[ $container_env -eq 0 ]]; then
echo -e "\e[33mDetected container environment. MASQUERADE is enabled, which hides the original client IP address. This will prevent tools like fail2ban from correctly identifying and blocking malicious actors. Consider the security implications.\e[0m" # コンテナ環境であることを示すメッセージ
fi
# 確立済みの通信は許可する (戻りのパケットのため)
iptables -I FORWARD 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
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)
# IPアドレス検証
if ! [[ "$target_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
error "Invalid IP address: $target_ip"
fi
# ポート番号検証
if ! [[ "$local_port" =~ ^[0-9]+$ ]] || ! [[ "$target_port" =~ ^[0-9]+$ ]]; then
error "Invalid port number"
fi
if (( local_port < 1 || local_port > 65535 || target_port < 1 || target_port > 65535 )); then
error "Port number out of range (1-65535)"
fi
# iptablesでルールを追加
# 外部からのパケットを対象
iptables -t nat -A PREROUTING -p "$PROTO" -m "$PROTO" --dport "$local_port" -j DNAT --to-destination "$target_ip:$target_port"
# ローカルで生成されたパケットを対象
iptables -t nat -A OUTPUT -p "$PROTO" -m "$PROTO" --dport "$local_port" -j DNAT --to-destination "$target_ip:$target_port"
if [[ $container_env -eq 0 ]]; then
# コンテナ環境ではMASQUERADEを使用
iptables -t nat -A POSTROUTING -p "$PROTO" -m "$PROTO" -d "$target_ip" --dport "$target_port" -j MASQUERADE
fi
# 転送されるパケットを許可する
iptables -A FORWARD -p "$PROTO" -m "$PROTO" -d "$target_ip" --dport "$target_port" -j ACCEPT
if ! $QUIET; then
echo "Rule added: port $local_port -> $target_ip:$target_port (proto=$PROTO)"
fi
}
# ルールを編集して復元
edit_rules() {
local temp_file
temp_file=$(mktemp -t iptables.rules.XXXXXX) || error "Failed to create temporary file"
chmod 0600 "$temp_file"
trap 'rm -f "$temp_file"' EXIT
# 現在のルールを一時ファイルに保存
iptables-save > "$temp_file"
# エディタを決定
local editor=${EDITOR:-nano}
if ! command -v "$editor" > /dev/null; then
editor=vi
fi
# nanoで編集
if ! "$editor" "$temp_file"; then
error "Editor closed without saving or an error occurred."
fi
# 編集後の内容でリストア
if iptables-restore < "$temp_file"; then
echo "iptables rules restored successfully from your edits."
echo "--- Displaying new rules ---"
iptables-save
else
error "Failed to restore iptables rules. Please check for syntax errors in your edits."
fi
}
# 疎通確認用関数
test_rule() {
local arg=$1
local local_port target_ip target_port
if [[ "$arg" =~ ^[0-9]+$ ]]; then
# rule number → ルール行を取得
local line=$(iptables -t nat -L PREROUTING -n --line-numbers | grep "^$arg ")
if [[ -z "$line" ]]; then
error "No rule found with number: $arg"
fi
local_port=$(echo "$line" | awk '{print $8}'|cut -d ':' -f2)
target_ip=$(echo "$line" | awk -F: '{print $1}')
target_port=$(echo "$line" | awk -F: '{print $2}')
else
# 文字列形式
local_port=$(echo "$arg" | cut -d':' -f1)
target_ip=$(echo "$arg" | cut -d':' -f2)
target_port=$(echo "$arg" | cut -d':' -f3)
fi
echo "Testing connectivity to local port \"$(tput setaf 3)nc -z -w 5 127.0.0.1 $local_port$(tput sgr0)\""
if nc -z -w 5 127.0.0.1 "$local_port" 2>/dev/null; then
echo "$(tput setaf 6)Connection successful.$(tput sgr0)"
else
echo "$(tput setaf 1)Connection failed.$(tput sgr0)"
fi
}
# メイン処理
main() {
# オプション解析
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
exit 0
;;
-L|-l)
list_rules
exit 0
;;
-v)
iptables-save | grep -v '^#' | less -R
exit 0
;;
-e)
edit_rules
exit 0
;;
-f)
if sysctl -w net.ipv4.ip_forward=1 > /dev/null && \
sysctl -w net.ipv4.conf.all.route_localnet=1 > /dev/null && \
sysctl -w net.ipv4.conf.default.route_localnet=1 > /dev/null; then
echo "IP forwarding and localnet routing enabled."
else
error "Failed to enable kernel parameters."
fi
exit 0
;;
--version)
show_version
exit 0
;;
-d)
if [[ $# -lt 2 ]]; then
error "Missing rule number for -d option"
fi
delete_rule "$2"
exit 0
;;
-t)
if [[ $# -lt 2 ]]; then
error "Missing argument for -t option"
fi
test_rule "$2"
exit 0
;;
-q)
QUIET=true
shift
;;
-p)
if [[ $# -lt 2 ]]; then
error "Missing protocol after -p"
fi
if [[ "$2" != "tcp" && "$2" != "udp" ]]; then
error "Unsupported protocol: $2"
fi
PROTO="$2"
shift 2
;;
-*)
error "Unknown option: $1"
;;
*)
break
;;
esac
done
# 引数が残っていれば、ルール追加処理
if [[ $# -eq 0 ]]; then
show_help
exit 1
fi
local 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 or -L or -d RULE_NUMBER"
fi
}
# 実行
main "$@"