Compare commits

...

32 commits

Author SHA1 Message Date
joe
28da4e2709 大改編 1.5.9 2026-01-22 21:18:44 +09:00
joe
d31a72201f BUG取れたか? 2026-01-22 21:04:56 +09:00
joe
14a4ab7f41 -q -f があれば test skip する様に修正 1.5.3 2026-01-22 20:59:05 +09:00
joe
3d2b788372 BUG付き -q でテストスキップ無い 2026-01-22 20:53:02 +09:00
joe
f809ee5c16 ipf1.4.7 2026-01-22 20:30:32 +09:00
joe
c0f1226ec5 ipfnからipfに改名 1.4.7 2026-01-22 20:17:48 +09:00
joe
f287b90c2d Merge remote-tracking branch 'old-repo/master' 2026-01-22 19:50:09 +09:00
joe
44fc5aa6ca Rename ipfn to ipf 2026-01-22 19:42:05 +09:00
joe
00c5a46278 HELP復活 2026-01-22 17:23:37 +09:00
joe
38e518b6f0 -t 大幅拡張 2026-01-22 15:33:06 +09:00
joe
229f756994 BUGBUGBUG 2026-01-22 15:18:56 +09:00
joe
faae2cbde7 最終に近い筈 2026-01-22 14:51:43 +09:00
joe
a22bad0622 -d debug 2026-01-22 14:35:15 +09:00
joe
2fdc3890a3 -d debug 2026-01-22 14:33:04 +09:00
joe
6ba3d68021 -t 2026-01-22 14:29:53 +09:00
joe
d6fe942181 -t 拡張 2026-01-22 14:22:08 +09:00
joe
7de4ed82f2 "/" にファイルをアップロード 2026-01-22 05:00:39 +00:00
joe
5115961094 修正忘れ 2026-01-22 13:10:43 +09:00
joe
8cdccae6bf コンテナやVMに対応
なるべく生IP維持
2026-01-22 12:32:42 +09:00
joe
79d11fecf7 各種オプション評価部分BUG取り
-d63-69 と指定した際に、間の「サブグループのハンドル(64-68)」を無視して、ちゃんと転送設定の親玉である 63 と 69 だけを検出し、そこから芋づる式に関連ルールを消去できています。表示もスッキリして、ようやく「道具」として信頼できるレベルになった気がします。
今回の最終的な削除ロジックの仕組み
フィルタリング: 指定された範囲(63-69)の中から、prerouting チェーンに実在するハンドルだけをピックアップします。
UUIDの特定: 見つかったハンドルのコメント欄から固有の ipf-id を抜き出します。
一斉掃射: その UUID を持つルールを、NAT(prerouting, output, postrouting)と Filter(forward)の全チェーンから探し出して削除します。
さらに「調べる」際のおすすめテスト
もしお時間あれば、こんなパターンも試してみてください:
ポート指定削除: ./ipfn -d :11434 (ハンドル番号を調べずにポートで消せるか)
混合削除: ./ipfn -d :80,33 (ポートとハンドルを混ぜて消せるか)
静かな削除: ./ipfn -qd :80 (何も言わずに一瞬で消え去るか)
2026-01-22 12:10:38 +09:00
joe
df59b6f1c4 オプション指定BUG 2026-01-22 12:05:14 +09:00
joe
a1abd44d49 ポート番号で消す: ./ipfn -d :80
複数のポートを一気に消す: ./ipfn -d :80,:443,:8080
    ハンドルとポートを混ぜる: ./ipfn -d 17,:8080,20-22
    無言でポート削除: ./ipfn -qd :80
    ポート番号の頭に : (コロン) を付けるだけで、スクリプトが裏側で「ハンドル番号への変換」を行ってから削除を実行します。
2026-01-22 12:02:08 +09:00
joe
526f037bba スイッチBUG修正
修正後の挙動
    -t の進化:
        ルールが1つの場合: ./ipfn -t だけでそのルールの疎通確認をします。
        スペースなし: ./ipfn -t17 が動きます。
    スイッチ無視の解消:
        while ループ内の shift 処理を見直し、-f を含めたオプションが確実に評価されるようにしました。
    追加時の -f:
        ./ipfn 80:10.0.0.1:80 -f とすると、疎通確認をスキップして即座に終了します。
2026-01-22 11:51:01 +09:00
joe
ee122e4597 削除に範囲を使用可能に 2026-01-22 11:46:54 +09:00
joe
cf88bb30fd オプション無しでリスト 2026-01-22 11:45:51 +09:00
joe
8aeb72fefd ほぼ完成かな? 2026-01-22 11:41:20 +09:00
joe
1c6b087b0b -d 2026-01-22 11:30:55 +09:00
joe
534c488783 add highlight 2026-01-22 11:21:18 +09:00
joe
2dcac1f8e4 IPv6アドレスが渡された場合にも対応できるよう少し柔軟性を持たせた 2026-01-22 11:00:16 +09:00
joe
e38fcf5b3a 初期はここ 2026-01-22 10:50:27 +09:00
joe
d61b305520 ipfn1.0
ご提示いただいた `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` のテキスト編集機能など)がどうしても必要な場合は、ロジックを追加しますのでお知らせください。
2026-01-22 01:41:29 +00:00
joe
4f74845e8c Gemini3.0flashにアイディアを言ったら作ってくれたcockpit.py 2026-01-16 22:55:12 +09:00
5 changed files with 496 additions and 411 deletions

View file

@ -1,37 +1,41 @@
このプロジェクトは、iptablesのルールを編集するためのスクリプトです。以下が使用方法とライセンス情報です。
このプロジェクトは、nftablesのルールを編集するためのスクリプトです。以下が使用方法とライセンス情報です。
---
### 概要
このツールは、`iptables`のルールを編集する際のスクリプトであり、一時ファイルを作成し、エディタでルールを編集する処理を含みます。
このツールは、`nftables`のルールを編集する際のスクリプトです。
---
### 使用方法
1. **ルールの編集**
**ルールの編集**
```bash
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 -e で一時ファイルが作成され、エディタ(例: `nano`)でルールを編集できます。編集が完了した後、一時ファイルは自動的に削除されます [1]。
Usage: ipfn [OPTIONS] [RULES]
2. **バージョン情報の表示**
```bash
ipf --version # Show version information
Rules Format:
80:10.10.100.5:8080 Full (LocalPort:TargetIP:TargetPort)
80:8080 IP defaults to 127.0.0.1
11434 Map same port to 127.0.0.1
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
```
これにより、ソフトウェアのバージョンとライセンス情報が表示されます [1]。
---
### ライセンス
MIT License に基づくライセンスです。
**権利者**: krasherjoe
**日付**: 2025-10-26
**日付**: 2026-01-22
---

68
cockpit.py Normal file
View file

@ -0,0 +1,68 @@
#!/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 Executable file
View file

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

88
ipf-safe-control.py Normal file
View file

@ -0,0 +1,88 @@
#!/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
View file

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