UnboundをIPv6対応させようとしてエラーと戦った話

足りないっていうとNATっていう。
こだまでしょうか、いいえIPv4。

枯渇するといわれ続けて三千年、枯渇しても普及しませんIPv6。
まずは自宅から始めてみましょう。

# 未来ずらぁ
皆さんはIPv6と聞くとどんなイメージを抱きますか？
私自身IPv6に対しては**割り当て数が多い次世代規格**程度の認識しか持っておらず、手持ちの環境（ISP）では実現不可だと思っていました。
ですが、先日、自宅回線をauひかりからコミュファ光に乗り換えようとしたところ「IPv6？速いらしいじゃん？PPPoE？IPoE？ポッポー！」みたいな状態だったので、きちんと理解しようと一念発起し勉強しました。
すると、今の環境でもIPv6いけるんじゃねということになり、実際、Wi-FiルーターのIPv6という項目をポチっとするだけでIPv6で接続ができ、じゃあ**サーバーなんかもIPv6にしてしまえ**ってことで本件が開戦した次第です。

別にIPv6のヘッダフォーマットやらまで理解する必要はありませんが、最低限、書式やリンクローカルアドレス等が分からないと**危険**ですので、僕と同じでIPv6？ピーポ君？な人は[こちらの記事](https://www.atmarkit.co.jp/ait/articles/1501/29/news014.html)でも読んでみてください。
IPv6に限らずですが、**ルーターのセキュリティはきちんと設定して**くださいね。

# IPv6デュアルスタック化
ネットをあさればゴロゴロと情報がありますが、当方の設定は以下のようにしてあります。

```bash:/etc/unbound/unbound.conf.d/unbound.conf
server:
        #ログ
        verbosity: 0 #無効

        #CPUコア数
        num-threads: 4

        #受け入れインターフェース
        interface: ::1 #IPv6ローカルホスト≒127.0.0.1
        interface: :: #IPv6任意IP≒0.0.0.0
        interface: 127.0.0.1 #IPv4ローカルホスト
        interface: 0.0.0.0 #IPv4任意IP

        #受け入れポート
        port: 53 #デフォルト

        #アクセスコントロール
        access-control: 192.168.1.0/24 allow #IPv4ローカル
        access-control: fe80::/10 allow #IPv6ローカル

        #動作設定
        do-ip4: yes #IPv4を解決する
        do-ip6: yes #IPv6を解決する
        do-udp: yes #UDPを解決する
        do-tcp: yes #TCPを解決する

        #情報の秘匿
        hide-identity: yes
        hide-version: yes

        #IPv6優先
        prefer-ip6: yes

        #パフォーマンス設定
        outgoing-range: 200 # 1024/コア数 - 50
        num-queries-per-thread: 100 # outgoing-rangeの半分
        msg-cache-size: 64m #キャッシュメモリサイズ（合計で最大2.5倍になるので余裕をもって）
        rrset-cache-size: 128m #キャッシュメモリサイズ（msg-cache-sizeの2倍）
        msg-cache-slabs: 4 #コア数に最も近い2の累乗
        rrset-cache-slabs: 4 #コア数に最も近い2の累乗
        infra-cache-slabs: 4 #コア数に最も近い2の累乗
        key-cache-slabs: 4 #コア数に最も近い2の累乗

#該当しない問い合わせをDNSに投げる
forward-zone:
        name: "." #すべて
        forward-addr: 2001:4860:4860::8888 #Google public DNS IPv6
        forward-addr: 2001:4860:4860::8844 #Google public DNS IPv6
        forward-addr: 8.8.8.8 #Google public DNS IPv4
        forward-addr: 8.8.4.4 #Google public DNS IPv4
```

このように設定しておけばIPv4/IPv6デュアルスタックのDNSキャッシュサーバーとして稼働するはずです。

#### outgoing-range
パフォーマンス設定について`outgoing-range`と`num-queries-per-thread`を`8192`のように大きく設定している記事がありますが、[ユーザー会](https://unbound.jp/unbound/howto_optimise/)様によると、`outgoing-range`には**最大で`1024`の制限がある**とのことです。
`libevent`というプラットフォームをコンパイル時に導入すると、`1024`の制限を突破できるようですが**apt installした場合は不可**だと思われます。
当方は家庭内のみでの実装なので制限に甘んじて、推奨の`1024/コア数 - 50`としています。

#### ファイアウォール
忘れないようにファイアウォールも開けておきましょう。
当方では`ufw`を使用していますので、`sudo ufw allow from fe80::/10 to any port 53`とすればローカルから53ポートへのアクセスが可能になります。

#### 機器側設定
IPv6ではIPアドレスは基本的にDHCPv6かRA（Router Advertisement）を用いた自動割り当てですから任意のIPを設定することはありません。
ですが、ローカル内ではプレフィックスやMACアドレスを用いて生成されたリンクローカルアドレスという固有のアドレスを持っていますので、そいつを使ってDNSサーバーを指定します。
まず、`ip a`でUnboundを走らせるマシンのリンクローカルアドレスを調べます。

```bash
hogehoge@hogehoge:~ $ ip a #リンクローカルアドレスを調べます
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.x/24 brd 192.168.1.255 scope global noprefixroute wlan0
       valid_lft forever preferred_lft forever
    inet6 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 297sec preferred_lft 117sec
    inet6 fe80::xxxx:xxxx:xxxx:xxxx/64 scope link #こいつがリンクローカルアドレス
       valid_lft forever preferred_lft forever
```

`inet6`というところにある`fe80::xxxx:xxxx:xxxx:xxxx`がリンクローカルアドレスです。
ちなみに、リンクローカルアドレスは`fe80::`から始まります。
このアドレスをPCのDNSサーバーとして設定すれば完了です。

# DNSゾーン設定
ここからが本命のローカルでのDNSに関する設定です。

## ダメです！
こっちがダメだった設定です。
ゾーンファイルについて少しですが解説も書いておきます。

```bash:/etc/unbound/unbound.conf.d/dns.conf
server:
      # local-zone: "ローカルで返すゾーン." type => 以下の表参照
        local-zone: "example.com." transparent

      # 正引きの設定
      # local-zone: ローカルで返すドメイン.（最後尾の.を忘れずに）
      #             |              TTL（パケットの寿命、一個ルーターを通る毎に減るので300回渡り歩く）
      #             |              |   クラス名（INTERNETの略）
      #             |              |   |  Aレコード（エラーの原因でした）
      #             |              |   |  | IPアドレス
        local-data: "example.com.　300 IN A 192.168.1.x" #これをそのままIPv6に書き換えたら…。
        local-data: "www.example.com. 300 IN A 192.168.1.x"

        #IPv6の正引き設定
        local-data: "example.com. 300 IN A fe80::xxxx:xxxx:xxxx:xxxx" #ダメです！
        local-data: "www.example.com. 300 IN A fe80::xxxx:xxxx:xxxx:xxxx" #動きません！
```

IPv6の`local-zone`をコメントアウトすると動くので、ここが原因と推測。

## local-zoneの主なtype
|**type**||
|:--:|:--|
|**deny**|`local-data`に一致したら問い合わせに回答します。<br>一致しない場合は無視します。|
|**refuse**|`local-data`に一致したら、問い合わせに回答します。<br>一致しない場合は`rcode REFUSED`を添えてエラーを返します。|
|**static**|`local-data`に一致したら、問い合わせに回答します。<br>完全不一致なときは、`nodata`あるいは`nxdomain`を回答します。<br>サブドメインなどで部分一致として`local-data`に存在するときには、否定回答として回答にSOAが含まれます。|
|**transparent**|`local-data`に一致したら、問い合わせに回答します。<br>一致しない場合は`forward-zone`で設定したDNSサービスに問い合わせます。|

## 原因と解決策
IPv4の時は`local-data: "example.com. 300 IN A 192.168.1.x"`のように設定してたので、IPv6でも`"example.com. 300 IN A fe80::xxxx:xxxx:xxxx:xxxx"`と設定しましたがエラーになりました。
試行錯誤した結果、IPv6ゾーンはAレコードではなくAAAAレコードであることが分かりました。
ですので、`"example.com. 300 IN AAAA fe80::xxxx:xxxx:xxxx:xxxx"`とするのが正解です。

## 正解
IPv6をAAAAレコードにして、逆引き設定を追加して完成形です。

```bash:/etc/unbound/unbound.conf.d/dns.conf
server:
        local-zone: "example.com." transparent

        local-data: "example.com. 300 IN AAAA fe80::xxxx:xxxx:xxxx:xxxx"
        local-data: "www.example.com. 300 IN AAAA fe80::xxxx:xxxx:xxxx:xxxx"

        local-data-ptr: "fe80::xxxx:xxxx:xxxx:xxxx 300 example.com."
        local-data-ptr: "fe80::xxxx:xxxx:xxxx:xxxx 300 www.example.com."

        local-data: "example.com. 300 IN A 192.168.1.x"
        local-data: "www.example.com. 300 IN A 192.168.1.x"

        local-data-ptr: "192.168.1.x 300 example.com."
        local-data-ptr: "192.168.1.x 300 www.example.com."
```

これで`example.com`と`www.example.com`に対してIPv6アドレスとIPv4アドレスの両方を返してくれます。
ディレクトリ`/etc/unbound/unbound.conf.d/`以下にあって拡張子が`.conf`であればファイル名は任意です。

# 気分爽快
デュアルスタックにすると処理量が増えます。
もう一度言います。
デュアルスタックにすると、IPv6とIPv4二重に処理をするため処理量が増えます。
見にくいし、長いし、つまりメリットなんてありません。多分。
それでも、`「俺、IPv6使ってるぜー！」`って爽快感が堪りません。
同じ気分を味わいたい方はぜひ挑戦してみてください。
それと「v6プラス契約してもうたー」って人も参考にしてみてください。

# IPv6化のメリットについて追記
自宅IPに動的DNSを設定してサーバーを開設したものの、ローカルからドメインにアクセスすると**ルーターの管理画面**に繋がってしまい、**IP直打ちだとSSLエラー**になる、といった事情に陥っている方は結構いらっしゃると思います。
私もその一人でしたので内向きDNSサーバーを立ててローカルからのDNSの問い合わせにサーバーのローカルIPを回答するようにしたわけです。
ところが、IPv6化しただけで当該事象が解決したので、これがIPv6化最大のメリットだと感じました。

`Unbound`の記事で`Unbound不要になったぜー`なんて書くのはあほらしいんですが、お役に立てれば幸いです。

### 理屈
`IPv4`ではルーターに`グローバルIPアドレス`が割り振られ、**宅内はローカルIP**のみで管理されていたと思います。
DNSから回答されるのはルーターのグローバルIPアドレスになりますので、WAN側からのアクセスは`NAT転送`で目的サーバーに繋がりますが、**LAN側からルーターの80ポートへのアクセス=>ルーター管理画面**となってしまうのです。

しかし、IPv6では全ての機器にグローバルIPアドレスが設定されますのでローカルのサーバーもグローバルIPを持つことになります。
このサーバーが持っているグローバルIPアドレスをDNSに通知した場合、回答されたグローバルIPアドレスで一意にサーバーへ到達が可能ですので、仮にローカルからのアクセスだとしてもサーバーにたどり着くことができるわけです。
この場合、ルーターで宅外からIPv6のパケットをホスト非公開（セキュリティ上推奨）の設定があったとしても、IPパケットは（宛先はグローバルIPアドレスですが）ローカル内でのやり取りにとどまるので問題なくアクセスが可能です。
グローバルIPアドレスは自動割り振りが基本なのでルーターでのファイアウォール設定がやや複雑になります。
IPv4とのデュアルスタックであれば宅外からのアクセスはIPv4としてNATを活用したセキュリティの維持が可能です。
`IPv6で80と443全開放`などはくれぐれもなさらないで、IPv6で宅外からアクセスを望まれる場合は、MACアドレス等で宛先をサーバーに限定して、ファイアウォールを開けてくださいますようお願いいたします。