はじめに
iptablesがよくわかっていないので勉強した時のメモ
学習教材:
https://straypenguin.winfield-net.com/iptables.html
構造
テーブル(filter,nat,mangle)
組み込まれているチェーンは、テーブルの種類によって異なる。
チェーン(PREROUTING, INPUT, OUTPUT, FORWARD, POSTROUTINGおよびユーザ定義チェーン)
ルールのまとまりによるフィルタリングアルゴリズム。
ルール(ACCEPT, DROP, REJECT, LOG, RETURN, TOS, TTL, MARK, DNAT, SNAT, MASQUERADE, QUEUE, REDIRECTなど)
「パケットがこれこれだったら、そいつをこう操作せよ」 という指示。設定できるルールはチェーンによって異なる。例えばDNATは、natテーブルPREROUTINGチェーンなどに登録する
主なコマンド
ルール操作
-t table: テーブルを指定。デフォルトではfilterテーブル
-A chain rule: chainにruleをappend
-D chain rule: chainのruleをdelete。numberでも指定可能
-I chain rule_number rule: chainにruleをnumber位置にinsert。rule_numberのデフォルト値は0。
-R chain rule_number new_rule: rule_numberの位置のruleをnew_ruleでreplace
チェーン操作
-N new_chain_name: New。チェーンを作成(ex. ip6tables -t filter -N new_chain_name
)
-P chain policy: chainの基本Policyをセット。ルールでドロップして残りは受け入れる (ACCEPT) か、ルールでアクセプトして残りを拒否する (DROP) かのいずれか。ユーザ定義チェーンには使えない
[-t table] -X [chain]: 指定したchain(ユーザ定義に限る)を削除
[-t table] -L [chain] [options]: chainにあるruleをリスト出力
実験
ip6tables -t filter -L などで、確かにChainが出てきた。
ip6tables -t filter -N my_new_chain で、chainを追加できた
ip6tables -t filter -A my_new_chain で、ruleを追加できた(こんなに空っぽでも追加できるのか)
また、Ubuntuで、
iptables -t filter -A INPUT -p tcp --dport 8080 -j DROP
を実行したら、ちゃんとLANの別のマシンから8080へアクセスしようとしたら、アクセスできなかった。
さらに、iptables -t filter -L -v
で、パケット数を見てみたら、LANの別のマシンからアクセスするたびに増えていっていた。
なお、変更は基本的にすべて、再起動で消えるらしい。また、SSH先のUbuntuで作業している場合(私の場合はそう)、うっかり閉め出されるといけないので、作業している時は、
(sleep 300 && iptables -F) & などをしておくと安全らしい(SSH先のUbuntuは私の場合は、別室にあるので、マシンのある部屋まで行って直接再起動すればよいので不要そう)
sudo iptables -A OUTPUT -p udp --dport 53 -j DROP
したら、ちゃんとDNS解決ができなくなった。
sudo ip6tables -t filter -L | grep Chain
Chain INPUT (policy ACCEPT)
Chain FORWARD (policy DROP)
Chain OUTPUT (policy ACCEPT)
Chain DOCKER (1 references)
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
Chain DOCKER-USER (1 references)
sudo ip6tables -t nat -L | grep Chain
Chain PREROUTING (policy ACCEPT)
Chain INPUT (policy ACCEPT)
Chain OUTPUT (policy ACCEPT)
Chain POSTROUTING (policy ACCEPT)
Chain DOCKER (2 references)
sudo ip6tables -t mangle -L | grep Chain
Chain PREROUTING (policy ACCEPT)
Chain INPUT (policy ACCEPT)
Chain FORWARD (policy ACCEPT)
Chain OUTPUT (policy ACCEPT)
Chain POSTROUTING (policy ACCEPT)
Dockerと名前の入ったもの以外はデフォルトの模様。
ルール操作
条件 -j 動作または他のユーザ定義チェーン[オプション]
"!" を挟むと意味を反転 (否定) できる。
-p [!] protocol tcp, udp, icmp, all のいずれか。all は「ありとあらゆるプロトコル」ではなく、tcp, udp, icmp を指す。指定無しは all と同じ
-s [!] address[/mask] 送信元アドレス。ネットマスクは4オクテット書式、CIDR書式とも可
-d [!] address[/mask] 宛先アドレス
-i [!] interface[+] 入ってくるインターフェイス。eth+ などとすると eth0, eth1.. .にマッチ
-o [!] interface[+] 出て行くインターフェイス
動作(ジャンプターゲット)の主なもの
ACCEPT: 受け入れる
DROP: 黙って捨てる
テーブルとチェーンの仕様
filterテーブル
INPUT, FORWARDチェーン
どちらもPCに到達してくるパケットが該当。
INPUTは、そのPCが終端のパケット。
FORWARDは、つぎのデバイスへ転送する必要があるパケット。
OUTPUTチェーン
そのPCで発生して、外へ出ていくパケット。なお、FORWARDの結果、次のマシンへ転送することになった場合、一見そのPCから出ていっているようにも見えるが、この場合はOUTPUTチェーンは利用されない(あくまでOUTPUTは、そのPCが作成したパケットのみに対応する)。
コラム(静的ルーティングCRUD)
どのIPアドレスは、どの
create
ip route add /<サブネットマスク> [dev <デバイス名>] via <デフォルトゲートウェイ>
例: ip route add 172.17.0.0/16 dev eth0 via 192.168.1.70
read
ip route
update
不可能。消して作るしかないらしい
delete
ip route delete 172.17.0.0/16 dev eth0 via 192.168.1.70
delete 以外については、条件文となるもよう。
ただ、viaは無視されているかも。
NATテーブル
PREROUTING、OUTPUT、POSTROUTINGチェーン
PREROUTING、POSTROUTINGは、外から来たパケットに関するチェーン。
PREROUTINGはルーティングの前、POSTROUTINGはルーティングの後。
すなわち、PREROUTING、ルーティング、POSTROUTINGの順番。
具体的な用途としては、
- PREROUTING
宛先NAT(DNAT) - POSTROUTING
送信元NAT(SNAT)、マスカレード
OUTPUTは、自分のマシンが生成したパケットに関するチェーン。
具体的な用途としては、
- OUTPUT
ローカルでの宛先NAT(DNAT)
例:ルーターに設定して、内部のネットワークからのパケットのL3ヘッダのソースIPアドレスを、ルーターが送信する送信元インターフェースのIPアドレスに変換する(NATゲートウェイ)。
# 内部ネットワーク (192.168.1.0/24) を外部ネットワークにNATする
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE
例:外部からの特定のサービスへのリクエストを内部サーバへ転送する(ポートフォワード)
# 外部ポート80のリクエストを内部サーバ192.168.1.100:80に転送
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80
追記メモ
[受信インターフェースでパケット受信]
↓
raw:PREROUTING (conntrack前、最初のhook)
↓
mangle:PREROUTING (TTL, TOS, MARKなど)
↓
nat:PREROUTING (DNATなど:宛先IPの書き換え)
↓
[routing decision] (宛先が自分か、別ホストか判断)
┌──────────────┬─────────────┐
│ │ │
▼ ▼ ▼
mangle:INPUT mangle:FORWARD filter:FORWARD
▼ ▼ ▼
filter:INPUT ─────────────→ security:FORWARD
▼
security:INPUT
▼
[ローカルプロセスへ]
[ローカルプロセスがパケット送信]
↓
raw:OUTPUT
↓
mangle:OUTPUT
↓
nat:OUTPUT (ローカル発生パケットのNAT)
↓
[routing decision] (どのデバイス・ゲートウェイに送るか)
↓
filter:OUTPUT
↓
security:OUTPUT
↓
mangle:POSTROUTING
↓
nat:POSTROUTING (SNAT, MASQUERADEなど:送信元IP変更)
↓
[インターフェースから送信]
実際の例:外部からSSH接続が来る場合
eth0で受信
↓
raw:PREROUTING → mangle:PREROUTING → nat:PREROUTING
↓
[routing:宛先が自ホストと判断]
↓
mangle:INPUT → filter:INPUT → security:INPUT
↓
ACCEPTされたら、アプリケーション(sshd)に到達
VPNの場合
[ユーザープロセス]
↓
[raw:OUTPUT] ← ここでは未加工の TCP パケットが見える
↓
[mangle:OUTPUT] ← MARK 等が設定されることがある
↓
[nat:OUTPUT] ← DNAT (※ローカル発通信にはあまり使われない)
↓
[routing decision] ← ここで dev=nordlynx に出すと決定
↓
[nordlynx に渡す] ← ※ここで WireGuard が処理を開始
↓─────────────────────────────┐
[WireGuard カプセル化処理] │
- 暗号化 │
- UDPヘッダ追加(例: dport:41641) │
- dip を VPN サーバの IP に変更 │
↓ │
[カーネル送信パスに再注入] │
↓ │
[mangle:POSTROUTING] │← ここでは既に UDP/41641
↓ │
[nat:POSTROUTING] │← SNAT/MASQUERADE が発動する可能性あり
↓ │
[物理NICから送信 (eg. eth0)] ←───────┘