Raspberry Pi as a Router

この記事は、[木更津高専 Advent Calender 2023](https://qiita.com/advent-calendar/2023/nit_kisarazu)の9日目の記事です。

前→[距離減衰式で長周期地震動階級を推定してみた](https://qiita.com/maikuradentetu/items/a16b1f75174224d37bbf) by [@maikuradentetu](https://qiita.com/maikuradentetu)
次→[RustでDNAT設定いじるサイトを作った](https://qiita.com/toma09to/items/b4518283ec9395e36076) by [@toma09to](https://qiita.com/toma09to)

# 動機
私は、Raspberry Piを使って自宅サーバーを公開していましたが、自宅の他のネットワーク機器と切り離されていなかったため、万が一Raspberry Piが乗っ取られた時に、他の機器にも影響しかねない危険な状態でした。
これを解消するために、Raspberry Piを使ってDMZ機能を持つルーターを構築することにしました。
~~あれ？Webサーバーに使うRaspberry Piは何処に？~~

# 使用機器
- Raspberry Pi 4 Model B 4GB
  - OS: Ubuntu Server 22.04.3 LTS
- UGREEN 20256 × 2
- 光BBユニット

NICの購入ページ（Amazon）はこちら

https://www.amazon.co.jp/gp/product/B00MYTSN18

# ネットワーク構成
今回は、下図のようなネットワークを作成します。元のルーターは残す方針で構築しました。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1647235/1582ef57-f749-bd31-39a0-708c828fac25.png">

各ネットワークの概要は以下の通りです。

| ネットワーク名 | ネットワークアドレス | 概要 |
|:-:|:-:|:-:|
| 外部ネットワーク | 192.168.0.0/24 | 親ルーターとラズパイ |
| DMZ | 192.168.1.0/24 | 公開サーバー等の置き場 |
| 内部ネットワーク | 192.168.3.0/24 | 自宅のネットワーク機器の所属先 |

# 親ルーターの設定
ネットワークアドレスを`192.168.0.0/24`に変更し、ルーターのIPアドレスを`192.168.0.1`とします。

![Screenshot 2023-11-03 144450.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1647235/3c5bc413-6da7-74e6-8af7-cc142fcbbf2b.png "光BBユニットにおける設定例")

また、ルーターの簡易DMZ機能等の、グローバルIPアドレスへの接続要求を転送する機能を有効にして、転送先アドレスを`192.168.0.2`にします。

![Screenshot 2023-11-03 144515.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1647235/07b35e07-7423-3079-96f6-d4937a4a0a30.png)

# Raspberry Piの設定
## IPアドレスの固定
NICをラズパイに挿すと、`enx************`のようなインターフェースが追加されます。
私の環境では、`enx207bd26c92bb`を外部ネットワーク、`enx207bd27657d3`を内部ネットワーク、`eth0`をDMZに割り当てました。以下、各自の環境に合わせて読み替えてください。

今回はnetplanを用いてIPアドレスの設定をします。`/etc/netplan/99_config.yaml`を作成して、以下のように編集します。
```yaml:/etc/netplan/99_config.yaml
network:
  ethernets:
    # external network
    enx207bd26c92bb:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.0.2/24
      nameservers:
        addresses:
          - 8.8.8.8
          - 8.8.4.4
      routes:
        - to: default
          via: 192.168.0.1
    # DMZ network
    eth0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.1.1/24
    # internal network
    enx207bd27657d3:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.3.1/24
  version: 2
```

ファイルを作成出来たら、それを適用します。
```shell-session
$ sudo netplan apply
```

## IPフォワードの有効化
このままでは、NICの間でパケットが転送されないので、IPフォワードを有効にします。
`/etc/sysctl.conf`にIPフォワードの設定があるので、その部分をアンコメントします。
```diff_conf:/etc/sysctl.conf
# Uncomment the next line to enable packet forwarding for IPv4
- #net.ipv4.ip_forward=1
+ net.ipv4.ip_forward=1
```

これで、IPフォワードの設定が出来たので、それを適用するために再起動します。
```shell-session
$ sudo reboot
```

## iptablesの設定
iptablesを使って、ファイアウォール等の設定をしていきます。
`/etc/iptables.sh`を作成して、そこにルールを記述していきます。

### 変数の定義とテーブルの初期化
まず、変数と初期化を記述します。`GLOBAL_IP`にはあなたのグローバルIPアドレスを入れて、その他の値についても適宜読み替えてください。
```bash:/etc/iptables.sh
#!/bin/bash

# interface names
EXT_INT=enx207bd26c92bb
DMZ_INT=eth0
INT_INT=enx207bd27657d3

# network addresses
EXT_NET=192.168.0.0/24
DMZ_NET=192.168.1.0/24
INT_NET=192.168.3.0/24

# my IP address
PRIVATE_IP=192.168.0.2
GLOBAL_IP=0.0.0.0

# initialize iptables
iptables -F
iptables -t nat -F
iptables -X
```

### ポリシー
ポリシーはどのルールにもマッチしないパケットの処理を決めます。
INPUTとFORWARDはDROP（破棄）、OUTPUTはACCEPT（許可）とするのが安全です。
```bash:/etc/iptables.sh
# policy
iptables -P INPUT   DROP
iptables -P FORWARD DROP
iptables -P OUTPUT  ACCEPT
```

### 各種通信の許可
許可された通信のパケットは許可します。
```bash:/etc/iptables.sh
# established packets
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
```

ループバック（自分自身）からのパケットは許可します。
```bash:/etc/iptables.sh
# loopback
iptables -A INPUT -i lo -j ACCEPT
```

今回の構成では、インターネットからpingが飛んでくることは（多分）ないので、利便性のためにpingは許可します。
```bash:/etc/iptables.sh
# ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
```

内部ネットワークからのSSHを許可します。
```bash:/etc/iptables.sh
# SSH
iptables -A INPUT -s ${INT_NET} -i ${INT_INT} -p tcp --dport 22 -j ACCEPT
```

### フォワード
内部→外部、内部→DMZ、DMZ→外部は無条件でパケットを通します。また、後述するDNATの設定によって許可されたポートへの通信を通すために、外部→DMZも許可します。
さらに、確立された通信のパケットも通します。
```bash:/etc/iptables.sh
# forward
iptables -A FORWARD -s ${INT_NET} -i ${INT_INT} -o ${EXT_INT} -j ACCEPT
iptables -A FORWARD -s ${INT_NET} -i ${INT_INT} -o ${DMZ_INT} -j ACCEPT
iptables -A FORWARD -s ${DMZ_NET} -i ${DMZ_INT} -o ${EXT_INT} -j ACCEPT
iptables -A FORWARD               -i ${EXT_INT} -o ${DMZ_INT} -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
```

### SNAT
インターネットへの通信が親ルーターに届くようにSNATを設定します。
```bash:/etc/iptables.sh
# SNAT
iptables -t nat -A POSTROUTING -o ${EXT_INT} -j MASQUERADE
```

### DNAT
外部からの特定ポートへの通信がDMZへ届くようにDNATを設定します。これは、宛先アドレスをルーターから他のアドレスに書き換える機能で、今回はDMZ内の機器のアドレスを指定します。
私の環境では、`192.168.1.10`にWebサーバーがあるので、ここにHTTPとHTTPSを通します。
また、通常はローカルからサーバーへグローバルIPを使ってアクセスすることが出来ないので、ここで設定します。
ポート番号と送信先アドレスを変更すれば他の通信も許可できます。
```bash:/etc/iptables.sh
# DNAT
iptables -t nat -A PREROUTING               -p tcp -d ${PRIVATE_IP} --dport 80  -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING               -p tcp -d ${PRIVATE_IP} --dport 443 -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -i ${INT_INT} -p tcp -d ${GLOBAL_IP}  --dport 80  -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -i ${INT_INT} -p tcp -d ${GLOBAL_IP}  --dport 443 -j DNAT --to-destination 192.168.1.10
```

### ログ
破棄されたパケットを記録します。
```bash:/etc/iptables.sh
# logging
iptables -N LOGGING
iptables -A LOGGING -m limit --limit 1/s -j LOG --log-level warning --log-prefix "DROP: "
iptables -A LOGGING -j DROP
iptables -A INPUT   -j LOGGING
iptables -A FORWARD -j LOGGING
```

### 設定の適用
完成したファイルは以下のようになります。
```bash:/etc/iptables.sh
#!/bin/bash

# interface names
EXT_INT=enx207bd26c92bb
DMZ_INT=eth0
INT_INT=enx207bd27657d3

# network addresses
EXT_NET=192.168.0.0/24
DMZ_NET=192.168.1.0/24
INT_NET=192.168.3.0/24

# my IP address
GLOBAL_IP=0.0.0.0

# initialize iptables
iptables -F
iptables -t nat -F
iptables -X

# policy
iptables -P INPUT   DROP
iptables -P FORWARD DROP
iptables -P OUTPUT  ACCEPT

# established packets
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT

# SSH
iptables -A INPUT -s ${INT_NET} -i ${INT_INT} -p tcp --dport 22 -j ACCEPT

# forward
iptables -A FORWARD -s ${INT_NET} -i ${INT_INT} -o ${EXT_INT} -j ACCEPT
iptables -A FORWARD -s ${INT_NET} -i ${INT_INT} -o ${DMZ_INT} -j ACCEPT
iptables -A FORWARD -s ${DMZ_NET} -i ${DMZ_INT} -o ${EXT_INT} -j ACCEPT
iptables -A FORWARD               -i ${EXT_INT} -o ${DMZ_INT} -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# SNAT
iptables -t nat -A POSTROUTING -o ${EXT_INT} -j MASQUERADE

# DNAT
iptables -t nat -A PREROUTING               -p tcp -d ${PRIVATE_IP} --dport 80  -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING               -p tcp -d ${PRIVATE_IP} --dport 443 -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -i ${INT_INT} -p tcp -d ${GLOBAL_IP}  --dport 80  -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -i ${INT_INT} -p tcp -d ${GLOBAL_IP}  --dport 443 -j DNAT --to-destination 192.168.1.10

# logging
iptables -N LOGGING
iptables -A LOGGING -m limit --limit 1/s -j LOG --log-level warning --log-prefix "DROP: "
iptables -A LOGGING -j DROP
iptables -A INPUT   -j LOGGING
iptables -A FORWARD -j LOGGING
```

このスクリプトを適用するために、実行権限を与えて実行します。
```shell-session
$ sudo chmod +x /etc/iptables.sh
$ sudo /etc/iptables.sh
```

## iptablesのルールの自動適用
iptablesのルールはシャットダウンすると消えてしまいます。そこで、systemdで起動時に先ほど作成したスクリプトを実行させるデーモンを作成します。
`/etc/systemd/system`内に`auto-iptables.service`というファイルを作成します。
```systemd:/etc/systemd/system/auto-iptables.service
[Unit]
Description=Automatic iptables setup service
After=network.target

[Service]
User=root
ExecStart=/etc/iptables.sh
Type=oneshot

[Install]
WantedBy=multi-user.target
```

そして、以下のコマンドを実行して、システム起動時にデーモンが起動するようにします。
```shell-session
$ sudo systemctl daemon-reload
$ sudo systemctl enable auto-iptables.service
```

## DHCPの設定
ルーターとしての設定はここまでで終わりなのですが、家族に

> 内部ネットワークにはDHCPでIPアドレスが自動で降ってくるようにしてほしい

と言われたので、DHCPサーバーの設定もしていきます。

まず、`isc-dhcp-server`をインストールします。
```shell-session
$ sudo apt install isc-dhcp-server
```

次に、設定ファイルである`/etc/dhcp/dhcpd.conf`を編集します。
以下の部分をそれぞれコメント、アンコメントします。
```diff_conf:/etc/dhcp/dhcpd.conf
# option definitions common to all supported networks...
- option domain-name "example.org";
- option domain-name-servers ns1.example.org, ns2.example.org;
+ #option domain-name "example.org";
+ #option domain-name-servers ns1.example.org, ns2.example.org;
```
```diff_conf:/etc/dhcp/dhcpd.conf
# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
- #authoritative;
+ authoritative;
```

そして、ファイルの末尾に以下のように追記します。
```conf:/etc/dhcp/dhcpd.conf
subnet 192.168.3.0 netmask 255.255.255.0 {
  range 192.168.3.2 192.168.3.254;
  option routers 192.168.3.1;
  option domain-name-servers 8.8.8.8;
  option broadcast-address 192.168.3.255;
  ignore declines;
}
```

最後に、サービスを起動し、有効化します。
```shell-session
$ sudo systemctl start isc-dhcp-server.service
$ sudo systemctl enable isc-dhcp-server.service
```

`/var/lib/dhcp/dhcpd.leases`に、取得されたIPアドレス等の情報が記録されます。

# あとがき
今回は内容に含みませんでしたが、

- DMZへのアクセスのDoS対策（hashlimit）
- IPv6対応

などのことをこれから実現させていきたいと思っています。
それでは、また明日お会いしましょう！