AlmaLinux9のルーティング設定のつづき

1つ前の記事にて、1つのOSが、左右のネットワークをルーティングできるように、構成を作りました。
が、改めて考えて、NAPTの方が良い、とのことで、構成を変えて、その動きを変更前と比較して確認します。

# 1. テスト環境と見直すところ

### (1) テスト環境のおさらい

テスト環境は、この記事で作った環境です。

https://qiita.com/nonbiri-yama/items/bca242a9d40d132590ed

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/90d23f3d-a9bd-49f1-889d-0cd156365032.png)

ここで登場するホストは、nettest1、nettest2、rhel8hostです。

192.168.51.0のネットワーク上のnettest2（=クライアント）から、192.168.11.0のネットワーク上のrhel8host（=サーバ）に、ping、curlの要求を出し、それに応答できるようにしました。nettest1が両側のネットワークの踏み台です。

### (2) テスト環境のネットワーク設定について

単純に、この図の通りにIPアドレスをつけるだけではダメで、各OSに、以下の設定が必要でした。

- OS上のNIC間のパケット転送の設定。nettest1のsysctl設定に、「net.ipv4.ip_forward = 1」を追加することで、eth0とeth1の間など、OS上の任意のNIC間で、パケットをスルーする。
- デフォルトゲートウェイの設定。宛先がどこにあるか知らないときにとりあえず送る先。
- スタティックルートの設定。その宛先には、ここに渡すと送れますよ、という個別ルール。

これらの設定を含めて、3つのOSの各NICに対して、このように設定しました。

| |IPアドレス|デフォルトGW|スタティックルート|
|--|--|--|--|
|nettest2<br>(eth0)|192.168.51.162|192.168.51.161|-|
|nettest1<br>(eth1)|192.168.51.161|-|-|
|nettest1<br>(eth0)|192.168.11.161|192.168.11.1|-|
|rhel8host<br>(br0)|192.168.11.201|192.168.11.1|192.168.51.0/24<br>(NextHop=192.168.11.161)|

### (3) テスト環境のスタティックルートの設定について

特に、rhel8hostのスタティックルート設定が無いと、送り元srcが192.168.51.162の要求に応答を返そうとしても、rhel8hostは、それがどこにいるのか知らないのでとりあえずデフォルトゲートウェイ（=ルータ）に送ります。受け取ったルータは、192.168.51.162とnettest2は結び付けられず、nettest2に応答が返らない、となっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/efa8dfdc-e837-437f-95ee-3fa4f1f26ef7.png)

スタティックルートを設定することで、rhel8hostは、応答のdst=192.168.51.162は、「192.168.51.0/24のものなので、192.168.11.161に送る」という個別ルールに従い、踏み台のeth0に送ります。踏み台ではeth0からeth1に転送されるので、eth1からさらに送られて、nettest2に届きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/a4204d6c-299c-4d8b-b762-f29d2e93b4be.png)

### (4) サーバにスタティックルートを設定するのは変？

ただ、これは一般的な設定方法だろうか、と考えると、まあ、違いますね。どこかにあるWebサーバが、どこから来るか不定なすべてのクライアントに対して、クライアントまでの経路を考慮して、すべてのクライアントにきちんと応答が届くように、サーバ自身にルーティング設定を行うなど、聞いたことがないです。（どんなサービス精神だよ、っていう）

### (5) ではどうすべきか

では、一般的にはどうすべきかというと、踏み台nettest1に対し、NAPTを設定します。踏み台のNAPTが次のように動作するように設定します。
- パケットが、eth1から入ってeth0から出るとき（=プライベートネットワークからの送信パケット）には、送信パケットの送り元srcを踏み台のIPアドレスとポート番号に置換する（ポート番号はランダム）。
- NAPTは、その置換の組み合わせ（送り元src->置換src）を覚えておく。
- 置換して送信したパケットに対する応答（=プライベートネットワークの受信パケット）が、NAPTのeth0に届くと、受信パケットの送り先dstのIPアドレスとポートを、覚えておいた送り元のIPアドレスとポートに置換し、eth1から送る。（そのあとは、置換の組み合わせは忘れてもいい）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/52bb9196-e5a7-4f58-9ddc-1bf921938a70.png)

### (6) NAPTに変えた場合の動き

このようにNAPTを設定すると、まず、クライアントからの送信パケットは、eth1から入ってNAPTを通過するとき、送り元srcが踏み台のeth0のIPアドレスとポート番号に置換されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/b317cdfb-8922-4a6c-8452-2dcbf50b4a7e.png)

これならば、サーバからすると、同じネットワークの192.168.11.161から要求を受けただけとなり、そこに応答を返せばよくなります。デフォルトゲートウェイに送って行方不明になることはありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/411e7ebe-dcaa-42ae-88e9-ec557d5097a1.png)

サーバの応答が踏み台のeth0に届くと、そのクライアントの受信パケットは、eth0から入ってNAPTを通過するとき、送り先dstがクライアントのIPアドレスとポート番号に置換されます。

こうして、クライアントの要求が、NAPTに中継されてサーバに届き、サーバからの応答が、NAPTに中継されて、クライアントに届くようになります。

ということで、rhel8hostに対するスタティックルート設定をやめ、nettest1にNAPTを設定する構成に変えます。

# 2. NAPT設定

### (1) rhel8hostからスタティックルート設定の削除

まず、rhel8hostから、スタティックルートの設定を消しました（nmtuiで設定し、OS再起動）。
これで、nettest2から、curl、pingを要求しても、応答が返らなくなりました。

### (2) iptablesによるNAPTの設定

NAPTの設定といえば、iptablesによる設定、ということで、以下を実施します。これは、NAPTのLinuxにおける実装である、IPマスカレードの設定を行うコマンドです。

```bash
[root@nettest1 ~]# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
[root@nettest1 ~]#
[root@nettest1 ~]# iptables -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
[root@nettest1 ~]#
[root@nettest1 ~]# iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
[root@nettest1 ~]#
[root@nettest1 ~]# iptables -A FORWARD -j LOG --log-prefix "FORWARD-DROP: "
[root@nettest1 ~]#
[root@nettest1 ~]# iptables -A FORWARD -j DROP
[root@nettest1 ~]#
```

#### # iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

この1つ目のコマンドは、こういう設定です。

- -t nat：設定対象を「nat」テーブルとする。アドレス変換用。
- -A POSTROUTING：ルールを「POSTROUTING」チェインに追加する。図のように、FORWARDフックか、OUTPUTフックの後に、このチェインのフックが評価される。
- -o eth0 -j MASQUERADE：条件とアクションの設定。
    - 条件：パケットの送信I/Fがeth0ならば
    - アクション：パケットの送信元アドレスを、eth0のIPアドレスに変換（=マスカレード、NAPT）する。

つまり、nettest1のOS上にはいくつかのI/Fがありますが、すべてのI/Fから送信される、すべてのパケットが、LinuxカーネルのNetfilter内の、この「POSTROUTING」hookで評価されて、その条件を満たすならば、アクションが行われる、という設定です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/2511a22e-ff2b-4232-9223-055267d9467f.png" alt="Description" width="80%">

（図は以下のページの図をベースにしています。全体像はリンク先にあります。）

https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks

このルールによって、eth0から送信されるすべてのパケットのsrcのIPアドレスは、eth0のIPアドレス=192.168.11.161に変換されます。nettest1のeth0から送信されるパケットとしては、
- ① nettest1自身から送るパケット
- ② 別のI/Fで受信して、ローカル宛てではないため、そのまま転送して送り出すパケット

があります。②はIPアドレスが変わります（①は変換しても変わりません）。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/4dcfd48e-4d55-4e7e-9139-9409ea541509.png" alt="Description" width="50%">

#### # iptables -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
#### # iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT

このコマンドの2つ目、3つ目は、こういう設定です。

- (-tなし)：設定対象をデフォルトの「filter」テーブルとする。パケットフィルタ（=通過パケットの許可など）用。
- -A FORWARD：ルールを「FORWARD」チェインに追加する。図のように、ローカル宛てではないとルーティング判定されたときに、このチェインのフックが評価される。
- -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT：ルール1の、条件とアクションの設定。
    - 条件：パケットの受信I/Fがeth0で、かつ、送信I/Fがeth1で、かつ、既存の接続か関連接続からのパケットならば
    - アクション：パケットを許可する。
- -i eth1 -o eth0 -j ACCEPT：ルール2の、条件とアクションの設定。
    - 条件：パケットの受信I/Fがeth1で、かつ、送信I/Fがeth0ならば
    - アクション：パケットを許可する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/966210d0-a828-4e79-9cc0-dc3736ee4c03.png)

この2つのルールは、いずれも、受信I/Fと送信I/Fが別なので、
- ② 別のI/Fで受信して、ローカル宛てではないため、そのまま転送して送り出すパケット

を対象にしたルールです。ルールが2つあるのは、パブリックネットワーク（eth0側）から受信するときと、プライベートネットワーク（eth1側）から受信するときで、それぞれ、パケットフィルタの仕方を非対称にするためです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/b3111a50-107d-4b5f-8ce9-4e9b9e42d213.png" alt="Description" width="50%">

- eth0で受信して、ローカル宛てではないため、eth1から転送（送信）するパケットは、「FORWARD」フックでフィルタ評価する。既存の接続か関連接続からのパケットならば許可する。
- eth1で受信して、ローカル宛てではないため、eth0から転送（送信）するパケットは、「FORWARD」フックでフィルタ評価する。この条件のパケットはすべて許可する。

要は、プライベートネットワークがなんでも受信しないようにするための設定ですね。ただ、では、このルールに該当しないパケットはどうなるかというと、「FORWARD」フックのデフォルトポリシーに従うことになります。

デフォルトポリシーは、以下のように、「*filter」テーブルの、「:FORWARD」チェインが、「ACCEPT」なので、許可です。
```
[root@nettest1 ~]# iptables-save
# Generated by iptables-save v1.8.10 (nf_tables) on Thu Mar 20 13:12:19 2025
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i eth1 -o eth0 -j ACCEPT
COMMIT
# Completed on Thu Mar 20 13:12:19 2025
# Generated by iptables-save v1.8.10 (nf_tables) on Thu Mar 20 13:12:19 2025
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o eth0 -j MASQUERADE
COMMIT
# Completed on Thu Mar 20 13:12:19 2025
[root@nettest1 ~]#
```

つまり、3つ目のコマンドまでを実行した時点では、nettest1のeth0<--->eth1間の「FORWARD」フックでは、全パケットが許可されるので、素通りの設定です。ということで、続く2つのコマンドで設定します。

#### # iptables -A FORWARD -j LOG --log-prefix "FORWARD-DROP: "
#### # iptables -A FORWARD -j DROP

このコマンドの4つ目、5つ目は、こういう設定です。

- (-tなし)：設定対象をデフォルトの「filter」テーブルとする。パケットフィルタ用。
- -A FORWARD：ルールを「FORWARD」チェインに追加する。
- -j LOG --log-prefix "FORWARD-DROP: "
    - 条件：なし。
    - アクション：「FORWARD-DROP: 」をプリフィックスとして、パケットをログに記録する。
- -j DROP
    - 条件：なし。
    - アクション：パケットを破棄する。

5つのコマンドを順番に実行すると、「*filter」テーブルの、「:FORWARD」チェインは、次のようにルールが設定されます。

```
[root@nettest1 ~]# iptables-save
# Generated by iptables-save v1.8.10 (nf_tables) on Thu Mar 20 13:30:55 2025
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i eth1 -o eth0 -j ACCEPT
-A FORWARD -j LOG --log-prefix "FORWARD-DROP: "
-A FORWARD -j DROP
COMMIT
～
```

ルールは上から順番に評価されていき、-j ACCEPTは終端ステートメント（terminal statement）なので、許可、という判決で評価が終了します。一方、-j ACCEPTの2ルールの条件を満たさないパケットは、後続のルール評価に進みます。

終端ステートメント、非終端ステートメントの話は、以下などより。

https://www.netfilter.org/projects/nftables/manpage.html

-j LOGでは、syslog（=/var/log/messages）に、パケットをログに記録します。また、-j LOG（logステートメント）は非終端ステートメントなので、次の、-j DROPの評価に進みます。

-j DROPでは、パケットが破棄されます。

ということで、許可の条件を満たさないパケットは、すべて、"FORWARD-DROP: "という文字列を付けてログを残し、破棄する、という設定です。

### (3) iptablesの設定確認

5つのコマンドを実行すると、設定は以下となります。

```
[root@nettest1 ~]# iptables-save
# Generated by iptables-save v1.8.10 (nf_tables) on Thu Mar 20 13:30:55 2025
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i eth1 -o eth0 -j ACCEPT
-A FORWARD -j LOG --log-prefix "FORWARD-DROP: "
-A FORWARD -j DROP
COMMIT
# Completed on Thu Mar 20 13:30:55 2025
# Generated by iptables-save v1.8.10 (nf_tables) on Thu Mar 20 13:30:55 2025
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o eth0 -j MASQUERADE
COMMIT
# Completed on Thu Mar 20 13:30:55 2025
[root@nettest1 ~]#
```

また、簡易的な設定は、iptables -Lコマンドで確認できます。

「nat」テーブルの簡易表示。
```
[root@nettest1 ~]# iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  anywhere             anywhere
[root@nettest1 ~]#
```
「-j MASQUERADE」の設定が、「POSTROUTING」チェインに行われていることが分かります。ただし、「-o eth0」の部分は、ここでは表現されていません。

「filter」テーブルの簡易表示。
```
[root@nettest1 ~]# iptables -L -t filter
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             anywhere
LOG        all  --  anywhere             anywhere             LOG level warn prefix "FORWARD-DROP: "
DROP       all  --  anywhere             anywhere

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
[root@nettest1 ~]#
```

-j ACCEPTの設定2行と、-j LOGと-j DROPの設定2行が、順番にルール設定されていることが分かります。ただ、こちらも、-j ACCEPT行のstateに行に、I/Fの表示がないため、どちらからどちらをどうする、が分かりません。


# 3. 動作確認

### (1) nettest2からrhel8hostへの通信確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/59b307cc-e42a-4c02-bc98-069dd54b463f.png)

まず、プライベートネットワーク内のnettest2から、rhel8hostに通信する場合です。

pingが返ります。

```
[root@nettest2 ~]# ping -c3 192.168.11.201
PING 192.168.11.201 (192.168.11.201) 56(84) bytes of data.
64 bytes from 192.168.11.201: icmp_seq=1 ttl=63 time=0.917 ms
64 bytes from 192.168.11.201: icmp_seq=2 ttl=63 time=1.97 ms
64 bytes from 192.168.11.201: icmp_seq=3 ttl=63 time=2.10 ms

--- 192.168.11.201 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2007ms
rtt min/avg/max/mdev = 0.917/1.665/2.104/0.531 ms
[root@nettest2 ~]#
```

また、curlでhttpリクエストを送っても、応答が返ります。

```
[root@nettest2 ~]# curl http://192.168.11.201
<html><body>Node: fd90d330e6d9(10.88.0.2), Pod: my-httpd(10.88.0.2)</body></html>
[root@nettest2 ~]#
```

TCP通信なので、応答パケットが返っているはずですが、--state RELATED,ESTABLISHED -j ACCEPTで許可されて、nettest1をeth0からeth1に転送されて通過できていることが分かります。


### (2) rhel8hostからnettest2への通信確認

続いて、許可されていないパケットを、無理やり、送りつけてみます。rhel8hostに、スタティックルート（192.168.51.0/24宛てのパケットは、192.168.11.161に送る）を設定します。

```
[root@rhel8host ~]# netstat -nr
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         192.168.11.1    0.0.0.0         UG        0 0          0 br0
192.168.11.0    0.0.0.0         255.255.255.0   U         0 0          0 br0
192.168.51.0    192.168.11.161  255.255.255.0   UG        0 0          0 br0
[root@rhel8host ~]#
```

これで、rhel8hostから、nettest2に対して、pingを送ってみます。

```
[root@rhel8host ~]# ping -c 3 192.168.51.162
PING 192.168.51.162 (192.168.51.162) 56(84) bytes of data.

--- 192.168.51.162 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2063ms

[root@rhel8host ~]#
```

100% packet lossなので、破棄されていることが分かります。
また、-j LOGの設定により、許可されずに破棄されたパケットは、nettest1のsyslogにログされています。これを確認すると以下です。

```
[root@nettest1 ~]# tail -F /var/log/messages
～
Mar 20 13:54:09 nettest1 kernel: FORWARD-DROP: IN=eth0 OUT=eth1 MAC=52:～5e:08:00 SRC=192.168.11.201 DST=192.168.51.162 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=22116 DF PROTO=ICMP TYPE=8 CODE=0 ID=2 SEQ=1
Mar 20 13:54:10 nettest1 kernel: FORWARD-DROP: IN=eth0 OUT=eth1 MAC=52:～5e:08:00 SRC=192.168.11.201 DST=192.168.51.162 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=22423 DF PROTO=ICMP TYPE=8 CODE=0 ID=2 SEQ=2
Mar 20 13:54:11 nettest1 kernel: FORWARD-DROP: IN=eth0 OUT=eth1 MAC=52:～5e:08:00 SRC=192.168.11.201 DST=192.168.51.162 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=22596 DF PROTO=ICMP TYPE=8 CODE=0 ID=2 SEQ=3
```

ということで、この設定によって、NAPTが設定できたことが確認できました。
（firewalld、nftablesの設定方法も確認しようとしましたが、ちょっと力尽きました）

# まとめ

iptablesでNAPTの設定を試しました。