モチベ
NextDNSという広告などを弾いてくれるDNSがあり、1年ほどセットして運用してみたのですが、大変快適だったので、似たようなものを自分でも運用してみたくなった、というのが今回のモチベーションです。
今回の構成
サーバアプリケーションはなんでもよかったのですが、プロキシ構成が楽かなと思ったのでblockyを選択しました。上流で使うDNSは、Quad9とmozilla.cloudflare-dns.comを選択しています。
難しかったのはblockyまでの経路です。まずこのDNS経路を暗号化したかったため、それに対応した規格を使いたいという要求がありました。次に、DNSサーバを生やす予定の自宅サーバは普通の家庭のネットワークで、固定IPが振られているわけではなかったので、Cloudflare Tunnelなどの対策が必要な環境でした。既にWeb用途でCloudflare Tunnelは導入済で、Cloudflare Tunnelの設定を生やすのが楽だろうとなったため、DNS over HTTPS(以後DoHと呼ぶ)を選択しました。Cloudflare Tunnelでクライアント側にcloudflaredを立てない場合はHTTP/HTTPSしか選択肢がないのです。
クライアントはまず最低でもAndroid、ついでにDesktop環境のLinuxにも導入する前提で検討しました。AndroidはRethinkDNS、Linuxは最終的にdoh-client.serviceを使った構成になりました。
blocky構築
ArchLinuxだとblocky-binというパッケージがあるのでこれを導入しました。最初blockyというパッケージを使っていたのですが、blockyはバージョンがちょっと古く、ports.dohPathが使えなかったためblocky-binにしました。他のディストリ事情は分からないですが、最新であることを確認し、そうでなければdockerなどの使用を検討した方がいいです。
設定(/etc/blocky/blocky.yml)はいくつかキーポイントを挙げると、
- ports.httpでhttp待ち受けポートをセットする。TLS終端はCloudflareがやってくれるので要らない
- ports.dohPathをセット。DoHは基本的に/dns-queryで待ち受けるんですが、これを変えることができます。後述するのですが、public DNSとして運用したくないのでパスにランダム文字列突っ込むことで暫定的にセキュリティを担保する仕組みにしました
- upstreams.groups.defaultで上流DNSを設定できます
- blocking.blackLists.adsでblockするリストを取得するURLを設定します。結構雑にChatGPTに聞いてコピペしたので適当ですが、広告、トラッキング、日本語対応の広告のそれぞれでメジャーなのを1〜2個指定するといい気がします
Cloudflare設定
ぼく自身があまりCloudflare分かってないので詳細な説明は省くんですが、
- /etc/cloudflared/config.ymlにhostnameと先に書いたHTTPの待ち受けポートをセット
- hostnameのDNS設定にCNAMEでTunnelのドメイン名に向ける。CNAME host Tunnel-UUID.cfargotunnel.comなど
- 普段のWebサービスで使うZero Trust(Access)の設定はそのままでは使えません。設定すると302で別ホストへリダイレクトされますが、DoHクライアントは仕様上それに追従できずエラーになります。認証が必要なら別の方式が必要です
Linuxクライアントの設定
簡単だろうと思っていたのに地味に面倒くさくてびっくりしました。
うちのLinuxクライアントはArchLinux + NetworkManagerという構成で動いているんですが、NetworkManagerは単体でDoHを設定できません。
まずNetworkManagerがリゾルバを設定する機能を無効化します。/etc/NetworkManager/conf.d/dns.confなどのファイルを作り、
[main]
dns=none
と書くだけです(conf.dなので設定ファイルを置けば後でマージしてくれそうです)
次にdoh-client.serviceというプロキシを立てます。普通にDNSプロキシとして動作し、53番ポートで待ち受けるようになります。設定は/etc/dns-over-https/doh-client.confにあって、upstreamでDNSへのURLを書きます。
最後にdoh-client.serviceを参照させるために/etc/resolv.confに設定を書きます。NetworkManagerのリゾルバ設定機能を無効化しないと、こいつを書き換えても戻されます。doh-client.serviceは127.0.0.1で待ち受けているので、普通にnameserver 127.0.0.1を設定するだけです。
※doh-client.service立てた後にNetworkManagerで個別の接続先のDNS設定を127.0.0.1などに設定する方が楽かもしれないんですが、接続先が多いと逆に面倒かもしれないです。
追記
後で発覚したのですが、ArchLinuxでNetworkManager + doh-client.serviceの組み合わせだと自動起動に失敗するようです。凄いレアなものを引いてしまった。
問題と解決策は大体ここに書いてあるとおりです。
実はこの件issueが立てられていて、↑のWikiを追記して解決とするしかなかったようです。とてもつらい。
Androidの設定
Android単体ではDNS over TLS(DoT)には対応しているんですが、DoHには対応してないのでなんらかのアプリが必要です。しかしRethinkDNSを使えばとても簡単です。常駐が要るので若干煩わしいですが。Mozillaの資金支援を受けているので、信頼性も折り紙付き。設定はGUIでぽちぽちするだけなので特筆すべき点はないかな…。
セキュリティの話
セキュリティの絡みもあり、public DNSとして運用したくないので、なんらかの制限をかける必要があります。しかしあんまりうまい方法が思い付かなくて、結局32文字のパスワード文字列をパスに付与して解決したことにしました。
最初サブドメインでやろうとしたんですが、証明書の公開ログ(CT)でドメインの存在は公になるらしく、セキュリティ担保するのは難しそうとなりパスになりました。
本当は証明書などで解決するのがよいと思うんですが面倒くさくなってやめました。次回以降の課題とします。
終わった後の雑感
VPNでよくない?
正直これはありますね。ChatGPTと会話しながら構成検討してるときずっと言われ続けてました。VPNにしなかった理由を敢えて挙げるなら、サーバにblockyとcloudflared以外のアプリが常駐するのを嫌がった、が最大の理由ですが、VPNだと通信量が増えるという問題もありますね。
DNSの暗号化辛くね?
そもそも割と標準的な技術スタックがデフォルトで対応していないパターンが多いです。当然もっと普及させるにはそれがデフォルトになってないといけないし、当然DHCPで配れないといけないとかもあって正直ハードルは高そうと感じます。