おうちルーターでDNS広告ブロックしたい

リンククリックするだけで契約が発生する的なワンクリック詐欺みたいなサービスが今後出てきそう。
誤ってクリックしないよう、当該サービスへのアクセスを自分が使うインターネットから完全排除しておきたいので、広告ブロックとかができるDNSサーバーを作りたい。

目標
- 自宅のDNSサーバーでDNS広告ブロックができること
- iPhone向けにグローバルでDNS over HTTPSが提供できること
- 誰でも使えるようになるのは避けたいので、認証すること
前提
現在のネットワーク環境
- 自宅側
- フレッツ光ネクスト (マンションタイプ LAN配線方式)
- X.X.X.0/24 で、.1 に一般的なルーター (DHCP OFF、以下GWルーター)がいて、別途Alma Linuxで作ったPCルーターがいる
- GWルーターはAterm
- PCルーターには様々な機能が入ってるけど、現在のネットワーク系機能は以下の通り
- SoftEther VPN Bridge
- DNSサーバー (dnsmasq)
- DHCPサーバー
- net.ipv4.ip_forward によるルーティング (GWルーター)
- DHCPサーバーでは一般端末にはPCルーター自身を指定し、遅延が気になるゲーム機には直接GWルーターを向くようにしている。
- VPNルーター
- さくらのVPS上に存在
- SoftEther VPN Serverが動いていて、複数拠点のサーバーを宅内と同じセグメントでアクセスできる

dnsmasqだけでできるんじゃないかと思って、ひとまずChatGPTに聞いてみた
Q. dnsmasqで特定ドメインのサブドメインをすべて1つのIPアドレスにポイントすることはできますか?
できる。dnsmasqの設定に以下のように書くと良い。
(サブドメインも含まれるらしい)
address=/example.com/192.168.1.100
Q. dnsmasqをDNS over SSLやDNS over HTTPSとして公開することはできますか?プロキシとして動くソフトウェアを使う等で可能でしたら教えてください。
単体ではできないけど、corednsやcloudflaredでできる。

dnsmasq の設定
DNS over HTTPS
先行事例
280blocker のブロックリストをダウンロードできる

Pi-holeというdnsmasqを管理しつつクエリ状況のダッシュボードを出すソフトウェアがあるらしい
これ入れたいなあ

iPhoneにDNS over HTTPSを入れるには構成プロファイルが必要
認証はどうやらクライアント証明書しか使えなさそう…

AndroidではDNS over HTTPSは使えないが、DNS over TLSが使える。
ただし、認証をさせることができないので、そういうのが必要ならVPNアプリ組むしかなさそう。
こういうのは一応ある (が、このアプリを信用できるかがわからないので自分で書きたい気持ち)

IPv6ってどうだっけ?
ルーター設定確認したところ、GWルーターがDHCPv6として動いていた。
先にDHCPv6をPCルーターに差し替える必要がありそう。
NGN・ひかり電話なしだとRAらしいけど、実際どうなってるんだろう?

ざっくり考えている作業
- GWルーターのDHCPv6をPCルーターに差し替える
- Pi-holeをPCルーター上にDockerで構築して、dnsmasqを差し替える
- この時点で家のWi-FiではPi-holeを使えるようになる
- プライベートCAを構築して、クライアント証明書管理するなにかを作る
- VPNルーター上にcorednsを構築する。VPN経路を抜けて家のPi-holeを参照する。クライアント証明書認証する。
- iPhoneに構成プロファイルを書き込む
- この時点で出先でも使えるようになる
- VPN抜けるルートの信頼性が微妙だけど、必要に応じて切り替えできるはずなので出先で障害してそうな状況であれば直参照に差し替える。

DHCPv6停めようが停めまいが、DNSv6サーバアドレス通知設定をOFFにするとIPv6のDNSが伝わらないようになる。
dnsmasqを正しく設定できればこれでいけそう。

今回の一連のやつで、いまのPCルーターのdnsmasqちゃんと動いていないのがわかったので停める。
逆に好都合で、そのままPi-hole入れる。
$ sudo systemctl stop dnsmasq
$ sudo systemctl disable dnsmasq
$ $ ps -ef | grep dns | grep -v grep
dockerは入ってたのでそのまま設定を作る。
$ mkdir -p /opt/pihole
$ cd /opt/pihole
$ vi docker-compose.yml
services:
pihole:
image: pihole/pihole
ports:
- "53:53/tcp"
- "53:53/udp"
- "8053:80/tcp"
environment:
TZ: 'Asia/Tokyo'
volumes:
- './etc:/etc/pihole'
- './dnsmasq.d:/etc/dnsmasq.d'
cap_add:
- NET_ADMIN
restart: unless-stopped
$ docker compose up -d
パスワードを設定する
$ docker compose exec pihole pihole -a -p
Enter New Password (Blank for no password):
Confirm Password:
[✓] New password set
http://[PCルーターのIP]:8053/admin
へアクセスして、ログインする。
手元マシンで確認
$ dig www.google.com @[PCルーターのIP]
; <<>> DiG 9.10.6 <<>> www.google.com @10.26.4.4
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29879
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 113 IN A 142.251.222.4
;; Query time: 20 msec
;; SERVER: 10.26.4.4#53(10.26.4.4)
;; WHEN: Tue Dec 03 22:49:22 JST 2024
;; MSG SIZE rcvd: 59
$ dig aaaa www.google.com @10.26.4.4
; <<>> DiG 9.10.6 <<>> aaaa www.google.com @10.26.4.4
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38221
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.google.com. IN AAAA
;; ANSWER SECTION:
www.google.com. 114 IN AAAA 2404:6800:4004:821::2004
;; Query time: 15 msec
;; SERVER: 10.26.4.4#53(10.26.4.4)
;; WHEN: Tue Dec 03 22:50:27 JST 2024
;; MSG SIZE rcvd: 71

フレッツのDNS参照させたかったのでDockerをIPv6に対応させる必要があったがこれが結構苦労した
$ cat /etc/docker/daemon.json
{
"dns": [
"8.8.8.8"
],
"ipv6": true,
"fixed-cidr-v6": "fd00::/80"
}
$ cat /opt/pihole/docker-compose.yaml
services:
pihole:
image: pihole/pihole
ports:
- "53:53/tcp"
- "53:53/udp"
- "8053:80/tcp"
environment:
TZ: 'Asia/Tokyo'
volumes:
- './etc:/etc/pihole'
- './dnsmasq.d:/etc/dnsmasq.d'
cap_add:
- NET_ADMIN
networks:
- default
restart: unless-stopped
networks:
default:
enable_ipv6: true
ipam:
config:
- subnet: fd00:1::/80
$ sudo systemctl docker restart
$ ip6tables -t nat -A POSTROUTING -s fd00::/80 ! -o docker0 -j MASQUERADE
$ ip6tables -t nat -A POSTROUTING -s fd00:1::/80 ! -o docker0 -j MASQUERADE
systemdでDocker起動後にNAT設定入るようにする
$ cat /etc/systemd/system/docker.v6nat.service
[Unit]
Description=Setup IPV6 NAT after start docker
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
ExecStart=/opt/scripts/docker_ipv6nat.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
$ sudo chmod +x /opt/scripts/docker_ipv6nat.sh
$ sudo systemctl daemon-reload
$ sudo systemctl enable docker.v6nat.service

これで、ルーターに出ていたNGNのDNSアドレスをPi-holeのUpstream DNSに設定すれば引けるようになった。
すでにPCルーターからDHCPで自身をDNSサーバーにする設定は入っている。
あとはGWルーターのIPv6のDNSアドレス通知を停めればいけるはず…!

いけた!
広告リストとかはこちらを参考に投入した
次はDoH

クライアント証明書発行のためのことをちょっと考える
プロファイルサービス
SCEPが必要
改めて調べたらmicromdmのSCEPが使えそう

DoHサーバー立てようとする
CoreDNS自体にはDNS over HTTPSをホストする機能がないので、プラグイン込みでビルドする。
git clone --depth 1 https://github.com/coredns/coredns.git
cd coredns
go get github.com/v-byte-cpu/coredns-https
echo "https:github.com/v-byte-cpu/coredns-https" >> plugin.cfg
go generate
go mod tidy -compat=1.17
GOOS=linux GOARCH=amd64 go build
しかしうまく設定できず…
こちらを使わせてもらう。
HTTPになるので、別途nginxでSSLを終端する必要がある。
$ git clone git@github.com:m13253/dns-over-https.git
$ cd dns-over-https
$ go mod tidy
$ cd doh-server
$ GOOS=linux GOARCH=amd64 go build
出てきたdoh-serverをVPNサーバーにアップロード、以下のように配置した
/opt/doh
doh-server
doh-server.conf
doh-server.conf はこんな感じ。
listen = [
"127.0.0.1:8053"
]
path = "/dns-query"
upstream = [
"udp:[PCルーターのIP]:53",
]
timeout = 10
tries = 3
verbose = false
log_guessed_client_ip = false
ecs_allow_non_global_ip = false
ecs_use_precise_ip = false
試しに起動して確認
$ /opt/doh/doh-server -conf /opt/doh/doh-server.conf &
$ curl -H 'accept: application/dns-json' 'http://127.0.0.1:8053/dns-query?name=example.com&type=A'
{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"example.com.","type":1}],"Answer":[{"name":"example.com.","type":1,"TTL":555,"Expires":"Wed, 04 Dec 2024 16:12:13 UTC","data":"93.184.215.14"},{"name":"example.com.","type":46,"TTL":555,"Expires":"Wed, 04 Dec 2024 16:12:13 UTC","data":"A 13 2 3600 20241224074721 20241203063240 60915 example.com. az9CzhbaOtIB2pwWBzd64SU7oWdIuinCEYKnS/yEUxyOWES17mlhlRPdHygmRa4Zv3DFIbjrA2Oi6Et11bg4VA=="}]}
systemd で自動起動するようにする。
$ sudo vim /etc/systemd/system/doh-server.service
[Unit]
Description=DNS over HTTPS server
After=network.target
[Service]
User=root
ExecStart=/opt/doh/doh-server -conf /opt/doh/doh-server.conf
Restart=always
WorkingDirectory=/opt/doh/
[Install]
WantedBy=multi-user.target
$ sudo systemctl enable doh-server.service
$ sudo systemctl start doh-server.service
あとはnginxでHTTPSリバースプロキシに入れてあげればOK。

でもそれだったらPCルーター側のpi-holeのdocker-composeに入ってたほうが嬉しい気がする。あとで直す

docker-composeに移す
services:
pihole:
image: pihole/pihole
ports:
- "53:53/tcp"
- "53:53/udp"
- "8853:80/tcp"
environment:
TZ: 'Asia/Tokyo'
volumes:
- './etc:/etc/pihole'
- './dnsmasq.d:/etc/dnsmasq.d'
cap_add:
- NET_ADMIN
networks:
- default
restart: unless-stopped
doh:
image: satishweb/doh-server
ports:
- "8053:80/tcp"
environment:
TZ: "Asia/Tokyo"
UPSTREAM_DNS_SERVER: "udp:pihole:53"
DOH_HTTP_PREFIX: "/dns-query"
DOH_SERVER_LISTEN: ":80"
DOH_SERVER_TIMEOUT: "10"
DOH_SERVER_TRIES: "3"
DOH_SERVER_VERBOSE: "false"
networks:
- default
restart: unless-stopped
networks:
default:
enable_ipv6: true
ipam:
config:
- subnet: fd00:1::/80

このあたりのことをやってみた
- nginxでhttpsプロキシでルートを作る
- ルート認証局と中間認証局をopensslで作成。CSRをmacOSのキーチェーンで発行して、中間認証局から証明書発行
- nginxのクライアント証明書認証を設定。ルート証明書だけ読み込ませている。
- 試しに証明書をSafariに登録してアクセスするもアクセスできず。キーチェーン側にもルートまでのツリーが必要で、中間認証局、ルート認証局証明書を登録しておく必要がある
- クライアント認証証明書+鍵(p12)と中間認証局、ルート認証局証明書が入った構成プロファイルをApple Configuratorで作成
- その内容にIIJ Public DNSのプロファイル を参考にしたDNS設定ペイロードを書き足す
- PayloadCertificateUUID をDNSの構成に追加して、p12のPayloadUUIDを指定する。
- プロファイルをインストールして、DNSに追加した構成に設定
しかしうまくいかず…400になっている。

DNS over TLSしかサポートしてない説

VPNルーターにstunnelを立てて、Pi-honeのDNS over TCPをそのままDNS over TLSにしてみてもクライアント認証が使われない…
iPhoneのコンソールログをみるとこんなのが出ててクライアント証明書を正しく参照できていない
Connection 3742: received response for client certificates (-1 elements)
Connection 3742: providing TLS Client Identity (-1 elements)
なんで-1になるのか
どうしようもないのでAppleのフォーラムに駆け込む

WWDC22によればクライアント証明書認証はDoT/DoH両方サポートしているはずらしい
自力でアプリ書いたらいけるかも…
これとかすごいきれいだからここに足したらって思ったけど、きれいすぎて下手に手を入れるのが申し訳なくなってしまって二の足踏んでる…