iptablesの基礎、基本操作、D-NAT/S-NAT/フィルタの構成方法
概要
iptablesは、Linuxシステムでネットワークトラフィックを制御するための強力なツールです。本記事では、iptablesの基本概念、基本操作、DNAT(Destination Network Address Translation)、SNAT(Source Network Address Translation)について詳しく解説します。
目次
IPTablesの基礎
IPTablesは、パケットフィルタリングとNAT(ネットワークアドレス変換)を実現するためのコマンドラインツールです。Linuxカーネルのネットフィルターフレームワークを利用しており、ネットワークトラフィックの制御に使用されます。しかし、ルータなどのネットワーク機器の設定や考え方とは少し異なりLinux独特の考え方や動作があるため、内部の処理構造を捉えた上で設定することが重要です。
基本的な概念として、Chain
=>Table
=>Target
の順番に処理されます。このChain
とTable
は5つの固定で設置されているものがあります。もちろん、ユーザで追加の定義を行うことも可能です。
ネットワークから届いたパケットは、5つのChain
を経由して処理されていきます。(ここでは、XDP/eBPFなどの低レベルの割り込みは述べません)
他にもIptablesの内部処理を表現している図は沢山ありますので自分の理解に合うものを探してみるのも良いでしょう。
例:https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg
図中では、簡略化のためmangle
,raw
,security
のテーブル記載を省略しています。
テーブル
iptablesには5つのテーブルがあります。ほとんどの場合では、使用するテーブルはfilterとnatの2つだけです。他のテーブルは複数の判定が関わる複雑な設定になるため、使用する場合には慎重な検討が必要となります。
テーブル | 用途 | チェーン | 説明 |
---|---|---|---|
filter | デフォルトのテーブルでありフィルタリングに使用されます。 | INPUT、FORWARD、OUTPUT | すべてのパケットが必ず通過し、主にパケットの許可または拒否を決定します |
nat | ネットワークアドレス変換を行うためのテーブルです | PREROUTING、OUTPUT、POSTROUTING | パケットの送信元または宛先アドレスを変更するために使用されます。チェーンの位置が複数ありますが、PREROUTINGではD-NATを、POSTROUTINGでS-NATを実施します。これはRouting処理前に実施する必要があるかないかの理由でこのような処理位置となっています。 |
mangle | パケットの変更(マーキングや変更)を行うためのテーブルです | PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING | 特殊なパケット処理(TOSフィールドの変更、マーキングなど)に使用されます |
raw | パケットをトラッキングする前に設定を行うためのテーブルです | PREROUTING、OUTPUT | パケットトラッキングの無効化など、特定の処理を行うために使用されます |
security | SELinuxのポリシーに基づいてパケットを処理するためのテーブルで、それ以外で有用ではありません。 | INPUT、OUTPUT、FORWARD | セキュリティコンテキストの設定や変更に使用されます。通常は使用することはありません。 |
チェーン
テーブルはチェインで構成され、チェインは順番に並べられたルールのリストです。
チェーン | 用途 |
---|---|
INPUT | OSプロセスに入ってくるすべてのパケットを処理します |
FORWARD | 他のネットワークに転送されるパケットを処理します |
OUTPUT | システムから外に出ていくパケットを処理します |
PREROUTING | パケットがルーティングされる前に処理を行います |
POSTROUTING | パケットがルーティングされた後に処理を行います |
デフォルトのテーブルであるfilter
には3つの組み込みチェインが含まれます。 INPUT
, OUTPUT
, FORWARD
になり、それぞれ別々の場所でフィルターが処理されます。nat テーブルには PREROUTING
, POSTROUTING
, OUTPUT
チェインが含まれており、アドレスの変換タイプに応じて実施場所が異なります。例えば、Destination NATを実施したい場合にはルーチングの前に変換が必要なためREROUTING
で処理されます。また、Source NATを実施したい場合には自ノード発のパケットも含めてSourceの変換が必要になるため、POSTROUTING
にて処理されます。以降、他のテーブルの組み込みチェインの説明は man 8 iptables
を参照ください。
初期状態ではチェインにはルールが何も入っていません。基本的にはルールは空で、デフォルトポリシーは ACCEPT
に設定されており全てのパケットは制限なく通り抜けできます。ただし、FORWARD
についてはsystemctl
にて制限がデフォルトで入っているためデフォルトでは入ってきたパケットを他のNICへ転送することはできません。
もしセキュリティ的な課題からホワイトリスト運用を行いたい場合には、チェインのデフォルトポリシーをDROP
に設定しなおすことができます。デフォルトのポリシーは常にチェインの最後に適用されるため、チェインに存在する明示的なルールをが先に適用された結果、マッチしない場合には最後のデフォルトポリシーが適用されます。パケットは最終的にデフォルトポリシーに従いACCEPT
またはDROP
されます。
ルール
パケットを処理する時にはデフォルトのフィルターテーブルがまず参照されます。その中の記載ルールにパケットがマッチする場合には、アクションターゲットによって記載されたルールによりパケットが操作されます。ルールのマッチングルールには、複数の条件を指定でき、入力インターフェースやプロトコル、パケットの送信先ポートなどが指定できます。
ターゲットには、パケット処理がそのルールで止まってしまう(ターミネート)ものと、続行(非ターミネート)するタイプがあります。ターゲットの種類としては、標準組込、拡張、ユーザ定義の3種類あり、 -j
または --jump
オプションを使って指定します。拡張には、TTLの変更や、DSCPのマーキング、ULOGへの転送、MSS書き換えなど様々な内容が盛り込まれています。
参考に、組込みのものでよく使うものについて下記に挙げます。その他は、man 8 iptables-extensions
をご参照ください。
ターゲット | タイプ | ターミネート | 用途 |
---|---|---|---|
ACCEPT |
組込 | - | パケットを許可して次のチェインに転送します |
DROP |
組込 | ✅ | パケットを破棄して処理を終了します |
QUEUE |
組込 | - | ユーザ空間のキューに渡す(現在はNFQUEUE が使われる) |
RETURN |
組込 | - | ジャンプ先から元のチェインへ戻します |
REJECT |
拡張 | ✅ | マッチしたパケットの応答としてエラーパケット(ICMP)を送信し、パケットを破棄します |
LOG |
拡張 | - | マッチしたパケットをカーネルログに記録する(kern.log, dmsg) |
パケットが入力インターフェースから入り、出ていく(または破棄)されるまでの評価は基本的には上記のフロー図に従い5つのチェインとテーブルを通過して評価されます。まずは、パケットが入ってきたとき自ノード宛であればINPUT
に転送され、他ノード宛であればFORWARD
に転送されていきます。その中で標準の5つ以外のユーザ定義のチェインにジャンプされた時には、転送先のチェイン内での評価を行い、その中の処理でターミネートされなかった(ドロップなのではない)場合、もしくはどの条件にもマッチしなかった場合には元のチェインに戻りジャンプした次のルールから評価されます。
戻った場所がチェインの最終行だとすると、そのチェインのデフォルトポリシー(ACCEPT
,DROP
)が評価されて次のチェインへ移行します。
IPTablesの基本操作
ルールのリスト表示
現在のIPTablesルールを表示するには、以下のコマンドを使用します:
$ sudo iptables -L -v -n
このコマンドでは、デフォルトのテーブルのに表示されるため、natなどのテーブルは表示されません。つまり、filterテーブルを指定した時と同じとなります。オプションは**-L
(list):テーブル内のすべてのチェーンとそのルールをリスト、-v
(verbose)**:詳細な情報を表示する、-n
(numeric):ホスト名やサービス名を解決しないということになります。
$ sudo iptables -nvL -t filter
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
natなど別のテーブルを参照するためには-t
で指定します。
$ sudo iptables -nvL -t nat
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
515 37025 ts-postrouting all -- * * 0.0.0.0/0 0.0.0.0/0
Chain ts-postrouting (1 references)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 mark match 0x40000/0xff0000
ルールの追加
新しいルールを追加するには、-A
オプションを使用します:
$ sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
自ノード宛のTCP22(SSH)の通信を許可する内容になります。
追加方法として-A
:既存のルールの後に挿入するAPPENDにて指定しましたが、既存のルールの前に導入する-I
INSERT、置き換えを行う-R
REPLACEの方法があります。
ルールの削除
特定のルールを削除するには、-D
オプションを使用します:
$ sudo iptables -D INPUT -p tcp --dport 22 -j ACCEPT
複数のルールの中から確実に削除する方法としては行数を指定する方法もあります。デフォルトでは省略されていますが、実はルール毎に番号が振られていますので、--line-numbers
オプションをつけるとネットワーク機器のACLのように指定して削除することができます。
$ sudo iptables -nvL -t filter --line-numbers
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
2 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
$ sudo iptables -D INPUT 1
$ sudo iptables -nvL -t filter --line-numbers
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
iptables -D [CHAIN] [line-number]
を指定することで特定のルールを削除することができます。ルール削除後は自動的に番号がリナンバリングされるため、連続で行を削除する場合には注意が必要です。
ルールの初期化
すべてのルールを削除するには、-F
オプションを使用します:
$ sudo iptables -F
フィルターのみ使用している場合にはこれで良いのですが、完全に全てのルールを削除して、デフォルトに戻すためには全てのテーブルについての初期化が必要になります。-X
オプションはユーザ定義のルールを削除します。また、-P
の設定はデフォルトポリシーをACCEPT
に戻す指定をしています。
$ sudo iptables -F
$ sudo iptables -X
$ sudo iptables -t nat -F
$ sudo iptables -t nat -X
$ sudo iptables -P INPUT ACCEPT
$ sudo iptables -P FORWARD ACCEPT
$ sudo iptables -P OUTPUT ACCEPT
(他のテーブルも初期化する場合)
$ sudo iptables -t mangle -F
$ sudo iptables -t mangle -X
$ sudo iptables -t raw -F
$ sudo iptables -t raw -X
$ sudo iptables -t security -F
$ sudo iptables -t security -X
ルールの保存・リストア
現在のルールのフィルタリストを全て表示されるためには-S
オプションを使用します。
$ sudo iptables -S -t filter
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N ts-forward
-N ts-input
-A INPUT -j ts-input
-A FORWARD -j ts-forward
-A ts-forward -i tailscale0 -j MARK --set-xmark 0x40000/0xff0000
コマンドラインでルールを追加した場合はメモリ上で保存されていますのでリロードすると全て削除されてしまいます。永続化するためには、下記のコマンドでファイルに書き出して起動時に読み込む方法があります。
$ sudo iptables-save > /etc/iptables/iptables.rules
直接ruleファイルを編集した場合には、再起動するか iptables を使って直接ロードすることもできます
$ sudo iptables-restore < /etc/iptables/iptables.rules
実際の設定例
DNATの設定方法
DNAT(Destination Network Address Translation)は、受信パケットの宛先アドレスを変更するために使用されます。例えば、外部からのトラフィックを内部サーバーにリダイレクトする場合です。
以下は、外部のポート80へのトラフィックを内部サーバーのポート8080にリダイレクトする例です:
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.2:8080
SNATの設定方法
SNAT(Source Network Address Translation)は、送信パケットの送信元アドレスを変更するために使用されます。通常、内部ネットワークのマシンがインターネットにアクセスする際に使用されます。
以下は、内部ネットワーク192.168.1.0/24からのトラフィックを外部IPアドレスに変換する例です。
$ sudo iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.1
上記の場合でインターフェースに割り振られるアドレスをソースに使用する場合には、ソースポートがOSや他のプロセスにより使用済みの場合もありえます。その場合はエフェメラルポートが自動的に選択されて重複は回避されます。ソースアドレスがインターフェースにバインディングされていない場合には問題ないですが、バインディングされていてソースポートの使い方自体もダイナミックに選択される場合には重複を想定したPATの方が良いでしょう。通常の使い方では不足することはないですが、大量のセッションを扱うようなケースではエフェメラルポートの範囲を検討した方が良いでしょう。
$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999 (エフェメラルポートの範囲)
PAT( Masquerade)を行いたい場合には拡張ターゲットのMASQUERADE
を指定します。
$ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
自ノード向けIPフィルタの設定方法
自ノード宛のパケットについて、特定のIP帯域のアドレスのみを許可するような構成を作るためにはINPUTチェインでのフィルタが使われます。例えば、192.168.1.0/24というレンジを許可する場合、このレンジ内の全てのIPアドレスがサーバのウェブサービス(TCP:80)にアクセスできるようになります。
$ sudo iptables -A INPUT -p tcp -m tcp --dport 80 -s 192.168.1.0/24 -j ACCEPT
-A INPUT
オプションでINPUTチェインにルールを追加し、-p tcp
でTCPプロトコルを指定、--dport
で特定のポート番号(この場合は80)を指定しています。また、-s
オプションで許可するIPレンジを指定し、-j ACCEPT
でこれらの条件に一致するトラフィックを許可します。
iptablesでは、192.168.10.10-20/24
のように、特定のIPレンジ(開始IPと終了IPを指定した範囲)を直接指定することはできません。もし、192.168.10.10
から 192.168.10.20
までの範囲を許可したい場合は、個別にルールを複数行で追加するか、iprange
拡張モジュールを使用して範囲を指定することができます。
$ sudo iptables -A INPUT -p tcp -m tcp --dport 80 -m iprange --src-range 192.168.10.10-192.168.10.20 -j ACCEPT
この例では、-m iprange
オプションを使用して、--src-range
でIP範囲を指定しています。
上記、2つの例では許可する設定をご紹介しましたがINPUTチェインにおいては追加で2つの考慮事項があります。INPUTチェインはサーバをコントロールするためのsshやsyslogなどのパケットも通過しているということです。また、INPUTチェインのデフォルトポリシーはACCEPT
だということです。そのため、これらとの干渉を考慮して設定が必要です。デフォルトポリシーACCEPT
を維持した状態で、特定場所からのアクセスのみ許可する要件であるならば許可とともに、拒否も明示的に指定する必要があります。
$ sudo iptables -nvL -t filter --line-numbers
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 source IP range 192.168.10.10-192.168.10.20
2 0 0 ACCEPT tcp -- * * 192.168.1.0/24 0.0.0.0/0 tcp dpt:80
3 0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
対して、デフォルトポリシーをDROP
に変更するのであれば、ホスト上で動作している他のサービスに対するアクセスも明示的に許可する設定を追加する必要がありますのでご注意ください。
ルールの最後には暗黙の動作として、デフォルトポリシーが適用されることを忘れないようにしましょう。
参考:
モダンなLinuxディストリビューションでは、表向きにはiptables
を使っているように見えても、実際にはその背後でnftables
が動作していることが多く、両者は互換性を持っています。これにより、iptables
を使い慣れた人でも安心して従来通りのコマンドやルールを使用できます。
iptables
コマンドを使用する際、nftables
の新機能や改善されたパフォーマンスが自動的に適用されるため、特別な設定を行わなくても最新の技術を活用できます。たとえば、iptables-legacy
は古いバージョンのiptables
ですが、ほとんどの場合、最新のLinuxカーネルではnftables
ベースのモードがデフォルトで有効になっており、従来のiptables
と同じ感覚で使用しながら、より柔軟で高性能なネットワークフィルタリングが可能です。
一部のミドルウェアがnftables
に対応していない場合がありますが、そのような場合でもiptables-legacy
モードを選択することで対応できます。iptables-legacy
に切り替えると、nftables
の新機能(例:より柔軟なマッチング、パフォーマンス向上、より複雑なルールセットのサポートなど)を使用できなくなることがあります。
$ sudo update-alternatives --config iptables
There are 2 choices for the alternative iptables (providing /usr/sbin/iptables).
Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/sbin/iptables-nft 20 auto mode
1 /usr/sbin/iptables-legacy 10 manual mode
2 /usr/sbin/iptables-nft 20 manual mode
また、現在のモードに従ってシンボリックリンクが変更されることになります。状態は下記でも確認することができます。
$ ls -la /etc/alternatives | grep ipta
lrwxrwxrwx 1 root root 22 Aug 10 16:03 iptables -> /usr/sbin/iptables-nft
lrwxrwxrwx 1 root root 30 Aug 10 16:03 iptables-restore -> /usr/sbin/iptables-nft-restore
lrwxrwxrwx 1 root root 27 Aug 10 16:03 iptables-save -> /usr/sbin/iptables-nft-save
ネットワーク構成例
この例では、LinuxのNetwork Namespaceを利用して3つの仮想ネットワーク環境(NA1、NS2、NS3)を作成し、それらを連携させることで、NA1からNS3への通信を通すネットワーク構成を構築します。さらに、NS3上でPythonのWebサーバーを起動し、NS2にてiptablesを使用して、DNATおよびMASQUERADEの設定を通してNS1からcURLでアクセスしてきたパケットをNS3で起動しているリアルサーバにリダイレクトします。
+-------------+ +-------------+ +-------------+
| NS1 | | NS2 | | NS3 |
| | | | | |
| | | | | |
| Curl | | NAT-RT | | WebSV |
| Client | 10.0.0.0/24 | | 192.168.0.0/24| |
| veth1|.1 .2|veth2 veth3 |.2 .1|veth4 |
| +-------------------+ +----------------+ |
| | |DNAT Masq| | |
| | | | | |
+-------------+ +-------------+ +-------------+
ネットワークの作成
# NSを作成
$ sudo ip netns add NS1
$ sudo ip netns add NS2
$ sudo ip netns add NS3
# veth pairを作成
$ sudo ip link add veth1 type veth peer name veth2
$ sudo ip link add veth3 type veth peer name veth4
# 仮想インターフェイスを各ネームスペースに割り当て
$ sudo ip link set veth1 netns NS1
$ sudo ip link set veth2 netns NS2
$ sudo ip link set veth3 netns NS2
$ sudo ip link set veth4 netns NS3
#L3の設定
# NS1
$ sudo ip netns exec NS1 ip addr add 10.0.0.1/24 dev veth1
$ sudo ip netns exec NS1 ip link set veth1 up
# NS2
$ sudo ip netns exec NS2 ip addr add 10.0.0.2/24 dev veth2
$ sudo ip netns exec NS2 ip addr add 192.168.0.1/24 dev veth3
$ sudo ip netns exec NS2 ip link set veth2 up
$ sudo ip netns exec NS2 ip link set veth3 up
# NS3
$ sudo ip netns exec NS3 ip addr add 192.168.0.2/24 dev veth4
$ sudo ip netns exec NS3 ip link set veth4 up
#FORWARDを有効化
$ sudo ip netns exec NS2 sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
#疎通確認
$ sudo ip -all netns exec ip -4 -br add show | egrep "netns|veth"
netns: NS1
veth1@if9 UP 10.0.0.1/24
netns: NS2
veth3@if7 UP 192.168.0.1/24
veth2@if10 UP 10.0.0.2/24
netns: NS3
veth4@if8 UP 192.168.0.2/24
$ sudo ip netns exec NS2 ping -c 1 192.168.0.2&&sudo ip netns exec NS2 ping -c 1 10.0.0.1
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.744 ms
--- 192.168.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.744/0.744/0.744/0.000 ms
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.542 ms
--- 10.0.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.542/0.542/0.542/0.000 ms
Python Webサーバーの起動
NS3において、Webサーバを簡易的に用意します。
$ cat << EOS | tee index.html
<html>
<body>
<P>Test Server binding if/192.168.0.1</P>
</body>
</html>
EOS
$ sudo ip netns exec NS3 python3 -m http.server 80&
iptables設定
DNATとMASQUERADE設定
# DNAT: NS2への到達パケットをNS3に転送
$ sudo ip netns exec NS2 iptables -t nat -A PREROUTING -i veth2 -p tcp --dport 80 -j DNAT --to-destination 192.168.0.2:80
# MASQUERADE: NS2から出るパケットのソースIPをNS2のインターフェイスIPに変換
$ sudo ip netns exec NS2 iptables -t nat -A POSTROUTING -o veth3 -j MASQUERADE
(Extended:ロギング設定)
# NS2において各チェインでのログ取得
$ sudo ip netns exec NS2 iptables -t nat -A PREROUTING -j LOG --log-prefix "PREROUTING: " --log-level 4
$ sudo ip netns exec NS2 iptables -A FORWARD -j LOG --log-prefix "FORWARD: " --log-level 4
$ sudo ip netns exec NS2 iptables -t nat -A POSTROUTING -j LOG --log-prefix "POSTROUTING: " --log-level 4
動作確認
$ sudo ip netns exec NS1 curl http://10.0.0.2
::ffff:192.168.0.1 - - [12/Aug/2024 15:37:10] "GET / HTTP/1.1" 200 -
<html>
<body>
<P>Test Server binding if/192.168.0.1</P>
</body>
</html>
NS1の10.0.0.1から10.0.0.2にHTTPアクセスし、NS3上の192.168.0.1で動作するWebSVにリダイレクトされたことを確認できました。
まとめ
IPTablesの基本概念、テーブルとチェーンの位置付けおよび用途、基本操作、DNATおよびSNATの設定方法について解説しました。IPTablesは強力なツールであり、ネットワークのセキュリティとトラフィック制御に不可欠です。
Discussion