DebianでauひかりHGW配下のIPv4/IPv6対応ルーターを作る

# 目的
Debianでルーターを作って遊びたい！

# 前提
* IPv4はNATを使用
* IPv6は基本パススルーで、外部からの通信を制限する
* 書いてる人の家の回線はauひかりなので、MAP-Eとかそのへんの設定はしません
* VM（仮想マシン）として作ります
    * WANポートとLANポートを用意する
* ネットワーク関係はあんまり知識がありません
* 実用性はあんまり考えてません

# やること
* Debianのインストール（手順外）
* ネットワークインターフェースの設定
* カーネルパラメータの設定
* DNS、DHCPの設定
* ファイアウォールの設定
* 操作IP制限の設定

# 手順
## Debianのインストール
まずDebianを好きなようにインストールします。
※この手順では`Debian12(bookworm)`を使用します

起動したらターミナル等を起動してコマンドを打てる状態にします。
後続作業をする前にrootになっておきます。
※sudoはデフォルトで入らないのでsudoがいい人は別でインストールしてください
```shell
su -
```

## ネットワークインターフェースの設定
ブリッジインターフェースを作成するので必要なパッケージを導入。
```shell
apt install -y bridge-utils
```
/etc/network/interfacesを以下の通り編集。
```configuration:/etc/network/interfaces
# ブリッジ対象のインターフェースはIPを割り当てない
# enp6s18(WAN)
allow-hotplug enp6s18
iface enp6s18 inet manual

# enp6s19(LAN)
allow-hotplug enp6s19
iface enp6s19 inet manual

# ブリッジインターフェースの作成
auto br0
iface br0 inet dhcp # WAN側のIPv4のアドレスは自動取得する(HGWが設定)
  bridge_ports enp6s18 enp6s19
iface br0 inet static # LAN側に公開するルーターとしてのIPを設定
  address 192.168.1.1
  netmask 255.255.255.0
iface br0 inet6 manual # IPv6アドレスは不要なので割り当てない
```
* IPv4のポート開放もしたい場合はWAN側のIPアドレスをHGWでDMZ設定等すればできるはず
    * DMZ設定する場合は固定IPにする必要あり
* IPv6のネットワーク自体は利用者側にルーターがなくても動く上に、不正にアクセスされる等のセキュリティリスクにもなるのでルーター自体のIPv6アドレスは割り当てなくてもいいと思う
※割り当てられないのはグローバルユニキャストアドレスのみで、リンクローカルアドレスは常に生成される
* ちなみにブリッジインターフェースを作成せずに実現しようとも試みたが、（知識不足もあり）うまくいかなかった

設定できたら反映させる。
```shell
systemctl restart networking.service
```

## カーネルパラメータの設定
/etc/sysctl.confに以下の記述を追記する（なければファイルを作成する）
```config:/etc/sysctl.conf
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv4.conf.all.send_redirects = 0
```
* `net.ipv4.ip_forward`と`net.ipv6.conf.all.forwarding`はパケットをルーター配下へ転送するために必要
* `net.ipv4.conf.all.send_redirects = 0`はIPv4でNATを使用する場合に必要
※設定しないとICMPがうまく転送されない

上記設定を反映する。
```
sysctl -p
```

## DNS、DHCPの設定
簡易的に作るので`dnsmasq`で設定します。
```shell
apt install dnsmasq
```

```config:/etc/dnsmasq.conf
# logを残す
log-queries

# logファイルの場所を指定
log-facility=/var/log/dnsmasq/dnsmasq.log

# Port番号設定
port=53

# プライベート IP アドレスの逆引き要求は上位サーバーに問い合わせない
bogus-priv

# resolv-fileに指定しているIPを上から順番に問い合わせする
strict-order

#ドメイン名や.のないクエリを上位サーバーに問い合わせない
domain-needed

# dnsmasqでキャッシュするレコード数。0にするとキャッシュしない
cache-size=10000

listen-address=127.0.0.1,192.168.1.1

#DHCP設定
# DNS, dhcpを提供するNICを指定
interface=enp6s19
# dhcpを提供しないNICを指定。WAN側にはdhcpを提供しない。
no-dhcp-interface=enp6s18 
# dhcpでのアドレス割り当て範囲
dhcp-range=192.168.1.100,192.168.1.200,255.255.255.0,12h
# デフォルトゲートウェイ
dhcp-option=option:router,192.168.1.1
```
内容は以下を参考にさせていただきました。

https://chi2npui2.mydns.jp/setting-dnsmasq.html

https://zenn.dev/cran/articles/6c9588e36a0d53

ログ用ディレクトリを作成し、設定を反映。
```shell
mkdir /var/log/dnsmasq/
systemctl restart dnsmasq.service
```

## ファイアウォールの設定
一番肝の部分で設定の難しい部分。
ここで設定するのはあくまで一例なので、自身の環境に合わせてアレンジしてください。
### 基本方針
IPv6は内部の通信は送受信OK、外部からの通信は内部からの通信に対する応答等であればOK、ICMPv6はすべて通す。
IPv4はNATを使用して、すべての通信をルーターが中継する。

### 使うツール
Debian12に標準で導入されている`nftables`を使用。
※firewalldの設定が簡単そうなので導入を検討したが、nftablesとの連携でバグがあるらしいので使うのをやめてしまった

### 実際の設定

```shell
# IPv6
nft create table bridge filter
nft create chain bridge filter input {type filter hook input priority 0\; policy accept\;}
nft create chain bridge filter forward {type filter hook forward priority 0\; policy drop\;}
nft add rule bridge filter forward ct state established,related accept
nft add rule bridge filter forward meta l4proto ipv6-icmp accept
nft add rule bridge filter forward ip6 saddr "<ISPから広告されたIPv6プレフィックス>" ct state new,established,related accept
nft add rule bridge filter forward ip6 saddr "fc00::/7" ct state new,established,related accept
nft add rule bridge filter forward ip6 saddr "fe80::/64" ct state new,established,related accept
nft create chain bridge filter output {type filter hook output priority 0\; policy accept\;}

# IPv4 (NAT)
nft create table ip nat_filter
nft create chain ip nat_filter dst_nat {type nat hook prerouting priority dstnat \;}
nft create chain ip nat_filter src_nat {type nat hook postrouting priority srcnat \;}
nft add rule ip nat_filter src_nat ip saddr "192.168.1.0/24" masquerade
```

登録内容を確認。
```shell
nft list ruleset
```

```config
table bridge filter {
        chain output {
                type filter hook output priority 0; policy accept;
        }

        chain forward {
                type filter hook forward priority 0; policy drop;
                ct state established,related accept
                meta l4proto ipv6-icmp accept
                ip6 saddr <ISPから広告されたIPv6プレフィックス> ct state established,related,new accept
                ip6 saddr fc00::/7 ct state established,related,new accept
                ip6 saddr fe80::/64 ct state established,related,new accept
        }

        chain input {
                type filter hook input priority 0; policy accept;
        }
}
table ip nat_filter {
        chain dst_nat {
                type nat hook prerouting priority dstnat; policy accept;
        }

        chain src_nat {
                type nat hook postrouting priority srcnat; policy accept;
                ip saddr 192.168.1.0/24 masquerade
        }
}

```

* `table bridge filter`でブリッジインターフェースに対するフィルタリングをする
    * table ip6 filterにするとうまく動作しなかった
    * `chain input`と`chain output`には基本的に何も入れずaccept
※フィルターはforwardで制御
    * `chain forward`はデフォルトでdropし、条件に合致するものだけ転送する
        * `ct state established,related accept`で、LAN内部からセッションを開始したものだけ外部からの通信を受け入れる
        * `meta l4proto ipv6-icmp accept`でICMPv6をすべて受け入れる
※IPv6ネットワークの正常動作のために必要（ping以外の動作もあります）
            * ガチガチに固めるのであれば、ICMPv6の個々の動作を許可設定するのもOK
        * `<ISPから広告されたIPv6プレフィックス>`はHGW等で参照できるグローバルIPv6アドレスから算出等して作成する
            * 基本的には`2`から始まる`2xxx:xxx:xxxx:x/64`みたいな形式になるはず
            * プレフィックスは基本的に回線ごとに分かれているはず※なので、同じ回線内からの通信だと判別するために設定
※調べても確証は得られなかった　ChatGPTくんは基本回線ごとだとは言っていたが…
* `table ip nat_filter`でIPv4のNATを設定する
    * `ip saddr 192.168.1.0/24 masquerade`で192.168.1.0/24のネットワークに対しNAT変換を設定
    * ポートを空けたい（ポート転送したい）場合は以下のようにする
        * `nft add rule ip nat_filter dst_nat ip daddr <WAN側IPアドレス> tcp dport <WAN側ポート> dnat to <LAN内PCのIPアドレス>:<LAN側ポート>`


再起動しても反映されるように設定を保存しておく。
```shell
nft list ruleset > /etc/nftables.conf
```

## 操作IP制限の設定
ファイアウォールルールの`input`ですべてacceptしているので、ルーター自体の操作をローカルIPからしか行えないように設定しておく
※設定を間違えるとリモートアクセスができなくなるので直接コンソール等で直接操作することを推奨

```config:/etc/hosts.allow
ALL: 192.168.
```

```config:/etc/hosts.deny
ALL: all
```

* もう少し狭い範囲で絞る場合は`ALL: 192.168.1.`等としてもOK
* 許可する範囲はお好みで
※外部IPには許可しないことを推奨
* 保存したら即反映されます

# 完成！
配下にPCを配置したりして遊びましょう。

# 参考資料
https://nanbu.marune205.net/2021/12/debian-router-torouble.html?m=1

https://chi2npui2.mydns.jp/setting-dnsmasq.html

https://zenn.dev/cran/articles/6c9588e36a0d53