令和のLinuxルーター作成手順〜raspi4を添えて〜

# 令和のLinuxルーター作成手順

## TL;DR
だいたい15000円くらいで作れる
Path MTU Discovery問題でハマった

## 流れ
だいたい以下のことをやります。

1. OSのインストール
2. PPPoEの接続(フレッツ網への接続)
3. DHCP,DNSの構築
4. ローカルネットワークの作成(有線、無線をブリッジ接続させる)
5. iptablesの構成
6. uPNP対応させる

## 必要な道具
### must (約\14000)
* RasberryPi4 (\6600)
  * 有名通販サイトではボッタクリ価格の転売が横行しているので、正規代理店( https://raspberry-pi.ksyic.com/ )から買いましょう。 　   
  * 他の使っていないPC等でも問題ありませんが、電気代的にRaspiを買ってもお釣りがくるんじゃないかと思います。（未検証）
* RasberryPi4用の電源(\1320)
  * raspi4からは5V/3Aじゃないと動かないので、適当な電源だと動かない可能性が高いです。専用電源を買いましょう。
* MicroSDカード(\4000)
  * Raspi4のストレージ用です。
  * 記事を書くタイミングではやすかったので、シリコンパワーの256GBの物( https://www.amazon.co.jp/gp/product/B07SPVT24H )にしました。  
* USBの有線LANアダプタ(\1000)
  * 何でもいいんですが、ELECOM推しなので今回はELECOMのにしました。
  * https://www.amazon.co.jp/gp/product/B07FD47QGV 
* MicroHDMI-HDMIケーブル(\1000)
  * ないと画面がうつらないので
  * https://www.amazon.co.jp/dp/B014I8TVLI/   

### may
* raspi4用ケース(\660)
  * むき出しでも支障はありませんが、何かしらのケースはあったほうがいいです。 

## OSのインストール
* Archlinux信者なのでArchlinuxをインストールしました。
  * インストール手順( https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-4 )を参考に実施します。 

###### 編集時点での手順
sdXのXの部分は環境に応じて適宜読み替えてください。

```
# SDカードのフォーマット
fdisk /dev/sdx

oを押してパーティションを削除
nを押してパーティションを作成
pを押してその後1を押し、基本パーティションを作成
何も入力せずEnterを押し先頭セクタを指定し、+128MBで先頭から128MBを第一パーティションに
tを押してcを押すことで、第一パーティションをFAT32に変更
pを押してその後2を押し、基本パーティションを作成
何も入力せずEnterを押し先頭セクタを指定し、何も入力せずEnterを押し、残りの領域全てを第二パーティションに
wを押して変更内容を書き込み

# パーティションのフォーマットとマウント
mkfs.vfat /dev/sdx1
mkfs.ext4 /dev/sdx1
mkdir {boot,root}
mount /dev/sdx1 boot
mount /dev/sdx2 root

# OSのインストール
wget http://os.archlinuxarm.org/os/ArchLinuxARM-rpi-4-latest.tar.gz
bsdtar -xpf ArchLinuxARM-rpi-4-latest.tar.gz -C root
sync

# bootのファイルを第一パーティションに移動
mv root/boot/* boot

# SDカードを取り出してOSを起動
umount boot root

# OSの初期化
## ※RHELの「subscription-manager register」みたいなコマンドなので
## 初期化はニュアンスが違う気がしますが、こういう時のinitialize,populateの
## 適切な日本語訳がわかる人はコメントください
pacman-key --init
pacman-key --populate archlinuxarm
```

デフォルトではless,vimが入っておらず、あまりに不便なためインストールします。

```
pacman -S vim less
```

## フレッツ網への接続
Linuxからpppの接続を行う際は、pppを使うと簡単に接続させることができます。

### pppのインストール

```
pacman -S ppp
```

### pppの設定

設定ファイルは/etc/ppp/peers/<適当なファイル名>で作成します。設定内容は以下のとおりです。
詳細についてはpppd(8)を見てください。

```
vim /etc/ppp/peers/hogehoge
```

```
plugin rp-pppoe.so

# WAN側のインターフェースを指定します。eth0はほとんどの場合RaspiについているLANの口です。
eth0

# プロバイダに指定された接続IDを記載します。@<ドメイン>も必要です。
name "hogehoge@foo.bar"

#　ISPの提示するDNSサーバを利用します
usepeerdns

# 常時接続します。都度切断<demand>させることもできますが、
# 電源切ったほうが早いのでほとんどの場合persistで問題ありません。
persist　

# pppの接続が成立時に自動的にルーティングテーブルに追加
defaultroute

#ログにパスワードを出力するのを抑止します。
hide-password

# ISPに自己証明を要求しない。基本的にはauthすべきですが、ISP相手の時はnoauthらしいです
noauth

# IPV6の接続を有効にします
+ipv6
```

PPP接続時のパスワードは、「/etc/ppp/pap-secrets」または「/etc/ppp/chap-secrets」の物を利用し、ISPに接続方式が指定されている場合は、指定されている方法の側にパスワードを記載します。

両方に書いても問題なく動くので、よくわからなかったら両方に書けば問題ありません。

```
# /etc/ppp/pap-secrets
<nameで指定した値> * <ISP指定のパスワード>

(例)
hogehoge@foo.bar * password
```

接続確認のため、手動でPPP接続を開始してみます。

```
pppd call /etc/ppp/peers/hogehoge
```

正常に動作していれば、ppp0が作成されます。

```
ip a
...
ppp0:
```

OSの起動時にサービスを自動起動するよう設定します。

```
systemctl enable ppp@hogehoge.service
```

## ローカルネットワークの作成

systemd-networkdで家庭内ネットワークを作成します。

まずは端末に存在するネットワークインターフェースを確認します。

```
ip a
```

だいたい以下のような出力がされるはずです。

```
lo:
eth0:
enp1s0u1:
wlan0:
ppp0:
```

無線と有線で疎通がとれたほうが何かと便利（Bonjour等）なので、ブリッジアダプタを作成し、有線のネットワークインターフェースと、無線のネットワークインターフェースを、仮想的にL2のネットワークで接続させることにします。

### ブリッジアダプタの作成

以下のファイルを作成し、ブリッジアダプタを作成します。

```
# /etc/systemd/network/br0.netdev
[NetDev]
Name=br0
Kind=bridge
```

ファイルの作成後、systemd-netwokrdを再起動します。

```
systemctl restart systemd-networkd
```

もう一度、ネットワークインターフェースの一覧を表示すると、ネットワークデバイスが作成されています。

令和なのでbrctlとか面倒なことをする必要はありません。

```
ip a
lo:
...
br0: (←増える)
```

次は、ローカルネットワーク用のデバイスをブリッジに接続します。eth0はPPP接続に利用しているため、変更は行いません。

```
# /etc/systemd/network/enp1s0u1.network (新規作成)
[Match]
Name=enp1s0u1

[Network]
Bridge=br0

# /etc/systemd/network/wlan0.network (新規作成)
[Match]
Name=wlan0

[Network]
Bridge=br0
```

これで、ネットワークインターフェースはブリッジに接続されました。

令和なので簡単です。

次は、ローカルネットワーク上で認識できるようにするため、IPアドレスを静的に指定します。今回は192.168.0.1/24にしましたが、ローカルアドレスとして指定されているアドレス( https://tools.ietf.org/html/rfc1918 )であれば何でも問題ありません。

結論から言うと以下のアドレスです。

* 10.0.0.0 - 10.255.255.255  (10/8 prefix)
* 172.16.0.0 - 172.31.255.255  (172.16/12 prefix)
* 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)

```
# /etc/systemd/network/br0.network (新規作成)
[Match]
Name=br0

[Network]
Address=192.168.0.1/24
Gateway=192.168.0.1
```

設定の完了後、再度systemd-networkdを再起動します。

```
systemctl restart systemd-networkd
```

以下のようにbr0が192.168.0.1として起動し、enp1s0u1とwlan0はL2で接続されているためIPアドレスを持ちません。(MACアドレス等は環境により異なるので適宜読み替えてください。)

```
enp1s0u2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br0 state UP group default qlen 1000
    link/ether 04:ab:18:3c:d4:6b brd ff:ff:ff:ff:ff:ff
br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ae:52:e3:c8:67:ca brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/24 brd 192.168.0.255 scope global br0
       valid_lft forever preferred_lft forever
wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br0 state UP group default qlen 1000
    link/ether dc:a6:32:71:0b:9e brd ff:ff:ff:ff:ff:ff
```


## DHCP,DNSの構築

今までの手順でローカルネットワークが作成され、手動で192.168.0.0/24のアドレスを入力すれば正常に動作しますが、いちいちやるのはあまりに面倒です。そのため、DHCP,DNSサーバを構築します。

### DHCPサーバの構築
dhcpdをインストールします。

```
pacman -S dhcpd
```

dhcpdの設定ファイルは「/etc/dhcpd.conf」です。以下のように設定ファイルを作成します。

```
option domain-name-servers 192.168.0.1;
option subnet-mask 255.255.255.0;
option routers 192.168.0.1;

subnet 192.168.0.0 netmask 255.255.255.0 {
  range 192.168.0.10 192.168.0.200;
}
```

この設定を行うことで、ローカルネットワークでは自動的に以下の設定が行われるようになります。

| 設定 | 値 |
| --- | --- |
| IPアドレス | 192.168.0.10〜192.168.0.200の空いているIP |
| デフォルトゲートウェイ | 192.168.0.1 |
| サブネットマスク | 255.255.255.0 |
|DNSサーバ | 192.168.0.1 |

作成後は、DHCPサーバを起動し、自動起動も有効にします。

```
systemctl enable dhcpd4
systemctl start dhcpd4
```

なお、上のセットは以下の一行でも同じことが出来ます。(蛇足)

```
systemctl enable --now dhcpd4
```

## DNSサーバの作成
ローカルPCの名前解決は行わず、インターネットのDNS情報を保持するだけのDNSキャッシュサーバを作成します。

まずはbindをインストールします。

```
pacman -S bind 
```

設定ファイルは「/etc/named.conf」です。DNSキャッシュサーバとして構築する場合の設定は以下のとおりです。

```
options {
    // 諸々のファイルを生成するディレクトリ
    directory "/var/named";

    // PIDファイルのパス
    pid-file "/run/named/named.pid";  

    // DNS問い合わせを待ち受けるネットワーク
    listen-on { 127.0.0.1; 192.168.0.0/24; };

    // DNS問い合わせに応答するネットワーク
    allow-recursion { 127.0.0.1; 192.168.0.0/24; };

    // DNSゾーン転送を許可するネットワーク
    allow-transfer { none; };
    // DNSゾーン情報の書き換えを許可するネットワーク
    allow-update { none; };

    // forwardersからのみ名前解決を行う
    forward only;
    // このDNSサーバが利用する親DNS (8.8.8.8,8.8.4.4はgoogleのDNS)
    forwarders{ 8.8.8.8; 8.8.4.4; };

    // サーバー情報を隠す
    version none;
    hostname none;
    server-id none;
};
```

設定の完了後、以下のコマンドでbindを起動し、自動起動も有効にします。

```
systemctl enable named
systemctl start named
```

## iptablesの構成

ここまで実際に試した方はわかるとおもいますが、Raspiからインターネットには接続できますが、ローカルネットワークからインターネットに接続することは、いま時点ではできません。ローカルネットワークからppp0のネットワークに出られるように許可を行います。

まずは、OSでIP転送が無効になっているため、許可します。

```
cat /proc/sys/net/ipv4/ip_forward
0(無効)
```

```
# /etc/sysctl.d/ipforward.conf (新規作成)
net.ipv4.ip_forward=1
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.all.forwarding=1
```

設定の作成後、OSを再起動します。

再度確認すると、IP転送が有効になっているはずです。

```
cat /proc/sys/net/ipv4/ip_forward
1
```

その後、iptablesをインストールします。

```
pacman -S iptables
```

本来は、iptablesコマンドで一つ一つ追加するべきですが、「/etc/iptables/iptables.rule」を以下のように編集します。

```
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:MINIUPNPD - [0:0]
-A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -o ppp0 -j TCPMSS --clamp-mss-to-pmtu
COMMIT
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:TCP - [0:0]
:UDP - [0:0]
-A INPUT -i br0 -p udp -m udp --dport 53 -j ACCEPT
-A INPUT -i br0 -p tcp -m tcp --dport 53 -j ACCEPT
-A INPUT -i br0 -p udp -m udp --dport 67 -j ACCEPT
-A INPUT -i br0 -p tcp -m tcp --dport 67 -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j TCP
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-proto-unreachable
-A FORWARD -i br0 -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -j REJECT --reject-with icmp-host-unreachable
-A OUTPUT -o ppp0 -d 192.168.0.0/24 -j DROP
-A TCP -p tcp -m tcp --dport 22 -j ACCEPT
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o ppp0 -j MASQUERADE
COMMIT
```

個々の設定の意味は以下のとおりです。

```
# mangleテーブルという、ToSフィールドというIPネットワークのネットワーク品質に関わる設定のテーブルです
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]

# Path MTU Discovery問題の対策を行います。
# Qiitaやstackoverflow等では一度に送るパケットサイズが"""なぜか"""大きく設定されているため、
# 既定値の1500だと、ネットワークの想定よりも大きいパケットサイズとなり、途中でデータが
# 破棄されてしまいます。以下の設定でMSSパケットというパケットサイズをリモート側に
# 伝える機能を有効にし、受け取れないパケットが送られてくることを抑止します。

-A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -o ppp0 -j TCPMSS --clamp-mss-to-pmtu
```

```
# 接続の許可拒否を行うテーブルです。
*filter

# どのルールにもひっかからない外からのパケットは拒否します
:INPUT DROP [0:0]

# どのルールにもひっかからないサーバを経由するパケットは拒否します
:FORWARD DROP [0:0]

# raspiから別ネットワークにでるパケットは許可します。
:OUTPUT ACCEPT [0:0]

# TCP/UDP用に作成したテーブルです。
:TCP - [0:0]
:UDP - [0:0]

# ローカルネットワーク(br0)からのDNS問い合わせを許可します。
-A INPUT -i br0 -p udp -m udp --dport 53 -j ACCEPT
-A INPUT -i br0 -p tcp -m tcp --dport 53 -j ACCEPT

# ローカルネットワーク(br0)からのDHCP問い合わせを許可します。
-A INPUT -i br0 -p udp -m udp --dport 67 -j ACCEPT
-A INPUT -i br0 -p tcp -m tcp --dport 67 -j ACCEPT

# raspi自身の全ての通信を許可します。
-A INPUT -i lo -j ACCEPT

# 何らかの方法で既に確立された接続を許可します。
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# ICMPのechoを許可します。
-A INPUT -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT

# UDP接続はUDPテーブルの処理に移譲します
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP

# TCP接続はTCPテーブルの処理に移譲します
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j TCP

# 状態の不明な接続は破棄します。
-A INPUT -m conntrack --ctstate INVALID -j DROP

# UDPテーブルに一致しなかったパケットを、ホストが存在しないエラーで拒否します。
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable

# TCPテーブルに一致しなかったパケットを、TCP RESETで拒否します。
-A INPUT -p tcp -j REJECT --reject-with tcp-reset

# その他の接続を、対応するプロトコルがないエラーで拒否します。
-A INPUT -j REJECT --reject-with icmp-proto-unreachable

# ローカルネットワークからインターネットに出る通信を許可します。
-A FORWARD -i br0 -j ACCEPT

# ローカルネットワーク側から確立したセッションが、サーバーを通過することを許可します。
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# 上2個のどちらでもない通信を拒否します。
-A FORWARD -j REJECT --reject-with icmp-host-unreachable

# 192.168.0.0/24向けの通信がインターネットに出ていくのを遮断します。
-A OUTPUT -o ppp0 -d 192.168.0.0/24 -j DROP

# SSH接続を許可します。
-A TCP -p tcp -m tcp --dport 22 -j ACCEPT
```

```
# ローカルネットワークがインターネットに出る時のテーブルです
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]

# ローカルネットワークからインターネットに出る通信は、iptablesで動的に割り当てして通信します。
# SNATにした場合、常に一定の値が利用されます。
-A POSTROUTING -o ppp0 -j MASQUERADE
```

設定の完了後、以下のコマンドでiptablesを起動し、自動起動も有効にします。

```
systemctl enable iptables
systemctl restart iptables
```

# uPNP対応させる

令和なので、ネットワーク機器もuPNPで動的にポート開放してほしい時がまあまああります。そこで、miniupnpdをインストールし、ローカルネットワークからの要求に応じてルーターで動的にポート開放が行えるようにします。

```
pacman -S miniupnpd
```

設定ファイルは「/etc/miniupnpd/miniupnpd.conf」です。

```
# 外側インターフェースを指定します
ext_ifname=ppp0

# 内側インターフェースを指定します
listening_ip=br0

# ソケットファイル(内部通信用ファイル)の場所を指定します。
minissdpdsocket=/var/run/minissdpd.sock

secure_mode=no
system_uptime=yes
notify_interval=60
clean_ruleset_interval=600
uuid=00000000-0000-0000-0000-000000000000

# upnpを許可するネットワークを指定します
allow 1024-65535 192.168.0.0/24 1024-65535
deny 0-65535 0.0.0.0/0 0-65535
```

設定の完了後、以下のコマンドでminiupnpdを起動し、自動起動も有効にします。

```
systemctl enable miniupnpd
systemctl restart miniupnpd
```