Compare commits

..

No commits in common. "28da4e27099dbf01dc3e73ac4d256302ef17abed" and "fb391b9d7da1f38899d9e2b082a025f3443fa34e" have entirely different histories.

5 changed files with 411 additions and 496 deletions

View file

@ -1,41 +1,37 @@
このプロジェクトは、nftablesのルールを編集するためのスクリプトです。以下が使用方法とライセンス情報です。 このプロジェクトは、iptablesのルールを編集するためのスクリプトです。以下が使用方法とライセンス情報です。
--- ---
### 概要 ### 概要
このツールは、`nftables`のルールを編集する際のスクリプトです。 このツールは、`iptables`のルールを編集する際のスクリプトであり、一時ファイルを作成し、エディタでルールを編集する処理を含みます。
--- ---
### 使用方法 ### 使用方法
**ルールの編集** 1. **ルールの編集**
```bash ```bash
Usage: ipfn [OPTIONS] [RULES] ipf 11434:10.1.1.2:11434 # Forward local port 11434 to 10.1.1.2:11434
ipf -L # List all rules with numbers
Rules Format: ipf -d 1 # Delete rule number 1
80:10.10.100.5:8080 Full (LocalPort:TargetIP:TargetPort) ipf -d 1 -q # Delete rule number 1 (quiet mode)
80:8080 IP defaults to 127.0.0.1 ipf -v # Show current iptables rules by iptables-save
11434 Map same port to 127.0.0.1 ipf -e # Edit all rules in editor and restore
Options:
-l, -L List all rules
-d HANDLE/:PORT/all Delete specific rules or '*' for all
-R Reset: Clear ALL rules immediately
-q Quiet mode (No output, Auto-yes)
-t [HANDLE] Test connectivity
-f Enable IP forward & Bridge tuning / Skip test
-v Verbose (raw nftables output)
-h Show this help
``` ```
ipf -e で一時ファイルが作成され、エディタ(例: `nano`)でルールを編集できます。編集が完了した後、一時ファイルは自動的に削除されます [1]。
2. **バージョン情報の表示**
```bash
ipf --version # Show version information
```
これにより、ソフトウェアのバージョンとライセンス情報が表示されます [1]。
--- ---
### ライセンス ### ライセンス
MIT License に基づくライセンスです。 MIT License に基づくライセンスです。
**権利者**: krasherjoe **権利者**: krasherjoe
**日付**: 2026-01-22 **日付**: 2025-10-26
--- ---

View file

@ -1,68 +0,0 @@
#!/usr/bin/env python3
import sys, tty, termios, subprocess, re, time
# [2026-01-16] ipf Multi-Brain Cockpit by Gemini3.0flash
# OS: Linuxmintmate 22.1 / Server: Proxmo\x
def get_key():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def get_status(targets):
"""ipf -l の実体から現在の接続先を特定してラベルを返す"""
try:
res = subprocess.run(["ipf", "-l"], capture_output=True, text=True)
for line in res.stdout.splitlines():
if "11434" in line:
for key, label, port in targets:
if port in line:
return f"\033[1;32m[{label}]\033[0m"
return "\033[1;31m[UNKNOWN]\033[0m"
except: return "???"
def safe_switch(target_port):
"""iptables/ipfのインデックスを掃除して張り直す"""
while True:
res = subprocess.run(["ipf", "-l"], capture_output=True, text=True)
idx = next((re.search(r'^(\d+):', l).group(1) for l in res.stdout.splitlines() if "11434" in l), None)
if not idx: break
subprocess.run(["ipf", "-d", idx], stdout=subprocess.DEVNULL)
time.sleep(0.05)
subprocess.run(["ipf", f"11434:127.0.0.1:{target_port}"], stdout=subprocess.DEVNULL)
def main():
# 引数から設定を動的に生成 (形式: key:label:port)
# 例: 1:3b:11431 2:7b:11432
if len(sys.argv) < 2:
print("Usage: cockpit.py <key:label:port> ...")
sys.exit(1)
targets = [arg.split(':') for arg in sys.argv[1:]]
mapping = {t[0]: (t[1], t[2]) for t in targets}
# 画面を1行に固定してスタイリッシュに表示
try:
while True:
cur_label = get_status(targets)
menu = " ".join([f"{k}:{l}" for k, l, p in targets])
# 計器のようなデザイン
sys.stdout.write(f"\r\033[K \033[1;34mBRAIN_CTRL\033[0m >> {menu} | \033[1;35mACTIVE\033[0m >> {cur_label} ")
sys.stdout.flush()
key = get_key()
if key in mapping:
safe_switch(mapping[key][1])
elif key == 'q':
break
except KeyboardInterrupt: pass
print("\n")
if __name__ == "__main__":
main()

320
ipf
View file

@ -1,320 +0,0 @@
#!/bin/bash
# Name: ipf
# Version: 1.5.9
# Date: 2026-01-22
# Description: Full feature set. Includes conflict prevention, fixed -t parsing, and -f logic.
# Root check
if [ "$(id -u)" -ne 0 ]; then
exec sudo "$0" "$@"
fi
# Global Variables
TABLE_NAME="ipf"
VERSION="1.5.9"
PROTO="tcp"
FORCE_FLAG=false
SKIP_TEST=false
QUIET_MODE=false
RESET_MODE=false
# --- 1. Core Functions ---
msg() {
[[ "$QUIET_MODE" == false ]] && echo -e "$@"
}
enable_forwarding() {
msg "\e[34m[System]\e[0m Enabling IP forwarding and localnet routing..."
sysctl -w net.ipv4.ip_forward=1 >/dev/null
# Enable route_localnet for all interfaces to allow forwarding to 127.0.0.1
for dev in /proc/sys/net/ipv4/conf/*/route_localnet; do
echo 1 > "$dev" 2>/dev/null
done
}
init_nft() {
nft add table inet "${TABLE_NAME}" 2>/dev/null
nft add chain inet "${TABLE_NAME}" prerouting { type nat hook prerouting priority -100 \; } 2>/dev/null
nft add chain inet "${TABLE_NAME}" output { type nat hook output priority -100 \; } 2>/dev/null
nft add chain inet "${TABLE_NAME}" postrouting { type nat hook postrouting priority 100 \; } 2>/dev/null
nft add chain inet "${TABLE_NAME}" forward { type filter hook forward priority 0 \; policy accept \; } 2>/dev/null
}
get_total_packets() {
local uuid=$1
local total=0
local counts=$(nft list table inet "${TABLE_NAME}" 2>/dev/null | grep "$uuid" | grep -o 'packets [0-9]*' | awk '{print $2}')
for c in $counts; do
total=$((total + c))
done
echo "$total"
}
test_connection() {
# Crucial: Argument order for nc (options first, then target)
nc -z -w 1 "$1" "$2" >/dev/null 2>&1
return $?
}
delete_by_handle() {
local h=$1
# Find UUID associated with this handle
local uuid=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "handle $h" | grep -o 'ipf-id:[a-z0-9-]*')
if [[ -z "$uuid" ]]; then
return 1
fi
# Delete rules in all chains that share this UUID
local chains=("prerouting" "output" "forward" "postrouting")
for c in "${chains[@]}"; do
nft -a list chain inet "${TABLE_NAME}" "$c" 2>/dev/null | grep "$uuid" | grep -o 'handle [0-9]*' | awk '{print $2}' | while read -r rh; do
nft delete rule inet "${TABLE_NAME}" "$c" handle "$rh"
done
done
return 0
}
test_strict_handle() {
local h=$1
local info=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "handle $h")
if [[ -z "$info" ]]; then
echo -e "Handle $h \e[31mNOT FOUND\e[0m"
return
fi
local lp=$(echo "$info" | grep -o 'dport [0-9]*' | awk '{print $2}')
local target=$(echo "$info" | grep -oE 'to ([0-9.]+|\[[0-9a-fA-F:]+\]):[0-9]+' | awk '{print $2}')
local tip=$(echo "$target" | cut -d':' -f1 | tr -d '[]')
local tp=$(echo "$target" | cut -d':' -f2)
local uuid=$(echo "$info" | grep -o 'ipf-id:[a-z0-9-]*')
local short_id=$(echo "$uuid" | cut -d':' -f2 | cut -c1-8)
echo -n "Testing handle $h (Local :$lp -> Target $tip:$tp) [${short_id}]... "
local count_before=$(get_total_packets "$uuid")
if test_connection "$tip" "$tp"; then
# Actual trigger test via localhost
nc -z -w 1 127.0.0.1 "$lp" >/dev/null 2>&1
sleep 0.1
local count_after=$(get_total_packets "$uuid")
if (( count_after > count_before )); then
echo -e "\e[32mPASSED\e[0m"
else
echo -e "\e[33mSKIPPED (Check other rules)\e[0m"
fi
else
echo -e "\e[31mOFFLINE (Target Down)\e[0m"
fi
}
list_rules() {
[[ "$QUIET_MODE" == true ]] && return
local highlight_uuid=$1
init_nft
msg "Forwarding Rules (ipf):"
printf "%-10s %-6s %-20s %-20s %-10s\n" "HANDLE" "PROTO" "LOCAL" "TARGET" "UUID"
echo "-----------------------------------------------------------------------------------"
nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "dnat" | while read -r line; do
local handle=$(echo "$line" | grep -o 'handle [0-9]*' | awk '{print $2}')
local proto=$(echo "$line" | grep -q "udp" && echo "udp" || echo "tcp")
local target=$(echo "$line" | grep -oE '([0-9.]+|\[[0-9a-fA-F:]+\]):[0-9]+' | head -n 1)
local lport=$(echo "$line" | grep -o 'dport [0-9]*' | awk '{print $2}')
local full_uuid=$(echo "$line" | grep -o 'ipf-id:[a-z0-9-]*' | cut -d':' -f2)
if [[ -n "$highlight_uuid" && "$full_uuid" == "$highlight_uuid" ]]; then
printf "\e[1;36m*%-9s %-6s %-20s %-20s %-10s\e[0m\n" "$handle" "$proto" ":$lport" "$target" "${full_uuid:0:8}"
else
printf " %-9s %-6s %-20s %-20s %-10s\n" "$handle" "$proto" ":$lport" "$target" "${full_uuid:0:8}"
fi
done
}
add_rule() {
local raw=$1
init_nft
[[ "$FORCE_FLAG" == true ]] && enable_forwarding
# Parsing
if [[ "$raw" =~ ^([0-9]+):([0-9\.]+):([0-9]+)$ ]]; then
lp=${BASH_REMATCH[1]}; tip=${BASH_REMATCH[2]}; tp=${BASH_REMATCH[3]}
elif [[ "$raw" =~ ^([0-9]+):([0-9]+)$ ]]; then
lp=${BASH_REMATCH[1]}; tip="127.0.0.1"; tp=${BASH_REMATCH[2]}
elif [[ "$raw" =~ ^([0-9]+)$ ]]; then
lp=${BASH_REMATCH[1]}; tip="127.0.0.1"; tp=${BASH_REMATCH[1]}
else
msg "\e[31mError: Invalid rule format '$raw'\e[0m"
return 1
fi
# --- Conflict Management ---
# Delete any existing rules using the same local port to prevent shadowing
local existing_h=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "dport $lp" | grep -o 'handle [0-9]*' | awk '{print $2}')
for eh in $existing_h; do
delete_by_handle "$eh"
done
# UUID Generation
local u_raw=$(cat /proc/sys/kernel/random/uuid)
local u="ipf-id:$u_raw"
local fam="ip"
[[ "$tip" =~ : ]] && fam="ip6"
# Rule Insertion (using insert to stay at the top)
nft insert rule inet "${TABLE_NAME}" prerouting "$PROTO" dport "$lp" counter dnat $fam to "$tip:$tp" comment "\"$u\""
nft insert rule inet "${TABLE_NAME}" output "$PROTO" dport "$lp" counter dnat $fam to "$tip:$tp" comment "\"$u\""
nft insert rule inet "${TABLE_NAME}" forward $fam daddr "$tip" "$PROTO" dport "$tp" ct state new,established,related accept comment "\"$u\""
nft insert rule inet "${TABLE_NAME}" forward $fam saddr "$tip" "$PROTO" sport "$tp" ct state established,related accept comment "\"$u\""
nft insert rule inet "${TABLE_NAME}" forward iifname "lo" accept comment "\"$u\""
nft insert rule inet "${TABLE_NAME}" postrouting $fam daddr "$tip" "$PROTO" dport "$tp" masquerade comment "\"$u\""
list_rules "$u_raw"
# Auto-test unless forced
if [[ "$SKIP_TEST" == false && "$FORCE_FLAG" == false ]]; then
local new_h=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "$u_raw" | grep -o 'handle [0-9]*' | awk '{print $2}')
echo ""
test_strict_handle "$new_h"
fi
}
all_clear() {
if [[ "$FORCE_FLAG" == false ]]; then
echo -e "\e[33mWarning: This will delete ALL ipf rules.\e[0m"
read -p "Are you sure? (y/N): " confirm
[[ ! "$confirm" =~ ^[yY]$ ]] && exit 0
fi
nft delete table inet "${TABLE_NAME}" 2>/dev/null
init_nft
msg "All ipf rules cleared."
list_rules
exit 0
}
show_help() {
echo "ipf version ${VERSION}"
echo "Usage: ipf [OPTIONS] [RULE]"
echo ""
echo "Options:"
echo " -f Kernel: Enable forwarding & route_localnet"
echo " Global: Acts as 'Force' (Skips confirmation & tests)"
echo " -l, -L List all forwarding rules"
echo " -d HANDLE/:PORT/all Delete rule (Add -f to skip confirmation)"
echo " -R Reset: Clear ALL rules (Add -f to skip confirmation)"
echo " -t [TARGET] Test connectivity (Handle, :Port, or IP:Port)"
echo " -y Same as -f (Force/Yes)"
echo " -q Quiet mode (implies -f)"
echo " -v, --version Show version"
echo " -h, --help Show this help message"
}
# --- 2. Flag Pre-processing ---
for arg in "$@"; do
if [[ "$arg" =~ q ]]; then QUIET_MODE=true; FORCE_FLAG=true; SKIP_TEST=true; fi
if [[ "$arg" =~ f ]]; then FORCE_FLAG=true; SKIP_TEST=true; fi
if [[ "$arg" =~ y ]]; then FORCE_FLAG=true; SKIP_TEST=true; fi
[[ "$arg" == "-R" ]] && RESET_MODE=true
done
if [[ "$RESET_MODE" == true ]]; then all_clear; fi
if [[ $# -eq 0 ]]; then list_rules; exit 0; fi
# --- 3. Main Loop ---
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) show_help; exit 0 ;;
-l|-L) list_rules; exit 0 ;;
-v|--version) echo "ipf version ${VERSION}"; exit 0 ;;
-f|-y|-q)
[[ "$1" == "-f" ]] && enable_forwarding
shift
[[ $# -eq 0 ]] && exit 0
continue
;;
-*[d]*)
# Handle combined flags like -fd or -qd
h_str=$(echo "$1" | sed -E 's/^-q?d?f?y?//')
if [[ -z "$h_str" ]]; then
h_str="$2"
shift
fi
if [[ "$h_str" == "all" || "$h_str" == "*" ]]; then
all_clear
fi
# Collect handles to delete
handles=()
IFS=',' read -r -a parts <<< "$h_str"
for part in "${parts[@]}"; do
if [[ "$part" =~ ^:([0-9]+)$ ]]; then
p="${BASH_REMATCH[1]}"
# Find all handles for this local port
for h in $(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "dport $p" | grep -o 'handle [0-9]*' | awk '{print $2}'); do
handles+=("$h")
done
else
handles+=("$part")
fi
done
for h in "${handles[@]}"; do
if delete_by_handle "$h"; then
msg "Deleted handle $h"
else
msg "\e[31mError: Handle $h not found\e[0m"
fi
done
list_rules
exit 0
;;
-t*)
h_str="${1#-t}"
if [[ -z "$h_str" ]]; then
h_str="$2"
shift
fi
# Pattern matching for different test types
if [[ "$h_str" =~ ^([0-9\.]+):([0-9]+)$ ]]; then
# IP:PORT test
tip=${BASH_REMATCH[1]}; tp=${BASH_REMATCH[2]}
echo -n "Checking Target $tip:$tp... "
test_connection "$tip" "$tp" && echo -e "\e[32mUP\e[0m" || echo -e "\e[31mDOWN\e[0m"
elif [[ "$h_str" =~ ^:([0-9]+)$ ]]; then
# :PORT test (local)
p="${BASH_REMATCH[1]}"
echo -n "Checking Local :$p... "
nc -z -w 1 127.0.0.1 "$p" >/dev/null 2>&1 && echo -e "\e[32mOK\e[0m" || echo -e "\e[31mOFFLINE\e[0m"
elif [[ "$h_str" =~ ^[0-9]+$ ]]; then
# Handle test
test_strict_handle "$h_str"
else
# Default: test the first rule found
top_h=$(nft -a list chain inet "${TABLE_NAME}" prerouting 2>/dev/null | grep "dnat" | head -n 1 | grep -o 'handle [0-9]*' | awk '{print $2}')
if [[ -n "$top_h" ]]; then
test_strict_handle "$top_h"
else
msg "\e[31mError: No valid test target found for '$h_str'\e[0m"
fi
fi
exit 0
;;
[0-9]*)
# Positional argument as a rule
add_rule "$1"
exit 0
;;
*)
msg "\e[31mError: Unknown option '$1'\e[0m"
show_help
exit 1
;;
esac
shift
done

View file

@ -1,88 +0,0 @@
#!/usr/bin/env python3
# [2026-01-16] ipf Monitor & Switch Panel
# Client: Linuxmintmate 22.1 / Server: Proxmox
# Logic: Always show mapping from 'ipf -l' and switch with single key.
import subprocess
import sys
import tty
import termios
import re
import time
import threading
# 接続先の定義
BRAINS = {
'1': ('pve1-3B ', '11431'),
'2': ('pve2-7B ', '11432'),
'3': ('pve3-14B', '11433')
}
def get_key():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def get_mapping_status():
"""ipf -l から 11434 の現在の接続先を解析する"""
try:
res = subprocess.run(["ipf", "-l"], capture_output=True, text=True)
# 出力例 "1: 127.0.0.1:11434 -> 127.0.0.1:11432" を想定
for line in res.stdout.splitlines():
if "11434" in line:
# 矢印の先のポート番号を抽出
match = re.search(r'->.*?(\d+)$', line.strip())
if match:
port = match.group(1)
# 逆引きして名前を出す
for k, v in BRAINS.items():
if v[1] == port: return f"\033[1;32m{v[0]}\033[0m ({port})"
return f"\033[1;36mUnknown\033[0m ({port})"
except: pass
return "\033[1;31mDISCONNECTED\033[0m"
def safe_switch(target_port):
"""削除・確認・追加のシーケンス"""
while True:
res = subprocess.run(["ipf", "-l"], capture_output=True, text=True)
idx = None
for line in res.stdout.splitlines():
if "11434" in line:
m = re.search(r'^(\d+):', line.strip())
if m: idx = m.group(1); break
if not idx: break
subprocess.run(["ipf", "-d", idx], stdout=subprocess.DEVNULL)
time.sleep(0.05)
subprocess.run(["ipf", f"11434:127.0.0.1:{target_port}"], stdout=subprocess.DEVNULL)
def main():
print("\033[2J\033[H") # 画面クリア
try:
while True:
# 現在の状態を取得
current = get_mapping_status()
# 最小限の1行表示
sys.stdout.write(f"\r\033[K[1:3B 2:7B 3:14B] ACTIVE: {current} | Key? (q:quit)")
sys.stdout.flush()
# キー入力を待つ (非同期にするほどでもないので、短いタイムアウトで回すのもありですが、
# ひとまず「入力があったら更新」の形にします)
# 常に最新にしたい場合は、別のスレッドで get_mapping_status を回すと良いです
key = get_key()
if key in BRAINS:
safe_switch(BRAINS[key][1])
elif key == 'q':
print("\nClosed.")
break
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()

395
ipf1.0.4 Normal file
View file

@ -0,0 +1,395 @@
#!/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 "$@"