Open22

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

Nobuhiro ItoNobuhiro Ito

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

Nobuhiro ItoNobuhiro Ito

目標

  • 自宅の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が動いていて、複数拠点のサーバーを宅内と同じセグメントでアクセスできる
Nobuhiro ItoNobuhiro Ito

dnsmasqだけでできるんじゃないかと思って、ひとまずChatGPTに聞いてみた

Q. dnsmasqで特定ドメインのサブドメインをすべて1つのIPアドレスにポイントすることはできますか?

できる。dnsmasqの設定に以下のように書くと良い。
(サブドメインも含まれるらしい)

address=/example.com/192.168.1.100

Q. dnsmasqをDNS over SSLやDNS over HTTPSとして公開することはできますか?プロキシとして動くソフトウェアを使う等で可能でしたら教えてください。

単体ではできないけど、corednsやcloudflaredでできる。

Nobuhiro ItoNobuhiro Ito

IPv6ってどうだっけ?
ルーター設定確認したところ、GWルーターがDHCPv6として動いていた。
先にDHCPv6をPCルーターに差し替える必要がありそう。

NGN・ひかり電話なしだとRAらしいけど、実際どうなってるんだろう?

Nobuhiro ItoNobuhiro Ito

ざっくり考えている作業

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

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

Nobuhiro ItoNobuhiro Ito

今回の一連のやつで、いまの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
Nobuhiro ItoNobuhiro Ito

フレッツのDNS参照させたかったのでDockerをIPv6に対応させる必要があったがこれが結構苦労した

https://qiita.com/curtain6935/items/4dc7c11740a533a4e8dd

$ 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
Nobuhiro ItoNobuhiro Ito

これで、ルーターに出ていたNGNのDNSアドレスをPi-holeのUpstream DNSに設定すれば引けるようになった。

すでにPCルーターからDHCPで自身をDNSサーバーにする設定は入っている。
あとはGWルーターのIPv6のDNSアドレス通知を停めればいけるはず…!

Nobuhiro ItoNobuhiro Ito

DoHサーバー立てようとする

CoreDNS自体にはDNS over HTTPSをホストする機能がないので、プラグイン込みでビルドする。
https://github.com/v-byte-cpu/coredns-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を終端する必要がある。
https://github.com/m13253/dns-over-https

$ 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。

Nobuhiro ItoNobuhiro Ito

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

Nobuhiro ItoNobuhiro Ito

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
Nobuhiro ItoNobuhiro Ito

このあたりのことをやってみた

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

しかしうまくいかず…400になっている。

Nobuhiro ItoNobuhiro Ito

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のフォーラムに駆け込む