Unboundでお手軽DNSシンクホール

背景 見出しにジャンプ
以前からルータのDNSフォワーダにDNSシンクホールをさせていた。
しかし近年、ChromiumやAppleデバイスでブロック漏れが発生する理由に、対応していないクエリタイプ(SVCB/HTTPS)があることや、FQDN単位でのルールしか書けないためにサブドメイン対応が冗長だったこと、CNAME Cloakingを阻止できないことからDNSソフトウェアの利用は視野に入れていた。
YAMAHA RTXと異なり、NEC IXルータではクエリタイプ毎にフォワード先を切り替えられないため、検討が加速した。BINDやknot、PowerDNSがある中、初学者にも書きやすそうな縛りのない構文が魅力的と感じてUnboundを選んだ。
結果的にはソースコードの変更を要したが、満足に機能している。
更新履歴 見出しにジャンプ
日時 | 修正・追加内容 |
---|---|
2024-08-15 |
|
2024-08-26 |
|
2024-09-19 |
|
2024-09-30 |
|
2024-12-26 |
|
2025-02-12 |
|
2025-03-31 |
|
2025-04-16 |
|
2025-08-25 |
|
ソースコードの変更 見出しにジャンプ
git clone https://github.com/NLnetLabs/unbound.git -b release-1.21.0rc1 -o upstream
- forward-addrのCNAME Scrubbing動作を止める。フォワード処理の再起の必要性について議論したIssueは紛糾したまま閉じられてしまったので、公式に対応することはないだろう。
- if(!scrub_message(pkt, prs, &iq->qinfo_out, iq->dp->name,
- qstate->env->scratch, qstate->env, qstate, ie)) {
- /* if 0x20 enabled, start fallback, but we have no message */
- if(event == module_event_capsfail && !iq->caps_fallback) {
- iq->caps_fallback = 1;
- iq->caps_server = 0;
- iq->caps_reply = NULL;
- iq->caps_response = NULL;
- iq->caps_minimisation_state = DONOT_MINIMISE_STATE;
- iq->state = QUERYTARGETS_STATE;
- iq->num_current_queries--;
- verbose(VERB_DETAIL, "Capsforid: scrub failed, starting fallback with no response");
- }
- iq->scrub_failures++;
- goto handle_it;
- }
または
int
scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
struct query_info* qinfo, uint8_t* zonename, struct regional* region,
struct module_env* env, struct module_qstate* qstate,
struct iter_env* ie)
{
~~
/* normalize the response, this cleans up the additional. */
if(!scrub_normalize(pkt, msg, qinfo, region, env))
return 0;
/* delete all out-of-zone information */
- if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate))
- return 0;
return 1;
}
cd unbound
git remote add PRblockaaaa https://github.com/strlcat/unbound.git
git cherry-pick 026af6b
ビルド 見出しにジャンプ
依存関係を解消
Installation — Unbound 1.19.3 documentation
DNSアプリの機能比較 on Rocky8編 - 20230623-kosaka.pdf
sudo dnf install git tar gcc make openssl openssl-devel expat-devel systemd-devel flex bison
sudo dnf install libevent-devel # outgoing-port-permitが1024を超えているので必要
sudo dnf install clang # gccの代わりにclangでコンパイルしたいなら
systemdあり, 1024ポート制限超え--with-libevent
有効でビルド
【学習メモ】unboundをインストールして最適化と言われる内容を適用する #unbound - Qiita
build with --enable-fully-static
cannot specify username
· Issue #886 · NLnetLabs/unbound
[Unbound-users] unbound performance tuning
Performance Tuning — Unbound 1.19.3 documentation
RHELでは-O2
がデフォルト?
Exploring x86-64-v3 for Red Hat Enterprise Linux 10 | Red Hat Developer
cd unbound
./configure --help
# --enable-subnet: response-ip-dataを壊す?
# --disable-explicit-port-randomisation: 遅い?
./configure --enable-systemd --enable-static --enable-fully-static --with-libevent # CFLAGS="-march=native -O3" CC=clang CXX=clang++
make -j4
インストール 見出しにジャンプ
sudo make install
cd contrib
sudo cp unbound.service unbound.socket /usr/lib/systemd/system/
CentOS8でunboundをソースからコンパイルして使う #dns - Qiita
サービスの起動 見出しにジャンプ
unbound.confを触らないでunboundを立ち上げても、DNSSECのトラストアンカーを更新するunbound-anchor.service
が実行されていないため、おそらく起動しない。
今回のユースケースでは、DNSSECを無効化で対応した。
useradd unbound
sudo systemctl daemon-reload
sudo systemctl enable --now unbound
sudo systemctl status unbound
OS再起動時にunboundが起動しない 見出しにジャンプ
error: can't bind socket: Cannot assign requested address for...
これで壊れて
- Fix #773: When used with systemd-networkd, unbound does not start · NLnetLabs/unbound@5c041c0 · GitHub
これで直った
- For #773: In contrib/unbound.service.in set unbound to start after · NLnetLabs/unbound@d43760a · GitHub
感謝人人
StartLimitBurst
を超過しないために、RestartSec
を伸ばす回避策もあったようだ。
私的サーバー構築日誌:LAN内DNSサーバー Unbound #dns - Qiita
Systemd Restart=always is not honored - Unix & Linux Stack Exchange
network-online.targetがIPv6より早くてPort Unreachable 見出しにジャンプ
ECSを使用する場合など、下記のようにIPv6 GUAでクエリが到達しうる構成では、システム再起動後にPort Unreachableを返すようになり、DNSクエリに対して応答しないことがある。
server:
interface: eth0@53
interface: 2001:db8:b0ba:10ee::53@53
最近のsystemd-networkd-wait-online自体はデュアルスタックを評価するはず。
beef up systemd-networkd-wait-online to wait for ipv4 or ipv6 or both addresses · Issue #16700 · systemd/systemd
しかし、GUAのNDが遅い(例えば、フレッツIPv6オプションでは収容ルータとのやり取りになるので)と間に合わないのかもしれない。
という訳で、回避策は下記の通り。
[Service]
ExecStartPre=/bin/sleep 5
SELinux 見出しにジャンプ
SELinuxが有効な環境で、Unboundには下記のようなエラーが発生すると思う。
journalctl -xeu unbound.service
# unbound.service: Failed to set up special execution directory in /var/lib: Permission denied
# unbound.service: Failed at step STATE_DIRECTORY spawning /usr/local/sbin/unbound: Permission denied
実際、UnboundにおいてもSELinuxを寛容モードに変更するとエラーは無くなった。
getenforce
# Enforcing
# permissiveモード
sudo grubby --update-kernel ALL --args enforcing=0
getenforce
# Permissive
Changing SELinux States and Modes :: Fedora Docs
SELinuxの設定ファイル/etc/selinux/config
や公式ドキュメントでは、無効化selinux=0
と再度有効化するコマンドが平然と紹介されているが、後者は二度とOSが立ち上がらなくなる点に留意したい。
「SELinuxのせいで動かない」撲滅ガイド #CentOS - Qiita
ソフトウェアは本来のLinuxの動作に基づいてエラーを処理するため、誤った動作をするSELinux環境は、エラーメッセージがアテにならない。監査ログ/var/log/audit/audit.log
と併せて読む必要がある。
Fedora ServerにプリインストールされているCockpitは、unboundをSELinuxから除外するソリューションを提案した。これによりUnboundにSELinuxが強制されなくなるため、SELinuxを強制することができた。
sudo ausearch -c '(unbound)' --raw | audit2allow -M my-unbound
sudo semodule -X 300 -i my-unbound.pp
# Enforcingに戻す
sudo grubby --update-kernel ALL --remove-args enforcing
アンインストール 見出しにジャンプ
sudo systemctl stop unbound
sudo make uninstall
sudo rm -r /usr/local/etc/unbound
firewalld 見出しにジャンプ
端末がUnboundへクエリする、内向きのトラフィックを許可する。外向きは通常許可されている。
# Do53 53/tcp 53/udp
sudo firewall-cmd --add-service=dns --permanent
# DoT 853/tcp
sudo firewall-cmd --add-service=dns-over-tls --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
これに加え、DNSソフトウェアがリプレイ攻撃の踏み台にならないよう、Ingress Filteringを徹底し、Unboundへ到達しうるクエリの送信元アドレスの真正性を担保する。access-controlのデフォルト動作であるSERVFAIL応答はDoSを軽減するが、攻撃を防ぐことはない。
また、キャッシュDNSなのでIPv4 NAPTやIPv6 SPIですみやかに5tupleを閉じ、再帰クエリへの正規の応答だけを許可するとよい。Preferred Lifetimeの短いPrivacy Extentionの5tupleを当てるのはほぼ不可能だろうが。
隠れオープンリゾルバーの放置は、クラッカーの攻撃行為に加担しているという事を自覚した方がいい。
— ǝǝqoɹnʞ 👇️🍥👆️ kurobee (@kurobee_dev) May 5, 2023
リンク先で指摘されてる政府組織、ユーザー企業や学校さんはベンダーに対策を要求してかないと駄目。
ネットワーク事業者は頑張って・・・https://t.co/URCAQy9hCb pic.twitter.com/5tP4qAzck3
カーネルパラメータ 見出しにジャンプ
カーネルパラメータをIPv6 Do53向けにチューニングしていく。
- TC=1な再帰クエリでポートが枯渇しないように、TIME-WAITな5tupleを即座に再利用する
net.ipv4.tcp_tw_reuse = 1
- 再帰クエリの送信元IPv6アドレスをランダム化し、高頻度でローテーションする
# クエリ送信元に、RFC3041 Privacy Extentionsを使う(デフォルト無効) NetworkManagerやsystemd-networkdで設定が必要な場合が多い net.ipv6.conf.all.use_tempaddr = 2 # 一時アドレスを切り替える間隔を1時間にする(デフォルト1日) DESYNC_FACTORがあるのでピッタリこの間隔で再生成される訳ではない net.ipv6.conf.all.temp_prefered_lft = 3600 # 一時アドレスの有効期間を2時間に(デフォルト7日) net.ipv6.conf.all.temp_valid_lft = 7200 # インタフェース上の一時IPv6アドレスを保持しない(デフォルトで保持しない) net.ipv6.conf.all.keep_addr_on_down = -1 # net.ipv6.conf.all.max_addresses = 16
- NetworkManagerの場合
# net.ipv6.conf.all.use_tempaddrにフォールバックさせないと、temp_prefered_lftが効かない # sudo nmcli connection modify eth0 ipv6.ip6-privacy -1 sudo nmcli connection modify eth0 ipv6.ip6-privacy 2 # 内向きのアクセスに固定のIPv6 GUAが必要なら生成する。Unboundが利用する一時アドレスとは別 # stable-privacyでは一時アドレスが生成されないか、入れ替わらない # sudo nmcli connection modify eth0 ipv6.addr-gen-mode 0 sudo nmcli connection modify eth0 ipv6.addr-gen-mode 1 sudo systemctl restart NetworkManager ip addr show # global temporary dynamic
ipv6: NetworkManager Reference Manual - systemd-networkdの場合(Proxmox LXCなど)
[Network] IPv6PrivacyExtensions = true IPv6DuplicateAddressDetection = 0
systemctl restart systemd-networkd ip addr show
- NetworkManagerの場合
- 個人的にIPv6マルチキャストが好きじゃないので減らす
# (MLD Snooping対応機器を持っていないので)DADを送らない。UUIDは被らない net.ipv6.conf.all.dad_transmits = 0 # リンク解決は一回でやれ net.ipv6.neigh.all.mcast_solicit = 1
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
似たようなふるまいとしてip-freebind: yes
はクエリ毎にランダムなSuffixになるが、SLAACに非対応。おそらくPrefix Delegation前提。
outgoing-interface randomization needs sysctl ip_nonlocal_bind=1 on Linux or TCP queries fail which breaks DNSSEC · Issue #933 · NLnetLabs/unbound
unbound.conf 見出しにジャンプ

NLnet Labs Documentation - Unbound - unbound.conf.5
パッケージマネージャから導入すると/etc/unbound/unbound.conf
、自ビルドしたunboundは/usr/local/etc/unbound/unbound.conf
をデフォルトで使う。
この記事では後者に寄せて書いている。
FreeBSD に於ける/etc と /usr/local/etc
configの更新はunbound-control reload
で行えるが、一部systemctl restart unbound
を要する。
unbound-checkconf
で設定に誤りがないか事前に確認できる。
unboundconf/update.sh at main · nyanshiba/unboundconf
ログ 見出しにジャンプ
Unboundをsystemdで動かしている場合、ログはJournaldに記録される。これをPromtailなどのexporterでデータベースに送信し、Grafanaで監視できる。
server:
# logfile: ""
# use-syslog: yes
log-servfail: yes
SERVFAILを記録すると、ネットワーク層のトラブルやいくつかの廃棄されたドメイン名、政府効率化庁(DOGE)のエンジニアがHTTPSレコードを知らないことなどを観測できる。
# ログ抑制
local-zone: "pubmed.ncbi.nlm.nih.gov." typetransparent
local-data: 'pubmed.ncbi.nlm.nih.gov. 60 IN HTTPS 1 . alpn="h2"'
unbound-control stats 見出しにジャンプ
unbound-controlには統計機能があり、クエリタイプやキャッシュヒット数、応答遅延など様々な指標を取得できる。

可視化はGrafanaでって言ったよね - 俺の外付けHDD
server:
# statistics-cumulative: no
extended-statistics: yes
remote-control:
control-enable: yes
control-use-cert: no
NLnet Labs Documentation - Unbound - unbound-control.8
DNSSECの無効化 見出しにジャンプ
キャッシュDNSソフトウェアとして、unbound.confのデフォルトでDNSSEC検証が有効になっている。
DNSSECを始めとする大きな応答はAmp攻撃の格好のリフレクタだが、キャッシュDNSにおいてはネットワークが正しく設計されている(BCP38/84)とき、サービスを提供する範囲access-control
にのみ影響する。
RFC 4732 - Internet Denial-of-Service Considerations
しかしDNSフォワーダとしてUnboundを利用するユースケースでは、例えばISPのキャッシュDNSがDNSSEC検証に対応していないし、local-zoneやRPZで応答を改ざんすると機能しないので、無効化した。
server:
# DNSSEC OKじゃない
- # disable-edns-do: no
+ disable-edns-do: yes
# validatorモジュールの無効化
- module-config: "ipsecmod validator iterator"
+ module-config: "iterator"
# トラストアンカーの削除
- auto-trust-anchor-file: "/usr/local/etc/unbound/root.key"
- auto-trust-anchor-file: "/var/lib/unbound/root.key"
- trusted-keys-file: /etc/unbound/keys.d/*.key
Unbound - DNSSECを無効にする方法 | 日本Unboundユーザー会 2. 3.
NLnet Labs Documentation - Unbound - Howto Turn Off DNSSEC
Listenインタフェースの設定 見出しにジャンプ
https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html#unbound-conf-interface
server:
interface: 10.0.3.5@53
port: 53
# do-udp: yes
# do-tcp: yes
incoming-num-tcp: 1024 # libeventなしの場合、*-num-tcpの合計<=125
- ここで指定したインタフェースのIPアドレスをDHCP、IPv6アドレスをDHCPv6やRA RDNSSオプションで通知することで、端末はUnboundで名前解決するようになる。フレッツ東のHGWは、DHCP機能で任意のDNSサーバを通知できないらしい。
暗号化DNSはDDRやDNRで自動設定する。 - IPアドレスのほか、
eth0
などのインタフェース名にも対応している。RHEL系では0.0.0.0
::0
は使えない。 - ブラウザエンジンのシェアの殆どを占めるChromiumは、UDPフラグメントに関係なく、ソースポートやtransaction IDのエントロピーが低いとTCPにフォールバックする。
Huge amount of "ERR_NAME_NOT_RESOLVED" error [40891645] - Chromium
標準的なブラウザ(Firefox)にこのような実装はないが、接続性のためネットワーク側での対応は必要だ。
Unboundはincoming-num-tcpのデフォルト値が非常に小さいので注意
summerday2023-chrome - 20230623-yamaguchi.pdf
日本DNSオペレーターズグループ | DNS Summer Day 2023
https://github.com/NLnetLabs/unbound/issues/366#issuecomment-844165241
そもそもプライベートネットワークでListenするDNSサーバ宛にTCPを強制する意味はほぼないし、NAT動作によっては内側ポートをトリガーにするだけでは不十分だ
access-control-tag 見出しにジャンプ
dig @dns.home.arpa example.com +noall +comments
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 30804
;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; EDE: 18 (Prohibited)
access-control:
で許可するかaccess-control-tag:
に書かれた送信元と、localhostだけがUnboundにクエリできる。上記の通りREFUSED応答を返すアプリケーション層の保護のため、オープンリゾルバにならないためのアクセス制御はネットワーク側で行う。
local-zone-tag:
やrpz-tag:
など、一部の端末にDNSシンクホールを適用するときに使うタグを、予め定義する。
server:
define-tag: "dualstack primary probe secondary guest linux lowbandwidth"
# access-control: 10.0.0.0/20 allow access-control-tag:にあればallowされる
access-control-tag: 10.0.0.0/24 "dualstack primary"
access-control-tag: 10.0.0.12/32 "primary lowbandwidth"
access-control-tag: 10.0.1.0/24 "dualstack probe"
access-control-tag: 10.0.2.0/24 "dualstack secondary"
access-control-tag: 10.0.3.0/24 "guest"
access-control-tag: 10.0.4.0/24 "linux"
access-control-tag: 10.0.5.0/24 "dualstack primary linux"
access-control-tag: 10.0.6.0/24 "dualstack primary linux"
access-control-tag: 10.0.7.0/24 "dualstack primary linux"
access-control-tag: 10.0.8.0/24 "dualstack primary linux"
access-control-tag: 10.0.8.80/32 "primary linux"
access-control-tag: 10.0.9.0/24 "dualstack primary linux"
access-control-tag: 10.0.15.0/24 "linux"
この例では、小さいIP範囲10.0.0.12/32
が優先されるが、大きいIP範囲10.0.0.0/24
のタグを継承しないので、primary
も含めて冗長に書く必要がある。
逆に書かれていないdualstack
は適用されない。
interface-tag 見出しにジャンプ
PVLANといった、同じセグメント内にセキュリティ境界がある場合、access-control-tagではアクセス制御できない。
リンクローカルアドレスさえあれば機能するDHCPv6を利用して、PVLANインタフェース毎に異なるinterface:
にクエリさせられる。
interface-*
よりaccess-control*
が優先されるので、前者をPVLAN、後者をVLAN用に使い分けられる。
server:
define-tag: "dualstack primary secondary"
interface-action: 2001:db8:b0ba:10ee::53@53 allow # interface-tag:だけではREFUSED
interface-action: 2001:db8:b0ba:10ee::35@53 allow
interface-tag: 2001:db8:b0ba:10ee::53@53 "dualstack primary"
interface-tag: 2001:db8:b0ba:10ee::35@53 "dualstack secondary"
Privacy Extentionを有効にしたインタフェースごと指定interface-action: eth0@53
すると、下記エラーでクラッシュして使えなかった。
fatal error: Could not setup interface control list
error: cannot update ACL on non-configured interface: 2001:db8:b0ba:10ee:1234:5678:9123:4567@53 53
- Linuxの設定状況
[Network]
IPv6PrivacyExtensions = true
IPv6DuplicateAddressDetection = 0
IPv6Token=::53
IPv6Token=::35
forward-addrはlongest matchではなく、一時アドレスが優先される
ip addr show
# 2: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
# inet6 2001:db8:b0ba:10ee:1234:5678:9123:4567/64 scope global temporary dynamic
# valid_lft 7095sec preferred_lft 3364sec
# inet6 2001:db8:b0ba:10ee::53/64 scope global dynamic mngtmpaddr noprefixroute
# valid_lft 2591898sec preferred_lft 604698sec
# inet6 2001:db8:b0ba:10ee:9876:5432:1987:6543/64 scope global temporary dynamic
# valid_lft 7095sec preferred_lft 3364sec
# inet6 2001:db8:b0ba:10ee::35/64 scope global dynamic mngtmpaddr noprefixroute
# valid_lft 2591898sec preferred_lft 604698sec
# ...
ipv6 dhcp server-profile dhcpv6-sv-0
dns-server 2001:db8:b0ba:10ee::53
!
ipv6 dhcp server-profile dhcpv6-sv-2
dns-server 2001:db8:b0ba:10ee::35
!
interface GigaEthernet2.0
description mgmt
ipv6 enable
ipv6 dhcp server dhcpv6-sv-0
!
interface GigaEthernet2.2
description home
ipv6 enable
ipv6 dhcp server dhcpv6-sv-2
!
DSCP 見出しにジャンプ
WLAN区間で、端末へのDNS応答がAC_VIに分類され優先されるよう、L3マーキングする。
server:
ip-dscp: 45
これは再帰クエリもマーキングしてしまうので、フレッツ環境ではマークダウンする必要がある。
EDNS buffer size 見出しにジャンプ
DNSのペイロードサイズは512 bytesまでだったが、EDNS拡張(頭痛が痛い?)により4096 bytesまで許可された。IPv4は理論値1472 bytes、IPv6は1452 bytesを超えたらフラグメントするしかないが、TCPフォールバックのオーバーヘッドを嫌ってかRFC 6891はMTUを考慮しない最大値を推している。
しかし実際のネットワークでは、UDPのフラグメントは再構成のオーバーヘッドはおろか、接続性やセキュリティやの問題があると分かっている。
IPv6 Fragmentation Failure Rate: 56%
3-slides-119-iepg-sessa-is-the-dns-ready-for-ipv6-geoff-huston-00.pdf
server:
# edns-buffer-size: 1232
UnboundのデフォルトはDNS flag dayで提案された1232 bytesで、IPv6の最小MTU 1280 bytesからUDPヘッダ 48 bytesを除いたデータグラムのサイズを示している。DNSSECを使っていない状態では、1232 bytesを超える応答は殆どなく、パフォーマンスへの影響はない。
EDNSを考慮していない機器との互換性をとって512 bytesとするのもよいだろう。1220 bytes以下のBufsizeでは、DNSSECを無効化が必須だ。
draft-ietf-dnsop-avoid-fragmentation-16
dig の適切な使い方 (10) モダンなサーバ (EDNS 対応) のポイント
IPv6でNGNのキャッシュDNSにフォワードするだけなら、大きなBufsizeを設定してもフラグメントは起こらないはずだ。
DNS 2XL | blabs
dns-troubleshoot.pptx - dns-troubleshoot-1.pdf
QUICもUDPのフラグメントを回避するために、小さいデータグラムを使用している。この方面からPMTUDが発展すると期待したい。
QUIC Analysis - A UDP-Based Multiplexed and Secure Transport
キャッシュサイズ 見出しにジャンプ
pthreadsの代わりにfork()でコンパイルした場合は、キャッシュが独立し、設定値をスレッド数で掛けたキャッシュサイズになる。
[Unbound-users] unbound performance tuning
serve-expired 見出しにジャンプ
UnboundをDNSフォワーダとして利用するとき、DNSキャッシュがある場合は1桁msオーダーで名前解決が完了する。しかし、キャッシュが切れたときは少なくともRTT、CNAMEの場合はチェーン数xRTTだけ遅延してしまう(ソースコードの変更で常に2RTT)。
そこで、TTLが切れてもserve-expired-ttl
間キャッシュを残し、短いTTLserve-expired-reply-ttl
で応答させてしまう。RFC 8767は可用性が主なので既定ではserve-stale動作serve-expired-client-timeout: 1800
だが、0
で応答してからキャッシュを更新する動作となる。
RFC 8767 - Serving Stale Data to Improve DNS Resiliency
Serving Stale Data — Unbound 1.23.0 documentation
- Fix #1175: serve-expired does not adhere to secure-by-default · NLnetLabs/unbound@eefdbb3
これにより、頻繫にアクセスするサイトの名前解決待ち時間はほぼゼロになる。
server:
# prefetch: no
serve-expired: yes
# serve-expired-ttl: 86400
# serve-expired-ttl-reset: no
serve-expired-reply-ttl: 60
serve-expired-client-timeout: 0
# serve-original-ttl: no
無視できるRTTとはいえ、serve-expiredは端末-キャッシュDNS間のクエリを倍増させるため、バッテリーパフォーマンスは低下する恐れがある。権威DNSの応答TTLよりserve-expired-reply-ttl
が常に下回っていればクエリ数は増加しないとはいえ、TTLを改変しすぎると通常起こり得ない浸透問題の原因になるので、Public DNSの調査に基づき1分に増やす程度に留めた。
1.1.1.1へのDNSクエリ 全世界 | Cloudflare Radar
AdGuardHomeにも搭載されている。
Optimistic caching - what is it? - Help / Community Help - Pi-hole Userspace
id.server 見出しにジャンプ
クエリクラスがINではなくCHなことに留意する。
dig id.server. ch txt +noall +answer
dig hostname.bind. ch txt +noall +answer
# id.server. 0 CH TXT "fedora"
dig version.server. ch txt +noall +answer
dig version.bind. ch txt +noall +answer
# version.server. 0 CH TXT "unbound 1.20.0"
dig trustanchor.unbound. ch txt +noall +answer
ISPのキャッシュDNSではREFUSEDされたので、模倣した。
server:
hide-identity: yes
hide-version: yes
hide-trustanchor: yes
forward-addr 見出しにジャンプ
unboundは大前提としてフルリゾルバのためのソフトウェアだが、local-zoneやRPZを利用するだけのユースケースにも対応している。
forward-addrを設定すると、CPEが搭載しているようなDNSフォワーダ/プロキシのように、特定のキャッシュDNSにFQDNを問い合わせる。
IPv4でDNSを使える環境は限られる 見出しにジャンプ
IPv4共有技術下でのIPv4 DNS利用は、ポート枯渇のリスクがある。IPv4/IPv6接続判定ツールでv6プラス判定が出たり、MAP-E展開にこなれていないISPをお使いなら、8.8.8.8
や1.1.1.1
を使うべきでない。NATタイマやBehaviorを調整できるなら止めないが、不自由な環境も多い。
フレッツのIPv6 IPoEおよびIPv4 over IPv6では、キャッシュDNSはIPv6のみ提供され、IPv4は提供されない。そもそもIPv4/IPv6デュアルスタックの中核となるDNSをIPv4延命技術に依存していたら、それはIPv6への移行でもなんでもなく障害点が増えただけのIPv4&IPv6キメラスタックだ。該当する環境は、IPv6で問い合わせよう。
些細なことを言えば、Do53のクエリ毎にNAPTキャッシュ生成&ショートパケットをencapslationしながらブラウジングしたいかという話でもある。IPv4 PPPoEにパケットを流すのに比べて、IPv4 over IPv6では気を遣ってしまう。
Public DNSを使うべきか 見出しにジャンプ
フォワード先の候補には、ISPが提供するキャッシュDNSや、GoogleやCloudflareなどが提供するPublic DNSが挙げられる。
パフォーマンスの観点でいえば、ISPのDNSが適切に運用されていないとき、後者はときによい。
「速い」という言い方はミスリードだけど、ISPやルータのリゾルバDNSが諸般の事情で腐ってタイムアウト連発とか時折あるので、public dns 全否定も違うかなぁと思ったり。
— Shirouzu Hiroaki(白水啓章) (@shirouzu) July 29, 2024
(ISPですらDNSにDDOS食らって一時的に google DNS誘導することあったり) https://t.co/9gYwdHvAiL
ISPのDNSキャッシュサーバはTTLを越えてキャッシュを保持するか?
ただしプライバシー面でいえば、日本では通信の秘密が保障されているし、セキュリティの観点では攻撃面は同一ASからthe Internetに広がる点には留意したい。特にDNS over 53(Do53)(Cleartext)でクエリすれば、AS外のインターネット区間で中間者攻撃される恐れがあるため、Public DNS利用時はDNS over TLS(DoT)やDNS over HTTPS(DoH)が望ましい。
8.8.8.8に対するBGPハイジャックの話:Geekなぺーじ
DoT、DoHはUDPを使わずDNS ampの踏み台に利用される恐れがないため、オープンリゾルバとしてIIJ以外のユーザにも開放
4. フォーカス・リサーチ(3)IIJとDNSの30年 | Internet Infrastructure Review(IIR)Vol.59 | IIJの技術 | インターネットイニシアティブ(IIJ)
ISPのDNSで機能するDo53に比べ、Public DNSで利用できるDoTやDoHは常にオーバーヘッドが課されるが、TCPやHTTP/2のフロー制御が効いてくるシーンもある。
残念ながらUnboundで利用する場合、端末はDo53でクエリを繰り出すので、ブラウザでDoHを利用するときのようなパフォーマンス上の恩恵はない。
getaddrinfoはブロッキングコールだからスレッドプール経由でマルチスレッドからの一定並列度での呼び出しになるけど、DoHならHTTP/2で確立済の単一接続にドバッと多数のクエリを丸ごと流せるみたいな https://t.co/UviFhHTpev
— Kazuho Oku (@kazuho) July 30, 2024
[1907.08089] Comparing the Effects of DNS, DoT, and DoH on Web Performance
server:
edns-tcp-keepalive: yes
forward-zone:
name: '.' # 全てのゾーンをフォワードする
forward-addr: 2606:4700:4700::1111@853 # Cloudflare DNS via DoT
forward-addr: 2001:4860:4860::8888@443 # Google Public DNS via DoH
forward-tls-upstream: yes
UnboundにもTCPコネクションの再利用tcp-reuse-timeout: 60000
は一応あるが、DoTは(num-threads: 1
でも)コネクションを度々切断していた。DoHは都度コネクションを張っていたのでオーバーヘッドになりそうだ。
Unbound 1.13.1 .. 1.16.1 memory leak (complete RAM exhaustion) due to high volume of TCP FD's · Issue #724 · NLnetLabs/unbound
[bug] DoT - TCP reset on egress · Issue #137 · NLnetLabs/unbound DoTサーバが切断してくる場合もあるとか
ISP内サーバの利用 見出しにジャンプ
フレッツ東のIPv6 IPoEを利用するISPは、CPEや端末に対してDHCPv6のオプションでキャッシュDNSサーバのIPv6アドレスを通知する。このとき、多くのISPがNTT東日本に割り当てられた共通のアドレス2404:1a8:7f01:a::3
2404:1a8:7f01:b::3
を利用する。
NTT ComのIPv6 PPPoEでは、同ASのアドレスがIPCPv6で通知される。4ヘクステット目が4は東, 104は西になっており、BGP.Toolsによると送信元が東京, 大阪になっている。東京/大阪それぞれに9つのIPv6アドレス、IPv4は星の数ほどあった。
OCN モバイル ONE - Wi-Fiルーター:マルウェア不正通信ブロックサービスの適用を解除したい | 個人向けOCNお客さまサポート
twitterのDNS Attackが、OCNのDNSキャッシュサーバーに残っていたので記録
驚くべきことに、東京のサーバにフォワードしたときも、送信元にかなりの率で大阪が含まれていた。(フレッツ東のNTT Comにおいて)送信元が一貫してフォッサマグナを跨がなかったのは下記だけだった。DHCPv6やIPCPv6で自動設定される内容と若干異なっていた。
forward-zone:
name: "."
forward-addr: 2001:380:0:4::1 # nv6-ef701.ocn.ad.jp.
forward-addr: 2001:380:0:4::2 # nv6-ef702.ocn.ad.jp.
forward-addr: 2404:1a8:7f01:a::3
フレッツ東共通のa/bも同ASからクエリされており、bは送信元に大阪が含まれていた。IPv6 Hop Limitも1小さかった。各ISP内のキャッシュDNSを指すAnycastになっているのだろう。
このISPは東西ひとつづつを渡しているのかな。東西冗長してあるのは良いことだけど、平時は近くのものを使いたいような... https://t.co/qIf3wzi0fU
— Tomoyuki Sahara (@tomosann) July 30, 2024
Biglobeさんは全国に同じアドレスを配っているようだ。pingは5ms未満なので二つとも関東近郊にあるように見える。TTLが違うから、たぶんDCも分けてある。これで関東以外にもあってanycastしているのなら最善ではなかろうか。すばらしい。
— Tomoyuki Sahara (@tomosann) July 30, 2024
NGN網内のサービス情報サイト(東)利用には、DHCPv6で通知されたサーチリストにあるflets-east.jp.
ゾーンを、少なくともNGNのキャッシュDNSにフォワードする必要がある。
forward-zone:
name: "flets-east.jp."
forward-addr: 2404:1a8:7f01:a::3
forward-addr: 2404:1a8:7f01:b::3
forward-zone:
name: "iptvf.jp."
forward-addr: 2404:1a8:7f01:a::3
forward-addr: 2404:1a8:7f01:b::3
- ひかりTV(フレッツ・テレビではない)の利用には、同サーチリストにある
iptvf.jp.
も必要そうだ。
ひかりTV導入記 〜次世代のTVインフラを試してみる〜 神保道夫@NISOC - VNEによるが、標準プロビジョニング方式の場合、プロビジョニングサーバ
4over6.info.
の名前解決は網内DNSに向ける必要がある。
v6mig-prov/spec.md at master · v6pc/v6mig-prov - IPv4のサービス情報サイトに接続するなら、IPCP情報を見て
v4flets-east.jp.
を向ければよい。
送信元アドレスやEDNS Client Subnetによって応答を変えるタイプのGSLBでは、キャッシュDNSによって接続先がレイテンシの小さいISP内キャッシュからピアリング、トランジットへと変わり、体験が悪化する場合がある。
国内トラフィックエンジニアリングの現状 | PPT
例えば、一部のISPで http://test.edge.apple/debug/ にアクセスすると、Apple Edge Cache*.ec.edge.apple
が使われれているのではないかと思う。

RIPE Atlas - Measurement Detail
同じドメイン名test.edge.apple.
の応答がDNSによって変わっているので、ISPのDNSから別のものに変更することで体験が悪化する例といえる。ISPの力関係によって、同じサービスでも接続状況が異なるため、常に 「○○DNSがオススメ」はすべて詐欺だ。
顧客は無知を貫けばよいが、ISP視点で見ればトランジットのコスト増を招くため思わしくない。こちらの記事はそんなISP側の視点で書かれたものではないかと思う。
DNSを変更するとネットワークは速くなるか | IIJ Engineers Blog
Anyone having significant issues with some of Apple's services? ISP related? | MyBroadband Forum ECSも効くらしいので、Public DNSで利用できる可能性は高いかも?
IIRC you're replacing the whole OS inage on every update now. Also, their edge c... | Hacker News 逆にApple Edge Cacheが遅いからDNSをISPのものから変えている、という話もありそう。
このようなISP内キャッシュの利用に全てををフォワードname: '.'
する必要はないが、サービスを逐一除外するほどのメリットがPublic DNSになければ、ISPのDNSを利用すればよい。
高速なDNSの自動選択 見出しにジャンプ
fast-server-permil: 0
のとき、クエリ毎に複数のforward-server:
が等コストでラウンドロビンされる。
Unboundにクエリを集約することでDNS RRLに引っかかりやすくなるのではないか、といった懸念を軽減できるかもしれない。
900/1000クエリに、3つのRTTが低いサーバを選択する例
https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html#unbound-conf-fast-server-permil
server:
fast-server-permil: 900
fast-server-num: 3
# infra-host-ttl: 900
infra-cache-min-rtt: 100
infra-cache-max-rtt: 5000
infra-keep-probing: yes
- クエリが失敗したとき、タイムアウトの度にinfra-cache-min-rttからinfra-cache-max-rttの間で増加する再送タイマの後に再試行される。
forward-addr先の再帰を待機するにはいささか過敏すぎるのでこれを100msecに伸ばし、NAPTやSPIのタイマー値を超える応答は待っても意味がないので、5000msecに制限した。
https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html#unbound-conf-infra-cache-min-rtt - infra-cache-max-rttに到達または連続してタイムアウトしたforward-addrは、少なくともinfra-host-ttlの間使われない。短くするか、
infra-keep-probing: yes
で早められる。 - infra-cacheの内容は
dump_infra
で確認できる。恐らくpingがRTT(Round Trip Time)を指し、rtt(roundtrip-timeout)(RTTではない)とrto(roundtrip backoff?)が再送タイマを指している。
下記では、低速なDNSサーバは往々にして再送タイマが増加していることが分かる。この後、最も遅い2001:380:0:4::2
はinfra-cacheから削除された。
unbound-control dump_infra
# 2001:380:0:4::2 . ttl 176 ping 79 var 96 rtt 463 rto 463 tA 0 tAAAA 0 tother 0 ednsknown 1 edns 0 delay 0 lame dnssec 0 rec 0 A 0 other 0
# 2404:1a8:7f01:a::3 . ttl 176 ping 16 var 4 rtt 100 rto 100 tA 0 tAAAA 0 tother 0 ednsknown 1 edns 0 delay 0 lame dnssec 0 rec 0 A 0 other 0
# 2001:380:0:4::1 . ttl 176 ping 44 var 49 rtt 240 rto 240 tA 0 tAAAA 1 tother 0 ednsknown 1 edns 0 delay 0 lame dnssec 0 rec 0 A 0 other 0
# 2001:380:0:4::3 . ttl 272 ping 20 var 8 rtt 100 rto 100 tA 0 tAAAA 0 tother 0 ednsknown 1 edns 0 delay 0 lame dnssec 0 rec 0 A 0 other 0
- infra-keep-probingは有効化。全てのforward-addrのrtoがinfra-cache-max-rttに達したときにSERVFAILになるため。
Documenation clarification on RTT band · Issue #567 · NLnetLabs/unbound
NLnet Labs Documentation - Unbound - Unbound Timeout Information
CNAME Scrubbing 見出しにジャンプ
dig logincdn.msauth.net aaaa +noall +answer logincdn.msauth.net. 139 IN CNAME lgincdnmsftuswe2.azureedge.net. lgincdnmsftuswe2.azureedge.net. 373 IN CNAME lgincdnmsftuswe2.afd.azureedge.net. lgincdnmsftuswe2.afd.azureedge.net. 71 IN CNAME firstparty-azurefd-prod.trafficmanager.net. firstparty-azurefd-prod.trafficmanager.net. 30 IN CNAME shed.dual-low.s-part-0018.t-0009.t-msedge.net. shed.dual-low.s-part-0018.t-0009.t-msedge.net. 30 IN CNAME s-part-0018.t-0009.t-msedge.net. s-part-0018.t-0009.t-msedge.net. 30 IN AAAA 2620:1ec:bdf::46
logincdn.msauth.net
はCNAMEチェーンが長いことで有名なFQDNの一つだ。
Resolving records through more than 8 CNAME fails due to hardcoded MAX_RESTART_COUNT · Issue #438 · NLnetLabs/unbound
フルリゾルバは、仮にlogincdn.msauth.net. IN CNAME
の応答にIN AAAA 2620:1ec:bdf::46
が付随しても信頼しない。信頼できるt-msedge.net.
の応答を聞くまでCNAMEチェーンを一つずつ再クエリする。このように信頼のないCNAMEチェーンを再検索することを、Scrubbing(擦る)という。
Strange DNS Samples 4. CNAME に付随する A を信じてはいけない
Clients should assume that other recordsmay have come from the server's cache. Where authoritative answersare required, the client should query again, using the canonical nameassociated with the alias.
RFC 2181: Clarifications to the DNS Specification
Unboundのforward-addr:
はフルリゾルバと共通のコードベースなので、同じくCNAMEの解決に数往復掛かってしまう。パフォーマンスへの影響は大きい。
Consider disabling CNAME scrubbing for forwarded queries · Issue #132 · NLnetLabs/unbound
こちらのIssueでは、Do53では依然として利点があるとして話が進んでいるが、通信の秘密のない国や、Public DNSに限った話に聞こえる。殆どのスタブリゾルバはキャッシュDNSを信頼しているので、Scrubを止めても追加のリスクはない。
完全には止められていないが、ソースコードの変更により2往復で済むようになった。上記の例だとs-part-0018.t-0009.t-msedge.net.
だけがScrubされるが、RPZを評価できるし、Appleのスタブリゾルバも同じ動作なので先読みされる形になる。
EDNS Client Subnet 見出しにジャンプ
ECSは、キャッシュDNSの地理と権威DNSの応答によってロードバランシングを実現するGSLBを、位置情報のコンテキストが崩れるPublic DNSでも機能させる技術だ。あるいはDHCPv6オプションが変更できず、単県GWR単位でキャッシュDNSを提供できない某通信事業者の回線でも有効かもしれないと思い至ったのが、私にとっての唯一の需要である。
離れた拠点のブレークアウトを一挙に担うフルリゾルバを運用するでもないので、単に固定のSource Netmaskを付けるだけならedns-client-string-opcode: 8
で実現できそうだったが、設定可能な値の範囲外だった。
server:
module-config: "subnetcache respip iterator"
- UnboundでECSを使うには
--enable-subnet
を付けてビルドが必要。
respipモジュールやserve-expiredと互換性がないとは言うものの、同時に使えない訳ではない。
[1753150053] unbound-checkconf[22003:0] fatal error: module conf 'subnetcache respip iterator' is not known to work
access-control: 2001:db8:b0ba:1000::/56 allow
send-client-subnet: 2001:db8::/30 # filter
# client-subnet-always-forward: no
max-client-subnet-ipv6: 38
max-client-subnet-ipv4: 0
- Unboundはクライアントのプレフィクスを再帰クエリに付与する。某ISPのキャッシュDNSは不正なSubnetにREFUSEDを返すので、LLAやULAでなく
client-subnet-always-forward: no
、必ずGUAsend-client-subnet
でクエリさせる必要がある。 - GUAの運用となるので、
send-client-subnet
とDHCPv6サーバ・RA RDNSSがプレフィクス変更に追従する必要がある。
DHCPv6オプションで通知されたDNSは各OSでIPv4 DNSより優先される傾向があるが、クライアントのDHCPv6リクエスト次第なので有効期間が設定できず、Androidが対応しない。
一方RDNSSはRouter Advertisementで即座に通知でき、Androidに対応するものの、引き続きIPv4 DNSが優先されたりする。
/64をPVLANしている環境では、access-control*
でアクセス制御できない。PVLAN毎にDHCPv6サーバかNDプロキシを用意すれば、interface-tag
で代替できる。 max-client-subnet-ipv4: 0
IPv4プライベート範囲からのクエリは許可するほかないので、プライベートIPv4アドレスの/24が付与されないように。0
にすればECSヘッダそのものが送信されない。max-client-subnet-ipv6
UnboundやAdGuard Homeのデフォルトの/56では、フレッツIPv6の契約単位が一意に特定され得る。到達性のあるIPv6プレフィクス情報を地球の裏までばら撒くのはナンセンスだ。IPinfoでGeoIPを確認しながら、GWRの位置情報として齟齬が出ない範囲で粒度を下げた(/64
->/38
)。
IPv6 GUAが2001:db8:b0ba:10ee::/56/0
なクライアントからのDNSクエリを受けると、Unboundは2001:db8:b000::/38/0
で再帰する。
AdGuard Homeは/56でハードコーディングされている例もあるので、逆にユニークな指標になってしまうかもしれない。特に、本来のフルリゾルバからDNSフォワーダ動作に変更している場合、通信の秘密が保証された上位キャッシュDNSサーバの選択が望ましい。
# Google
# https://support.google.com/a/answer/2589954
client-subnet-zone: "google.com." # /10/24 /38/56 www.google.com
client-subnet-zone: "googleapis.com." # /10/24 youtubei.googleapis.com.
client-subnet-zone: "googleusercontent.com."
client-subnet-zone: "ggpht.com."
client-subnet-zone: "gstatic.com."
client-subnet-zone: "google.co.jp." # /10/24 www.google.co.jp
# https://support.google.com/a/answer/6214622
client-subnet-zone: "youtube.com." # /10/24 /38/56 www.youtube.com
client-subnet-zone: "youtu.be." # /10/24
client-subnet-zone: "ytimg.com."
# https://support.google.com/work/android/answer/10513641
client-subnet-zone: "android.com."
client-subnet-zone: "appspot.com."
client-subnet-zone: "googlezip.net."
client-subnet-zone: "gvt1.com."
client-subnet-zone: "gvt2.com."
client-subnet-zone: "gvt3.com."
client-subnet-zone: "chrome.com."
client-subnet-zone: "googleblog.com."
# client-subnet-zone: "play.google." # /10/0
# client-subnet-zone: "pki.goog." # /10/0
# client-subnet-zone: "web.dev." # /10/0
# Meta
# client-subnet-zone: "cdninstagram.com." # /10/24 /38/48 scontent-nrt1-1.cdninstagram.com.
# client-subnet-zone: "instagram.com." # /10/24 /38/48 graph.instagram.com
# client-subnet-zone: "facebook.com." # /10/24 /38/48 web.facebook.com
# Fastly
client-subnet-zone: "fastly.net." # /10/0 twimg.com. GSLB...?
# Apple
client-subnet-zone: "aaplimg.com." # /10/24 /38/56 apple, apple.com, mzstatic.com, cdn-apple.com.
# AWS
# client-subnet-zone: "amazonaws.com." # /10/0 wwwl28.mitsubishielectric.co.jp.
client-subnet-zone: "apple-dns.net." # /10/10 gateway.icloud.com.
client-subnet-zone: "cloudfront.net." # /10/24
# ns1.cloudflare.net.
client-subnet-zone: "cloudflare.net." # /10/24 /38/64 api.x.com
# Azure
# client-subnet-zone: "ax-msedge.net." # /10/0
# client-subnet-zone: "fb-t-msedge.net." # /10/0
# client-subnet-zone: "ln-msedge.net." # /10/0 config.edge.skype.com.
# client-subnet-zone: "t-msedge.net." # /10/0
client-subnet-zone: "trafficmanager.net." # /10/24 prod.sucu.xboxlive.com.
# client-subnet-zone: "azure.com." # /10/0 browser.pipe.aria.microsoft.com.
# client-subnet-zone: "store.core.windows.net." # /10/0 msdntnarchive.blob.core.windows.net.
- ECSはCloudflareがその扱いに注意を払っているように、ISPのキャッシュDNSサーバしか知り得なかったクエリ元の個人情報を、権威DNSへ通知する機能でもある。そもそも対応した権威DNSが少なく、むやみに付与してもUnboundの目玉機能であるserve-expiredキャッシュヒット率が低下するだけだ。
ISPも運用経験の少なさや効率の悪さから、同じ設定を用いて一部のゾーンのみECSを有効化していると聞く。
日本DNSオペレーターズグループ | DNS Summer Day 2025 ECS(RFC 7871)の3社共同検証の結果共有 〜商用利用を前提とした検証結果の共有〜
ECSが有効なゾーンを絞り込むため、下記のような方法で権威DNSに直接非再起クエリを送り、ECSフィールドの有無を調査した。
一意の権威DNSの権威のある応答にECSが付くかを調べるので、CDNを指すCNAMEはScrubしてからdigる必要がある。つまり、client-subnet-zone
に書くのもCNAMEチェーンの最後のゾーンだけでよい(これはフルリゾルバでも、forward-addrでも、改造forward-addrでも同じ)。
No, you should use the EDNS Client Subnet Checker only with hostnames that always point to the same CDN.
EDNS Client Subnet Checker - CDN Planet
dig google.com. ns dig www.google.com. +norec +subnet=2001:db8:b000::/38 @ns1.google.com. ; CLIENT-SUBNET: 2001:db8:b000::/38/56
ECSをサポートしていても、Scope Netmaskが/0
(上記の例では/56
)の場合、プレフィクス毎のゾーン情報が無効だと判断して除外した。
EDNS クライアント サブネット(ECS)のガイドライン | Public DNS | Google for Developers ※キャッシュDNS目線の、権威DNS向け資料
下記によるとFastlyはAnycastとあり、実際/38/0
に対して/38/0
を返すが、AAAAレコードはGSLB動作に見えるので、例外的に含めた。
Which CDNs support edns-client-subnet? - CDN Planet
# +subnet REFUSED by 8.8.8.8
forward-zone:
name: "akadns.net."
forward-addr: 2620:119:35::35@853
forward-addr: 2620:119:53::53@853
forward-tls-upstream: yes
forward-zone:
name: "akamai.net."
forward-addr: 2620:119:35::35@853
forward-addr: 2620:119:53::53@853
forward-tls-upstream: yes
forward-zone:
name: "akamaiedge.net."
forward-addr: 2620:119:35::35@853
forward-addr: 2620:119:53::53@853
forward-tls-upstream: yes
- Akamaiの権威DNSは特殊で、Google Public DNSとOpenDNSにしかECS応答を返さない。丁度Akamaiがデバッグドメインを用意しているので、キャッシュDNSサーバへAkamaiがECS応答しているか確認できる。キャッシュDNSサーバがECSに対応しているかの確認にはならないので注意。
Akamai Blog | Introducing a New whoami Tool for DNS Resolver Information
dig +short TXT whoami.ds.akahelp.net @<ISP> dig +short TXT whoami.ds.akahelp.net @8.8.8.8
面白いことに、Google Public DNSにECS付きクエリを投げるとSERVFAILする。これでAkamaiのゾーンを絞り込んだ。
dig xxx.yyy.akamaiedge.net. +subnet=198.51.100.0/24 @8.8.8.8
A query via Umbrella to an ECS-enabled nameserver will include the Class C network of the requesting user (/24 CIDR block) to the authoritative DNS query, and return and cache (according to TTL) the relevant answer.
Umbrella and EDNS Client Subnet (ECS) – Cisco Umbrella
セキュリティ 見出しにジャンプ
価値の高い広告を提供するアドネットワークは、時にMalvertisingを仲介する悪意あるコンテンツになり下がることがある。紹介料で稼ぐメディアやその利用者、最終的には適切な広告出稿を行う広告主に不利益をもたらす。
このような管理下にないドメインへの対処に一般的なのはuBlock Originや拡張機能版AdGuardだ。復号後のパス名までルールに書けるのは強みである一方、ブラウザ拡張機能を全面的に信頼する必要があることや、複数の端末に自動的に展開できない欠点がある。
米CISAによると、Malvertising防止には拡張機能ではなく自組織のDNSでの提供が推奨されている。
Capacity Enhancement Guide: Securing Web Browsers and Defending Against Malvertising for Federal Agencies - Capacity_Enhancement_Guide-Securing_Web_Browsers_and_Defending_Against_Malvertising_for_Federal_Agencies.pdf
DNS型のセキュリティ、いわゆるDNSシンクホールはDNSの応答を改ざんし、IPアドレスを取得できなくすることでネットワーク側から防御する。類似の手段にSNIを見る透過プロキシ(フォワードプロキシではない)があるが、DNSに比べると強制力に欠け、コストも高い。ブロック情報を端末にキャッシュさせられる上、名前解決を伴うすべての実トラフィックのLBOが可能なため、少ない投資で集中管理が可能だ。
このうちブロック機能のあるPublic DNSは、拡張機能と違い侵害されてもTLSの保護を受けられるとはいえ、やはりAS外にキャッシュがある以上プライバシー・パフォーマンス面で難点がある。UnboundのようなキャッシュDNSソフトウェアをセルフホストし、キャッシュはすぐ傍に、名前解決が所属するAS内で完結するのが理想的だ。
有名企業や公的機関には https:// が付いています。これがサーバ真正性の担保に一役買っていて、否定応答以外の改竄は警告出ます。オンラインカジノにアクセス警告方式ができないのと同様、DNSだけでは攻撃が成立しないことが多いです。
— しばにゃん (@shibanyan_1) July 17, 2025
拡張機能は?悪意を持ったら終わりですhttps://t.co/qOZtlY56LK
ただし、端末が従わなければセキュリティにならない。端末が確実にUnboundへクエリするよう、ネットワークを設計する。
ユーザがVPN/DoHなど回避技術を使うと、ISPやネットワーク管理者はDNS解析情報を得られず、DDoSや感染ホストの検知・対処、サービス品質管理が困難になります。結果として、ネットワーク全体のセキュリティ運用が弱体化するおそれがあります (第3.4節)
DNSブロッキングを再考する – SAC127 – JPNIC Blog
local-zone 見出しにジャンプ
local-data
を用いると、権威のないゾーンのRRsetを編集できる。
応答を改ざんするので、当然DNSSECの信頼は失われる。DNSSECを無効にしないと動かない。
https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html#unbound-conf-local-zone
Ad DNSブロッキングおよび、kawango DNSブロッキングの実装をしてみたら、超快適だった とも ちゃ日記(Tomo cha) - 元大学生のOL日記-
local-zone type | ログレベル | local-data | local-dataと異なるタイプ | 子ゾーン | response-ip | 備考 |
---|---|---|---|---|---|---|
deny | 応答 | (DROP) | 端末はタイムアウトを待つので、何かしら応答すべき | |||
inform_deny | INFO | |||||
refuse | 応答 | REFUSED | 非対応 | response-ipでも使えるが、悟られたくない感じはある | ||
always_refuse | 常にREFUSED | REFUSED | ||||
static | 応答 | NODATA, local-data IN SOA |
NXDOMAIN, local-data IN SOA |
非対応 | ゾーン頂点にSOAレコードを置くと、全てのクエリタイプで否定応答が任意のTTL端末にキャッシュされる。 | |
transparent | 応答 | NODATA | 名前解決 | 非対応 | local-dataを書けばtransparentゾーンが作成されるため、AS112ゾーンを除きlocal-zoneは書かなくてよい。 | |
inform | INFO | 名前解決 | ||||
always_transparent | 常に名前解決 | 名前解決 | ||||
typetransparent | 応答 | 非対応 |
ゾーンでなくクエリタイプ単位で評価する性質から、AAAAはlocal-data、Aは名前解決といった混在が可能。 |
|||
redirect | 応答 (CNAMEを解決) |
NODATA | local-data | response-ip-data | ほぼワイルドカードドメイン。唯一、local-dataに書かれたCNAMEを解決する。非常に遅いので、TTL付きの否定応答だけ欲しいならstaticを使う。 | |
inform_redirect | INFO | 非対応 | ||||
nodefault | 応答 | 名前解決 | 非対応 |
逆引きクエリを抑制するためにハードコーディングされたAS112ゾーンのデフォルト応答を無効化するだけ。local-dataを応答するために使うならstaticが確実。 |
||
block_a | IN AはNODATA 他は名前解決 |
非対応 |
Happy Eyeballsさせず、IPv6接続のみ試行して欲しいときに使う。 |
|||
block_aaaa | IN AAAAはNODATA 他は名前解決 |
非対応 |
正式な機能ではないので、ソースコードの変更が必要。 |
|||
always_nodata | NODATA | 非対応 | プライベートリレーへのアクセスをブロックするときの、推奨された応答形式。
iCloudプライベートリレーに向けたネットワークやWebサーバの準備 - iCloud - Apple Developer |
|||
always_nxdomain | NXDOMAIN | |||||
always_null | 応答 | IN A 0.0.0.0 IN AAAA :: IN HTTPS等はNODATA |
非対応 | 否定応答ではないが、A/AAAAに限りブロック応答が端末で3600秒キャッシュされる。
このため、レガシーなスタブリゾルバでは100%、ChromiumやAppleデバイスでは2/3のクエリを一行でキャッシュさせられる。 |
||
noview | 応答 | 非対応 | view機能から除外するゾーン |
安全なTLDだけ通す 見出しにジャンプ
maliciousなドメインは日々変化する。TLDによってはAbuse対応が悪かったり、安価に取得できるためだ。
Networks, Countries, Registrars & TLD charts | Reputation Statistics | Spamhaus
Unboundでは.
com.
example.com.
などのゾーン単位で操作できるので、いっそのことTLDごとブロックしてしまえばよい。
荒い網なので期待はできないが、例えば一体何のためかUser-Agentによるリダイレクト機能を持つ短縮URLサービスなどは一行で落とせる。
このようにルートゾーン.
以下をリダイレクトすると、transparentしたTLD以外に否定応答を返せる。
syslogを取りたいのでinform_redirect
にしているだけで、SOAを返すだけならstatic
がよい。
server:
local-zone: "com." transparent
local-zone: "." inform_redirect
local-data: ". 60 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2024080800 1800 900 604800 86400"
逆に、TLD毎にブラックリスト式で記述できる。AS112ゾーン以外は名前解決される。
server:
local-zone: "com." static
local-data: "com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723351845 1800 900 604800 86400"
SOAレコードを置くと、SOAのTTLとMINIMUMフィールドまで(つまり短い方)ネガティブキャッシュが効くと期待できる。
否定応答がわからなくなる話 (11) RFC 2308: 5. 否定回答のキャッシング
これにより端末は高頻度でクエリしなくなるため、バッテリー寿命に優しいDNSができる。
また、SOAの内容は実際のものを参考にした。できる限り自然なRCODE・RRsetに徹して、往生際の悪いソフトウェアにUnboundがブロックしているのではなく正常な応答と見せかけたいからだ。
dig . +noall +authority | sed -E 's/\t+/ /g'
# . 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2024080800 1800 900 604800 86400
dig com. +noall +authority | sed -E 's/\t+/ /g'
# com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723351845 1800 900 604800 86400
com
でもunbound-checkconfに怒られないが、末尾にルートゾーンを示す.
を付けることを推奨する。
IANAのRoot Zone Databaseなどを利用して、gTLDとccTLDを取得しlocal-zoneのベースをつくるとよいだろう。
NTP 見出しにジャンプ
Linux環境は大抵時刻合わせにpool.ntp.org.
を利用しているが、某通信機器メーカーをdisる傍ら、福岡大のNTPに問い合わせていたら目も当てられない。
中には疑わしいNTPサーバが存在するのも事実で、攻撃先を収集するために利用される可能性があるのではないかと個人的に思っている。
DHCPv6のオプションで降ってきた、フレッツ東のNGN網内NTPサーバに向けた。
ブロックルールを書くことだけが、DNSシンクホールの役割ではないのだ。
server:
local-zone: "www.pool.ntp.org." transparent
local-zone: "pool.ntp.org." redirect
local-data: "pool.ntp.org. 10800 IN AAAA 2404:1a8:1102::a"
local-data: "pool.ntp.org. 10800 IN AAAA 2404:1a8:1102::b"
local-data: "pool.ntp.org. 10800 IN AAAA 2404:1a8:1102::c"
ついでにWindows, Apple, Androidも対策。
server:
local-data: "ntp.nict.jp. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "ntp-b2.nict.go.jp. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time.windows.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time-ios.apple.com. 10800 IN A 10.0.0.254"
local-data: "time-ios.apple.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time-macos.apple.com. 10800 IN A 10.0.0.254"
local-data: "time-macos.apple.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time.apple.com. 10800 IN A 10.0.0.254"
local-data: "time.apple.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time1.apple.com. 10800 IN AAAA 2404:1a8:1102::a"
local-data: "time2.apple.com. 10800 IN AAAA 2404:1a8:1102::b"
local-data: "time3.apple.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time4.apple.com. 10800 IN AAAA 2404:1a8:1102::a"
local-data: "time5.apple.com. 10800 IN AAAA 2404:1a8:1102::b"
local-data: "time6.apple.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time7.apple.com. 10800 IN AAAA 2404:1a8:1102::a"
local-data: "time.euro.apple.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time.google.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time1.google.com. 10800 IN AAAA 2404:1a8:1102::a"
local-data: "time2.google.com. 10800 IN AAAA 2404:1a8:1102::b"
local-data: "time3.google.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time4.google.com. 10800 IN AAAA 2404:1a8:1102::a"
local-data: "time.facebook.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time1.facebook.com. 10800 IN AAAA 2404:1a8:1102::a"
local-data: "time2.facebook.com. 10800 IN AAAA 2404:1a8:1102::b"
local-data: "time3.facebook.com. 10800 IN AAAA 2404:1a8:1102::c"
local-data: "time4.facebook.com. 10800 IN AAAA 2404:1a8:1102::a"
local-data: "time.cloudflare.com. 10800 IN AAAA 2404:1a8:1102::c"
NTT GINのNTPサーバも見つけたが、異種NTPを混在させても無視されるので使う機会はないだろう。
server:
local-zone: "time.gin.ntt.net." redirect
local-data: "time.gin.ntt.net. 60 IN CNAME time1.gin.ntt.net."
local-zone: "time2.gin.ntt.net." redirect
local-data: "time2.gin.ntt.net. 60 IN CNAME time1.gin.ntt.net."
dns-blocklists 見出しにジャンプ
私はページ読み込みに違和感を感じたときにFirefoxのDevToolsを開いて悪性ドメインを見つけている。とはいえ、今となっては真っ当な商売ヅラしているECサイト、L0(米議会)からL7を推測して語られることの多い陽キャ御用達SNSなどは経験上ルールが追いつかない。
hagezi/dns-blocklistsはDNSソフトウェア向けに現在rpzのみ提供されている。RPZはCNAME Cloaking対策でもなければ無駄な処理なので、dnsmasq用ルールからいくつか選んで、unbound.conf形式に書き替えて使っている。
- local=/aan.amazon.co.jp/
+ local-zone: "aan.amazon.co.jp." always_nodata
...
include:
を書いた行に別ファイルの内容が反映される。応答内容を書き換える機能なので、外部のものを使うときは中身を確認しよう。
server:
# chroot: "/usr/local/etc/unbound"
# username: "unbound"
- # directory: "/usr/local/etc/unbound"
+ directory: "/usr/local/etc/unbound"
...
+ include: *.txt
rpz 見出しにジャンプ
local-zoneと違いHTTPを介して外部からルールセットをインポートできるほか、Scrub時も評価されるためCNAME Cloakingにも対応できる。
Response Policy Zones — Unbound 1.19.3 documentation
Response Policy Zones in Unbound. We are incredibly happy to introduce… | by Ralph Dolmans | The NLnet Labs Blog | Medium
Public DNS 見出しにジャンプ
せっかくセキュリティ目的でunboundを導入しても、Public DNSへ端末がブレークアウトするのを許してしまえば無用の長物だ。
例えば、AndroidはGoogle Public DNSにDoTを試行するし、一部のモバイルアプリではDo53やDoHを利用して、端末のスタブリゾルバを回避する動きがある。
Do53やDoTは5tuple Firewallで853/tcpをブロックすれば事足りるが、名の知れたPublic DNSはIPアドレスで証明書を取得しているため、それだけはL3 FWでブロックせざるを得ない。
openssl s_client -connect 8.8.8.8:443 | openssl x509 -noout -text | grep DNS:
# DNS:dns.google, DNS:dns.google.com, DNS:*.dns.google.com, DNS:8888.google, DNS:dns64.dns.google, IP Address:8.8.8.8, IP Address:8.8.4.4, IP Address:2001:4860:4860:0:0:0:0:8888, IP Address:2001:4860:4860:0:0:0:0:8844, IP Address:2001:4860:4860:0:0:0:0:6464, IP Address:2001:4860:4860:0:0:0:0:64
幸いなことに、殆どのDoHは証明書の検証を行うときにDNS名が必要なため、DNSでブロックできる。
Blocking DNS-over-HTTPS (DoH) - General - Pi-hole Userspace
Block DNS over HTTPS (DoH), using pfsense - Block DOH with pfsense.pdf
Using Unbound response policy zones - Unbound response policy zones.pdf
rpz:
name: rpz.block.doh
url: https://raw.githubusercontent.com/jpgpi250/piholemanual/master/DOH.rpz
rpz-action-override: nodata
rpz-log: yes
rpz-log-name: rpz.block.doh
外部のルールを直接利用するときは、安全のためrpz-action-override:
を使用すべきだ。
RethinkDNSは有用なサービスのため、ホワイトリストにするとよい。
server:
local-data: "rethinkdns.com. 300 IN AAAA 2606:4700:3030::6815:533e"
local-data: "rethinkdns.com. 300 IN AAAA 2606:4700:3030::ac43:d6f6"
use-application-dns.net 見出しにジャンプ
悪意のある通信はL3 FWで防ぐしかないが、善意の通信はUnboundで歓迎する。
DoHを利用しないように指示するCanary domainの仕組み - ASnoKaze blog
server:
local-zone: "use-application-dns.net." inform_redirect
iCloud Private Relay 見出しにジャンプ
iCloud Private Relay経由で名前解決されると、当然Unboundの庇護下に入らない。
server:
# local-zone: "mask.icloud.com." static
# local-data: "mask.icloud.com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723185585 1800 900 604800 86400"
local-zone: "mask-api.icloud.com." static
local-data: "mask-api.icloud.com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723185585 1800 900 604800 86400"
local-zone: "mask-h2.icloud.com." static
local-data: "mask-h2.icloud.com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723185585 1800 900 604800 86400"
# local-zone: "mask-canary.icloud.com." static
# local-data: "mask-h2.icloud.com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723185585 1800 900 604800 86400"
AppleはNODATAstatic
かNXDOMAINalways_nxdomain
でのブロックを想定している。TTLを指定できるstatic
を使用した。
cf. ネットワークのトラフィック監査への対応 iCloudプライベートリレーに向けたネットワークやWebサーバの準備 - iCloud - Apple Developer
iOS 18以降、SafariのITPの不具合かApple公式通りにmask.icloud.com.
をブロックするとページの読み込みが遅くなることが分かったため、除外している。iCloud+未契約でも通信があるが、Unboundのブロックを迂回する機能はないので気持ち悪いが大丈夫だ。mask-canary.icloud.com.
は恐らく関係ないが、あるかもしれない。
"Limit IP Address Tracking" setting on iOS circumvents ad-blocking - Help / Community Help - Pi-hole Userspace
CNAME Cloaking 見出しにジャンプ
Unboundを導入する理由の一つでもあったCNAME Cloakingとはどのような技術か。
何のことはない、CDN利用にCNAMEを向けることで「same-siteであるもののネットワークは異なる」のと同義で、これをMalvertisingやプライバシー侵害判定を受けたドメイン名を隠すのに利用しているだけだ。
「Same-site」と「same-origin」 | Articles | web.dev
下記の例では、通常のスタブリゾルバをバックエンドにするDNSシンクホールにはtargetlr.adobe.com.
にしか見えず、これをルールに書くしかない。一方、フルリゾルバ、正確にはCNAME ScrubbingするDNSフォワーダでもよいが、data.adobedc.net.
であることをRPZで判定できる。
dig targetlr.adobe.com +nostats
; <<>> DiG 9.18.26 <<>> targetlr.adobe.com +nostats
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21745
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 2
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;targetlr.adobe.com. IN A
;; ANSWER SECTION:
targetlr.adobe.com. 8154 IN CNAME adobeinternalmobiles.tt.omtrdc.net.
adobeinternalmobiles.tt.omtrdc.net. 60 IN CNAME adobetarget.data.adobedc.net.
;; ADDITIONAL SECTION:
rpz.block.cloaking. 300 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2024081300 1800 900 604800 86400
例外的に、AppleのスタブリゾルバはScrubbing動作し、ITPのサードパーティCookie制限に利用される。誰でもAppプライバシーレポートを見ればその様子を確認できる。
Tracking Prevention in WebKit | WebKit
このITPの強化はさらなるイタチごっこを引き起こした。CNAMEがドメイン名を隠せないとなれば、今度はNSをサービス側がCNAME flatteningを行える権威DNSに委任することで、直接Aレコードを設定するサービスが現れた。これに対しITPではファーストパーティとのIP範囲の一致を見るようになった。CDN利用のリスクを直視すれば自然ともいえるが、美しい回避策ではないと思う。こちらもrpz-ipで対応できる。
Cap cookie lifetimes to 7 days for responses from third party IP addresses by whsieh · Pull Request #5347 · WebKit/WebKit
現在進行形で続くこれらのイタチごっこは、セキュリティの観点から見れば、maliciousなサードパーティリソースをどのように防ぐかという話が、そのファーストパーティ自身がmaliciousなのでブロックするのかというグラデーションを移動しただけに過ぎない。自らが提供するサービスを危険にさらす技術に投資するでなく、脆弱でないか考えることが長期的な利益につながるだろう。
ログインはともかくとしても、ECサイトの広告ライブラリが汚染されてスキミングの被害が出ています:https://t.co/S7cv3dKa24
— Yuki2718 (@Yuki27183) August 2, 2024
rpz:
name: rpz.block.cloaking
zonefile: /usr/local/etc/unbound/rpz.block.cloaking.zone
rpz-log: yes
rpz-log-name: rpz.block.cloaking
$ORIGIN rpz.block.cloaking.
$TTL 300
rpz.block.cloaking. IN SOA a.root-servers.net. nstld.verisign-grs.com. (
2024081300
1800
900
604800
86400
)
; https://github.com/AdguardTeam/cname-trackers/blob/master/data/combined_original_trackers.txt
*.data.adobedc.net CNAME *.
; gtm
32.21.32.239.216.rpz-ip CNAME *.
32.21.34.239.216.rpz-ip CNAME *.
32.21.36.239.216.rpz-ip CNAME *.
32.21.38.239.216.rpz-ip CNAME *.
128.15.zz.32.4802.4860.2001.rpz-ip CNAME *.
128.15.zz.34.4802.4860.2001.rpz-ip CNAME *.
128.15.zz.36.4802.4860.2001.rpz-ip CNAME *.
128.15.zz.38.4802.4860.2001.rpz-ip CNAME *.
ghs.googlehosted.com CNAME *.
directory:
後であれば相対パスでもよいが、unbound-checkconfがエラーになるので絶対パスを書いている- RPZはゾーンファイルを拡張した構文になっている。
Zonefile example — NSD 4.3.9 documentation - CNAME Cloakingが流行りだした当初から継続してメンテナンスされているAdGuardのルールを採用した。
cname-trackers/data/combined_original_trackers.txt at master · AdguardTeam/cname-trackers · GitHub
前述の通りUnboundはScrubするため、用意された冗長なRPZは必要なく、簡素なオリジナルルールをRPZに書き換えればよい。*.data.adobedc.net
は省略形で、正式には*.data.adobedc.net.rpz.block.cloaking.
のため、末尾に.
を付けるとunbound-checkconfで構文エラーになる。
rpz-ip
を用いると、A/AAAA応答を引っ掛けられる。CIDR表記のresponse-ip
の方が書きやすいので、他のDNSソフトウェアでの使用を想定したときにrpz-ip
を用いるとよい。
DNSの逆引きと似たり寄ったりで、CIDR表記のネットマスク128
を先頭に、ヘクステット毎に先に接尾辞、最後に接頭辞が来るように並び替えられ、ゼロの連続::
はzz
で表現する。ghs.googlehosted.com
はGoogle Tag Managerに使われており、CNAME名(頭痛が痛い?)とIPアドレスが公開されている。様々なサービスが利用しているため、除外が必要かもしれない。
CNAME record values - Google Workspace Admin Help
server:
# rpz.block.cloaking ghs.googlehosted.com
local-data: "nordot.app. 300 IN AAAA 2001:4860:4802:32::15"
local-data: "bugs.chromium.org. 300 IN AAAA 2001:4860:4802:34::15"
local-data: "dwango.co.jp. 300 IN AAAA 2001:4860:4802:36::15"
local-data: "material.io. 300 IN AAAA 2001:4860:4802:38::15"
local-data: "blog.google. 300 IN AAAA 2001:4860:4802:32::15"
abuse 見出しにジャンプ
RPZは複数のDNSソフトウェアで使える共通規格のため、各社からブロックリストが提供されている。
https://dnsrpz.info/
DNSセキュリティを語る上で避けられないあのSpamhausが無償で提供する、最低限のDo Not Route or Peer (DROP)とService FeedsをUnboundのIssueにあった設定例を参考に設定した。
rpz:
name: drop.ip.dtq
zonefile: /usr/local/etc/unbound/drop.ip.dtq.zone
master: 34.194.195.25 # US.spamhaus.zone
rpz-action-override: nodata
rpz-log: yes
rpz-log-name: 'drop.ip.dtq.spamhaus'
rpz:
name: coinblocker.srv
zonefile: /usr/local/etc/unbound/coinblocker.srv.zone
master: 34.194.195.25
rpz-action-override: nodata
rpz-log: yes
rpz-log-name: 'coinblocker.srv.spamhaus'
...
name:
はSpamhausにあるゾーン名と一致させ、ログの接頭辞だけ変えている。RPZゾーン(頭痛が痛い?)は$ORIGIN
が異なると使えなくなるためだ。- Free Use用のホストはIPv6が機能していなかった。Unboundのフォールバックが遅いので、IPアドレスを設定した。IP TTLはUSの方が1大きかった。
Admiralでもないのに引けないと思ったら、Spamhausの"DROP"(無料版のRPZブロックリスト)に載ってた。ありがたい https://t.co/Jjb2EYsVkL pic.twitter.com/YHRsA83vMn
— しばにゃん (@shibanyan_1) March 14, 2025
rpz:
name: threatfox
# zonefile: /usr/local/etc/unbound/threatfox.zone
url: https://threatfox.abuse.ch/downloads/threatfox.rpz
rpz-action-override: nodata
rpz-log: yes
rpz-log-name: 'threatfox.abuse.ch'
危険なTLDをブロックしているならかなり行数を減らせそう
curl -L https://threatfox.abuse.ch/downloads/threatfox.rpz | grep -vE '\.xyz|\.click|\.top|\.shop|\.cyou|\.ly|\.ru|\.online|\.fun|\.site|\.bond|duckdns\.org|\.dyndns\.|ddns\.net|\.no-ip\.|ply\.gg' > ./rpz/threatfox.zone
response-ip 見出しにジャンプ
https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html#unbound-conf-response-ip
DNS Rebinding 見出しにジャンプ
DNSリバインディング攻撃は、ブラウザの保護機構をバイパスするために温故知新された、古くからある攻撃手法だ。
要はWebと権威DNSで連携し、maliciousなWebにアクセスがあれば、すかさず権威DNS側でRRsetを対象の内部ホストに書き換えてしまおうというまさに今、浸透を待っているあなたに耳よりの技術だ。
ドメイン名だけを見てアクセス許可を行う傾向にあるブラウザは、ドメイン名がいつの間にか内部ホストを指していても素通ししてしまう。
Private Network AccessのIP範囲をベースに、DNSリバインディング攻撃が刺さりそうなアドレス範囲をCIDR表記で表現した。
State of DNS Rebinding in 2023 | NCC Group Research Blog | Making the world safer and more secure
server:
# cache-min-ttl: 0
# "this network" ("This host on this network" 0.0.0.0/32 for non-Windows OS)
private-address: 0.0.0.0/8
# IPv4 Loopback
private-address: 127.0.0.0/8
local-zone: "localhost." nodefault
# Private-Use
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
# Shared Address Space (e.g. Tailscale)
private-address: 100.64.0.0/10
# IPv4 Link-Local
private-address: 169.254.0.0/16
# IPv4-mapped Address ::ffff:0:0/96, IPv6 Unspecified Address ::/128, IPv6 Loopback ::1/128
private-address: ::/3
# GUAも忘れずに!
response-ip: 2001:db8:b0ba:1000::/56 always_nxdomain
# Unique-Local fc00::/7, Link-Local Unicast fe80::/10
private-address: 8000::/1
private-domain: "dot.home.arpa."
cache-min-ttl
を大きくするとDNS pinningできるが、ブラウザのDNSフェイルオーバー動作にTTLは関係ないので無意味。内部アドレスの応答を落とすことが重要。
Boneh Publications: Protecting Browsers from DNS Rebinding Attacks- ルール数を減らすためIPv6は大雑把に括っているが、BCP 38に使われるBogonプレフィックスと同義なので支障はない。
Bogon Prefixes – BGP Filter Guide – Guidance on BGP Filtering
IANA IPv4 Special-Purpose Address Registry
IANA IPv6 Special-Purpose Address Registry
Internet Protocol Version 6 Address Space - macOSやLinuxでは0.0.0.0がブロックされないらしいので、追加した。
0.0.0.0 Day: Exploiting Localhost APIs From the Browser | Oligo Security localhost.
はデフォルト応答を無効化し、ルート.
にリダイレクトした。絶対的な変更をするなら、static
やalways_nodata
が望ましい。- Shared Address Spaceを使うTailscaleも気になり、公式に警告されていたのでこれも追加した。
DNS Problems with internal services and DNS rebinding protection · Tailscale Docs - Unboundに設定された内部ドメイン以外のプライベートアドレスを弾くことを意味するので、
private-domain
を用いて除外する。
Local hostnames are not resolved by unbound · Issue #900 · NLnetLabs/unbound Protection Bypasses · nccgroup/singularity Wiki
これにより、管理下にないドメイン名を介して内部ホストへ不正にアクセスされなくなる。
インターネット到達性のあるアドレス範囲にACLがされていない(DMZ, 旧帝大, IPv6 GUA)ならこの限りではない。
自ビルド版では動かなかったので、response-ipで書き直した。
server:
response-ip: 0.0.0.0/8 always_nxdomain
response-ip: 127.0.0.0/8 always_nxdomain
response-ip: 10.0.0.0/8 always_nxdomain
response-ip: 172.16.0.0/12 always_nxdomain
response-ip: 192.168.0.0/16 always_nxdomain
response-ip: 100.64.0.0/10 always_nxdomain
response-ip: 169.254.0.0/16 always_nxdomain
response-ip: ::/3 always_nxdomain
response-ip: 2001:db8:b0ba:1000::/56 always_nxdomain
response-ip: 8000::/1 always_nxdomain
Google Public DNSを自宅に設置する 見出しにジャンプ
Public DNSによる脱獄は対策したが、それだけでは面白みがない。直接IPアドレスを叩いてくるアプリケーションなどは逆NATでもするほかないが、ご丁寧にUnboundにクエリして頂けたのなら、ぜひとも他のクエリも歓迎したい。
server:
# dns.google.
response-ip: 8.8.4.0/24 redirect
response-ip-data: 8.8.4.0/24 "60 IN A 10.0.3.5"
response-ip: 8.8.8.0/24 redirect
response-ip-data: 8.8.8.0/24 "60 IN A 10.0.3.5"
response-ip: 2001:4860:4840::/42 always_nxdomain
# one.one.one.one.
response-ip: 1.1.1.0/24 redirect
response-ip-data: 1.1.1.0/24 "60 IN A 10.0.3.5"
response-ip: 1.0.0.0/24 redirect
response-ip-data: 1.0.0.0/24 "60 IN A 10.0.3.5"
response-ip: 2606:4700:4700::/40 always_nxdomain
アンチアンチアドブロッカー 見出しにジャンプ
Unboundに施したセキュリティ設定が干渉し、コンテンツが表示できないWebサイトがある。アドブロッカーの解除を求めるダイアログを出すものがあるが、当然ブラウザ拡張機能ではないのでユーザ側での対処は難しいし、できないようにネットワークを設計すべきだ。
DNSレベルではどのサイトのコンテンツを許可するかをステートフルに処理するのは難しく、限定的な許可を表現できない。そこで、できる限りこのようなスクリプトに対処してみた。
jkrejcha/AdmiraList: A text-based list of 1000+ Admiral domains and IP addresses
microShield/sources/banner.txt at main · List-KR/microShield
server:
module-config: "respip iterator"
# Admiral
response-ip: 104.18.24.111/32 always_nxdomain
response-ip: 104.18.25.111/32 always_nxdomain
response-ip: 2606:4700::6812:186f/128 always_nxdomain
response-ip: 2606:4700::6812:196f/128 always_nxdomain
local-zone: "getadmiral.com." static
local-data: "getadmiral.com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723184575 1800 900 604800 86400"
local-zone: "cdnral.com." static
local-data: "cdnral.com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723184575 1800 900 604800 86400"
local-zone: "scarceshock.com." static
local-data: "scarceshock.com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723184575 1800 900 604800 86400"
local-zone: "succeedscene.com." static
local-data: "succeedscene.com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1723184575 1800 900 604800 86400"
...
- これまで推測が難しいFQDNとCDNを利用してブロッカーの回避を試みていたが、最近はCloudflareを挟んでいるため簡単にルールを記述できる。
- respipではHTTPS RRのipv4hint/ipv6hintがすり抜けてしまうため、代わりに
inform_redirect
を用いて監視するのもよい。発見したFQDNはlocal-zone設定すればよい。
server:
# Admiral
local-data: 'ambrosialsummit.com. 300 IN HTTPS 0 invalid.'
local-data: 'scarceshock.com. 300 IN HTTPS 0 invalid.'
local-data: 'succeedscene.com. 300 IN HTTPS 0 invalid.'
local-data: 'scenicdrops.com. 300 IN HTTPS 0 invalid.'
# CHP Ads Block Detector
local-data: 'pagead2.googlesyndication.com. 300 IN HTTPS 0 invalid.'
# www.youtube.com
local-zone: 'jnn-pa.googleapis.com.' typetransparent
local-zone-tag: 'jnn-pa.googleapis.com.' admin
local-data: 'jnn-pa.googleapis.com. 300 IN HTTPS 0 invalid.'
# ad-shield.io 部分的に許可
local-zone: 'html-load.com.' static
local-zone: 'fb.html-load.com.' transparent
# local-zone-tag: 'fb.html-load.com.' ios
# local-zone: '0.html-load.com.' redirect
# local-data: '0.html-load.com. 30 IN HTTPS 0 invalid.'
# ...
# local-zone: '9.html-load.com.' redirect
# local-data: '9.html-load.com. 30 IN HTTPS 0 invalid.'
local-zone: 'content-loader.com.' static
local-zone: 'css-load.com.' inform_redirect
local-data: 'css-load.com. 300 IN HTTPS 0 invalid.'
local-zone: 'html-load.cc.' inform_redirect
local-data: 'html-load.cc. 300 IN HTTPS 0 invalid.'
local-zone: 'img-load.com.' inform_redirect
local-data: 'img-load.com. 300 IN HTTPS 0 invalid.'
- HTTPS RRのユースケースには、一般的なServiceModeの他にSvcPriorityが0のAliasModeがあり、対応端末をDNSだけでリダイレクトできる。
このうちApple macOS/iOS 17+限定になるが、否定応答を返すTargetName(invalid.
など)を返すと読み込みを継続しようとするため、ブロックしたことにならない。
RFC 9460: Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records)
これにより、パスまで評価する拡張機能や、偽スクリプトを挿入するFirefoxのShimのような動きを表現できる。
iOS 17、AliasModeの向き先が否定応答のとき、タイムアウトしないらしい。 pic.twitter.com/kaneszdQvR
— しばにゃん (@shibanyan_1) January 4, 2024
ブラウザの仕様上、head内に該当scriptを書いていればユーザーはメインコンテンツを読めない可能性が高いが、副作用として多くの顧客のLCPが低下し、収益低下を免れない。
メジャーなフィルターリストはブロックしていませんが、jnn-pa.googleapis\.comをブロックするとYoutubeのライブをしばらく再生したのちエラーが出るようになりました。 pic.twitter.com/tArmaqZM59
— Yuki2718 (@Yuki27183) August 8, 2024
Ad-Shield広告にSafariコンテンツブロッカーの厳しい制約下で対応するには
— Yuki2718 (@Yuki27183) October 4, 2024
1. 罠を迂回する
2. ネットワークブロックは使わない
3. 最上位フレームは非表示にせず、入れ子になっているフレームや他の要素を狙う
の3点が少なくとも必要です。
5,6年前と比較してDNSブロックに反応するアンチブロックの割合は増えていると思います。画像のCHP Ads Block Detectorもpagead2.googlesyndication\.comのブロックを検知するためDNSでの対応が難しいものの一つ。 https://t.co/OAYPsmbzoB
— Yuki2718 (@Yuki27183) April 15, 2025
home.arpa. 見出しにジャンプ
内部ドメインには好き勝手にそれらしい名前のTLDが使われていて、.localがmDNSと被ったり、.homeが後にTLDに追加されるなど混沌としている。また、これらの内部ドメインの問い合わせが外に漏れると、ルートサーバの過負荷に繋がったり、セキュリティリスクが伴う。
ICANNのSAC113 SSAC Advisory on Private-Use TLDsは、パブリックに登録されたドメイン名のサブドメインを使うのが最善としている。例えばexample.com.
を持っているならserver.example.com.
とか。
内部構成を公開したくないなどのユースケースもあるので、他の選択肢にICANNのinternal.
, IANAのhome.arpa.
が挙げられている。
RFC 8375 - Special-Use Domain 'home.arpa.'
一点付け加えておくと,議論の元になったアドバイザリ (SAC113) では,プライベート用途ドメイン名が予約された場合でも,すでに委任されたドメイン名のサブドメイン (つまり自社ドメイン名からサブドメインを出す) を使うのが最善とされています→
— alt (@jj1lfc) August 5, 2024
server:
local-zone: "router.home.arpa." static
local-zone-tag: "router.home.arpa." admin
local-data: "router.home.arpa. 10800 IN A 10.0.0.254"
local-data: "server.home.arpa. 10800 IN A 10.0.2.8"
local-data: "dns.home.arpa. 10800 IN A 10.0.3.5"
local-data: "printer.home.arpa. 10800 IN A 10.0.4.62"
NGN IPv6のキャッシュDNS(東)をlocal-dataに設定する例。元の応答を調べるときにエイリアスがあると捗るよ。
server:
local-zone: "flets." static
local-data: "ntp.flets. 3600 IN AAAA 2404:1a8:1102::a"
local-data: "ntp.flets. 3600 IN AAAA 2404:1a8:1102::b"
local-data: "ntp.flets. 3600 IN AAAA 2404:1a8:1102::c"
local-data: "dns.flets. 3600 IN AAAA 2404:1a8:7f01:a::3"
local-data: "dns.osa.flets. 3600 IN AAAA 2404:1a8:7f01:a::3"
local-zone: "ocn." static
local-data: "dns.ocn. 3600 IN AAAA 2001:380:0:4::1"
local-data: "dns.ocn. 3600 IN AAAA 2001:380:0:4::2"
local-data: "dns.osa.ocn. 3600 IN AAAA 2001:380:0:104::1"
local-data: "dns.osa.ocn. 3600 IN AAAA 2001:380:0:104::2"
dig @dns.flets. example.com
Discord 見出しにジャンプ
DiscordはCloudflare CDNを利用している。
```sh
dig discord.com ns
dig @sima.ns.cloudflare.com. discord.com a +norec +noall +answer
# discord.com. 300 IN A 162.159.138.232
# discord.com. 300 IN A 162.159.128.233
# discord.com. 300 IN A 162.159.135.232
# discord.com. 300 IN A 162.159.137.232
# discord.com. 300 IN A 162.159.136.232
whois -h whois.cymru.com -v 162.159.128.233
Warning: RIPE flags used with a traditional server.
AS | IP | BGP Prefix | CC | Registry | Allocated | AS Name
13335 | 162.159.128.233 | 162.159.128.0/19 | US | arin | 2013-05-23 | CLOUDFLARENET, US
Cloudflareは積極的にIPv6やHTTP/3などのモダンな技術に対応してきたが、Discordが使用するドメインにはAAAAレコードが設定されていないため、実際にはIPv4で接続する。
dig @sima.ns.cloudflare.com. discord.com aaaa +norec +noall +answer +comments
# ;; Got answer:
# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48529
# ;; flags: qr aa; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
# ;; OPT PSEUDOSECTION:
# ; EDNS: version: 0, flags:; udp: 1232
dig @sima.ns.cloudflare.com. discord.com type65 +norec +noall +answer
# discord.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.233,162.159.135.232,162.159.136.232,162.159.137.232,162.159.138.232
他のホストも調べるが、AAAAレコードはほぼ設定されていない。discord.com.
, discordapp.com.
, discordapp.net.
, discord.gg.
, discord.media.
ゾーン毎にそれぞれ5つのIPアドレスが存在するようだ。
※RFC 9460 SvcParamが網羅的に存在するためHTTPSレコードをクエリするが、通常はA/AAAAをクエリすると留意してほしい。
discord.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.233,162.159.135.232,162.159.136.232,162.159.137.232,162.159.138.232
ptb.discord.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.233,162.159.135.232,162.159.136.232,162.159.137.232,162.159.138.232
streamkit.discord.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.233,162.159.135.232,162.159.136.232,162.159.137.232,162.159.138.232
updates.discord.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.233,162.159.135.232,162.159.136.232,162.159.137.232,162.159.138.232
support.discord.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.233,162.159.135.232,162.159.136.232,162.159.137.232,162.159.138.232
cdn.discordapp.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.129.233,162.159.130.233,162.159.133.233,162.159.134.233,162.159.135.233
discordapp.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.129.233,162.159.130.233,162.159.133.233,162.159.134.233,162.159.135.233
streamkit.discordapp.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.129.233,162.159.130.233,162.159.133.233,162.159.134.233,162.159.135.233
media.discordapp.net. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.232,162.159.129.232,162.159.130.232,162.159.133.232,162.159.134.232
images-ext-1.discordapp.net. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.232,162.159.129.232,162.159.130.232,162.159.133.232,162.159.134.232
images-ext-2.discordapp.net. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.232,162.159.129.232,162.159.130.232,162.159.133.232,162.159.134.232
gateway.discord.gg. 300 IN HTTPS 1 . alpn="h2" ipv4hint=162.159.130.234,162.159.133.234,162.159.134.234,162.159.135.234,162.159.136.234
latency.discord.media. 300 IN HTTPS 1 . alpn="h2" ipv4hint=162.159.128.235,162.159.129.235,162.159.130.235,162.159.137.234,162.159.138.234 ipv6hint=2606:4700:7::a29f:80eb,2606:4700:7::a29f:81eb,2606:4700:7::a29f:82eb,2606:4700:7::a29f:89ea,2606:4700:7::a29f:8aea
このように、CDNが対応している方式が使われていない事例があり、サービス提供側と利用者側に温度差がある。
実際、Discordのサポートフォーラムには完全なIPv6サポートを求める投稿があるが、日本においてIPv4接続性のない環境は考えにくいし、個人的にはIPv6推進派ですらない。IPv6化のメリットはせいぜいフレッツで高速だとか、大量のHTTP/3トラフィックが発生する状況くらいなものだ。
複数のWebページでAccess Deniedが表示されたので、調査したらAkamai八分を受けていた
同Cloudflareを利用するnyanshiba.comはというと、ipv6hintやAAAAが設定されている。
dig nyanshiba.com https +noall +answer
# nyanshiba.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=104.21.0.152,172.67.151.30 ipv6hint=2606:4700:3031::6815:98,2606:4700:3032::ac43:971e
CloudflareのIPv6アドレス割り当てには法則があり、基本的に2606:4700::/96
の接頭辞に、IPv4アドレスのdecimalをhexに変換した接尾辞を持つ。Anycast方式のため、IPアドレスは世界共通。
DiscordはAAAAがないものの、この法則に従って変換すると機能するサービスの一つだ。
Discord works on IPv6 if you force it : r/ipv6
そこで、他のドメイン名にも対応するために、CLoudflareを利用するサービスのHTTPSレコードのipv4hintを渡すとIPv6アドレスに変換するスクリプトを作成した。
unboundconf/calc-cfv6addr.ps1 at main · nyanshiba/unboundconf
./calc-cfv6addr.ps1 -ip 162.159.128.233,162.159.135.232,162.159.136.232,162.159.137.232,162.159.138.232
# 2606:4700::a29f:80e9
# 2606:4700::a29f:87e8
# 2606:4700::a29f:88e8
# 2606:4700::a29f:89e8
# 2606:4700::a29f:8ae8
unboundに設定すると下記の通り。同じIPアドレスを使う他のドメインにも同様に設定する。新規チャットの購読gateway.discord.gg.
や、ここに上がっていない通話のICEサーバはIPv6非対応だ。
IPv4でも接続できるように、typetransparent
でIN Aを透過するとよい。
server:
local-zone: 'discord.com.' typetransparent
local-data: 'discord.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.233,162.159.135.232,162.159.136.232,162.159.137.232,162.159.138.232 ipv6hint=2606:4700::a29f:80e9,2606:4700::a29f:87e8,2606:4700::a29f:88e8,2606:4700::a29f:89e8,2606:4700::a29f:8ae8'
local-data: 'discord.com. 300 IN AAAA 2606:4700::a29f:80e9'
local-data: 'discord.com. 300 IN AAAA 2606:4700::a29f:87e8'
local-data: 'discord.com. 300 IN AAAA 2606:4700::a29f:88e8'
local-data: 'discord.com. 300 IN AAAA 2606:4700::a29f:89e8'
local-data: 'discord.com. 300 IN AAAA 2606:4700::a29f:8ae8'
...
local-data: 'cdn.discordapp.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.129.233,162.159.130.233,162.159.133.233,162.159.134.233,162.159.135.233 ipv6hint=2606:4700::a29f:81e9,2606:4700::a29f:82e9,2606:4700::a29f:85e9,2606:4700::a29f:86e9,2606:4700::a29f:87e9'
...
local-data: 'media.discordapp.net. 1800 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.232,162.159.129.232,162.159.130.232,162.159.133.232,162.159.134.232 ipv6hint=2606:4700::a29f:80e8,2606:4700::a29f:81e8,2606:4700::a29f:82e8,2606:4700::a29f:85e8,2606:4700::a29f:86e8'
...
QoSに引っ掛けやすいように、Prefixを変えてもよい。
local-zone: 'media.discordapp.net' typetransparent
local-data: 'media.discordapp.net. 1800 IN HTTPS 1 . alpn="h3,h2" ipv4hint=162.159.128.232,162.159.129.232,162.159.130.232,162.159.133.232,162.159.134.232 ipv6hint=2606:4700::a29f:80e8,2606:4700::a29f:81e8,2606:4700::a29f:82e8,2606:4700::a29f:85e8,2606:4700::a29f:86e8'
local-data: 'media.discordapp.net. 1800 IN AAAA 2606:4700:3032::a29f:80e8'
local-data: 'media.discordapp.net. 1800 IN AAAA 2606:4700:3032::a29f:81e8'
local-data: 'media.discordapp.net. 1800 IN AAAA 2606:4700:3032::a29f:82e8'
local-data: 'media.discordapp.net. 1800 IN AAAA 2606:4700:3032::a29f:85e8'
local-data: 'media.discordapp.net. 1800 IN AAAA 2606:4700:3032::a29f:86e8'
CloudflareとFastlyを利用するStack Overflowによる、IPv6対応が難しい理由。ちょっと勿体ない。
Stack Overflow not reachable via IPv6 - Meta Stack Overflow
server:
local-zone: 'meta.stackoverflow.com.' typetransparent
local-data: 'meta.stackoverflow.com. 300 IN HTTPS 1 . alpn="h2" ipv4hint=104.18.32.7,172.64.155.249 ipv6hint=2606:4700::6812:2007,2606:4700::ac40:9bf9'
local-data: 'meta.stackoverflow.com. 300 IN AAAA 2606:4700::6812:2007'
local-data: 'meta.stackoverflow.com. 300 IN AAAA 2606:4700::ac40:9bf9'
local-zone: 'i.sstatic.net.' typetransparent
local-data: 'i.sstatic.net. 300 IN HTTPS 1 . alpn="h2" ipv4hint=104.18.41.33,172.64.146.223 ipv6hint=2606:4700::6812:2921,2606:4700::ac40:92df'
local-data: 'i.sstatic.net. 300 IN AAAA 2606:4700::6812:2921'
local-data: 'i.sstatic.net. 300 IN AAAA 2606:4700::ac40:92df'
このようにIPv6化の余地があるサービスは大抵Why No IPv6?(強い思想に注意)に載っている
Twitter 見出しにジャンプ
2024年9月現在、Twitter(自称𝕏)は多くの通信をIPv6で行い、Unboundで殆どの通信をIPv6化できる。
Cloudflare 見出しにジャンプ
イーロン・マスク氏がTwitterを買収されてから月日が経ち、少なくともt.co
, twitter.com
/x.com
, api.twitter.com
/api.x.com
に関してはCloudflareにBYOIPしているようだ。
curl -4 -i https://t.co/
# server: cloudflare tsa_m
Discordと同様にIPv6アドレスを計算すると、IPv6化できるた(2025年6月まで。現在はアクセスすると"IPv6"と警告される。バレてしまったようだ)。
server:
local-data: 'twitter.com. 1800 IN AAAA 2606:4700::68f4:2a01'
local-data: 'twitter.com. 1800 IN AAAA 2606:4700::68f4:2a41'
local-data: 'twitter.com. 1800 IN AAAA 2606:4700::68f4:2a81'
local-data: 'twitter.com. 1800 IN AAAA 2606:4700::68f4:2ac1'
local-data: 'x.com. 1800 IN AAAA 2606:4700::68f4:2a01'
local-data: 'x.com. 1800 IN AAAA 2606:4700::68f4:2a41'
local-data: 'x.com. 1800 IN AAAA 2606:4700::68f4:2a81'
local-data: 'x.com. 1800 IN AAAA 2606:4700::68f4:2ac1'
HTTP/3は非対応。
他のすべてのセッションからログアウトhttps://x.com/i/api/account/sessions/revoke_all
, Twitter for iPhoneからのコミュニティノート評価に非対応。
server:
# local-zone: 't.co.' static
# local-data: 't.co. 300 IN HTTPS 1 . alpn="h2" ipv4hint=104.244.42.5,104.244.42.69,104.244.42.133,104.244.42.197 ipv6hint=2606:4700::68f4:2a05,2606:4700::68f4:2a45,2606:4700::68f4:2a85,2606:4700::68f4:2ac5'
# local-data: 't.co. 300 IN A 104.244.42.5'
# local-data: 't.co. 300 IN A 104.244.42.69'
# local-data: 't.co. 300 IN A 104.244.42.133'
# local-data: 't.co. 300 IN A 104.244.42.197'
# local-data: 't.co. 300 IN AAAA 2606:4700::68f4:2a05'
# local-data: 't.co. 300 IN AAAA 2606:4700::68f4:2a45'
# local-data: 't.co. 300 IN AAAA 2606:4700::68f4:2a85'
# local-data: 't.co. 300 IN AAAA 2606:4700::68f4:2ac5'
local-zone: 't.co.' typetransparent
local-data: 't.co. 300 IN HTTPS 1 . alpn="h2" ipv4hint=162.159.140.229,172.66.0.227 ipv6hint=2606:4700::ac42:e3,2606:4700::ac42:e3'
local-data: 't.co. 300 IN AAAA 2606:4700::ac42:e3'
local-data: 't.co. 300 IN AAAA 2606:4700::ac42:e3'
# local-data: 't.co. 300 IN A 162.159.140.229'
# local-data: 't.co. 300 IN A 172.66.0.227'
t.co
は外部ドメインへのバウンストラッキングに使われる。Cloudflareに移行することで、チャレンジが頻発するようになってしまった。
CloudflareのWAFがIPアドレス単位のrate-limitをかけているか知らないが、CGNATなどのIPv4延命技術下では、同じIPアドレスから異なるユーザの大量にアクセスが予想されるため、避けたい。これにはIPv6化が有効だ。
t.co
が以前使っていたAS13414のIPと、現行のCloudflareのIPから、合わせて計6つIPv6アドレスが使えるので、安定したものを選ぶとよい。
server:
# 500 https://api.x.com/1.1/onboarding/task.json on desktop
local-zone: 'api.x.com.' typetransparent
local-zone-tag: "api.x.com." admin
local-data: 'api.x.com. 60 IN AAAA 2606:4700::68f4:2a02'
local-data: 'api.x.com. 60 IN AAAA 2606:4700::68f4:2a42'
local-data: 'api.x.com. 60 IN AAAA 2606:4700::68f4:2a82'
local-data: 'api.x.com. 60 IN AAAA 2606:4700::68f4:2ac2'
Web版のTwitterでは、サインイン時https://api.x.com/1.1/onboarding/task.json
が500を返すため、IPv4で接続しなければならない。上記は使えないと思ってよいだろう。
server:
# 500 https://api.twitter.com/1.1/onboarding/task.json on ios
local-zone: 'api.twitter.com.' typetransparent
local-zone-tag: 'api.twitter.com.' dualstack
local-data: 'api.twitter.com. 300 IN HTTPS 1 . alpn="h2" ipv4hint=104.244.42.2,104.244.42.66,104.244.42.130,104.244.42.194 ipv6hint=2606:4700::68f4:2a02,2606:4700::68f4:2a42,2606:4700::68f4:2a82,2606:4700::68f4:2ac2'
local-data: 'api.twitter.com. 300 IN AAAA 2606:4700::68f4:2a02'
local-data: 'api.twitter.com. 300 IN AAAA 2606:4700::68f4:2a42'
local-data: 'api.twitter.com. 300 IN AAAA 2606:4700::68f4:2a82'
local-data: 'api.twitter.com. 300 IN AAAA 2606:4700::68f4:2ac2'
# after 300sec, 200 https://tpop-api.twitter.com/1.1/onboarding/task.json
Twitter for iPhone(com.atebits.Tweetie2)では、未だにapi.twitter.com
が使われている。
一見Web版と瓜二つに見えるが、実際のフローは全く異なる。表の通り、typetransparent
ではAppleのスタブリゾルバがIN CNAME tpop-api.twitter.com.
をScrubするためだ。
dig api.twitter.com a +noall +answer
# api.twitter.com. 565 IN CNAME tpop-api.twitter.com.
# tpop-api.twitter.com. 450 IN A 104.244.42.2
このためAppleデバイス向けにはapi.twitter.com. IN A
かtpop-api.twitter.com. IN AAAA
を設定しないと、tpop-api.twitter.com. 450 IN A
- api.twitter.com. 300 IN A
の150秒間はIPv4で接続してしまう。
逆に言えば、DNSのレイヤでは通常api.twitter.com
以下のパスまで扱えないところ、https://api.twitter.com/1.1/onboarding/task.json
とhttps://tpop-api.twitter.com/1.1/onboarding/task.json
で判別できるため、IPv6化しても設定変更なく使用できる訳だ。api.twitter.com.
のTTLでIPv6の頻度を変えられるのも分かるだろう。キャッシュDNSで減算されたTTL次第で割合が変動し、その最大値は権威次第と留意しよう。
CloudFront 見出しにジャンプ
Twitterに𝕏というあだ名が付く以前は、twitter.com.
はAWS CloudFront特有のヘッダX-Amz-Cf-Pop:
を返していた。CloudFrontは、共有IPアドレスへのトラフィックをSNIを使って区別している。
CloudFront で HTTPS リクエストを処理する方法を選択する - Amazon CloudFront
[WEB-197] Minecraft authentication server not reachable over IPv6 - Jira
つまりAAAAが設定されていなくても、例えば以下のようにAAAAを含むCloudFrontドメイン名に向けることで、IPv6化できていた。現在は利用できない。
server:
local-zone: "twitter.com." redirect
local-zone-tag: "twitter.com" "admin"
local-data: "twitter.com. 60 IN CNAME dr49lng3n1n2s.cloudfront.net." # aws.amazon.com.
あれ?もしかして、Twitterって完全デュアルスタックいける?https://t.co/FBZxzaXmK9 pic.twitter.com/0ee83Dny68
— しばにゃん (@shibanyan_1) December 21, 2023
CloudFrontは基本的にIPv6やHTTP/3に対応しているため、よく使っているサービスも実は対応しているかもしれない。
server:
local-zone: 'www.amazon.co.jp.' typetransparent
local-data: 'www.amazon.co.jp 1800 IN HTTPS 1 . alpn="h3,h2"'
local-zone: 'm.media-amazon.com.' typetransparent
local-data: 'm.media-amazon.com 1800 IN HTTPS 1 . alpn="h3,h2"'
local-zone: 'images-fe.ssl-images-amazon.com.' typetransparent
local-data: 'images-fe.ssl-images-amazon.com 1800 IN HTTPS 1 . alpn="h3,h2"'
HTTP/3対応版のcurlで確認できる。
New – HTTP/3 Support for Amazon CloudFront | AWS News Blog
curl/docs/HTTP3.md at master · curl/curl
curl --http3-only --connect-timeout 1 --head -i "https://www.amazon.co.jp/"
# HTTP/3 405
TCP MSS 見出しにジャンプ
Twitterの画像サーバpbs.twimg.com
はFastlypbs-ft.twimg.com
と海外ではCloudflarepbs-cloudflare.twimg.com
, 電気自動車メーカーのCEOに買収されるまではAkamaipbs-ak.twimg.com
とEdgecastpbs-ec.twimg.com
、時期不明でGoogle Cloudpbs-gc.twimg.com
が稼働していた模様。
動画のvideo.twimg.com
や、Web版の動作に関わるabs.twimg.com
も命名規則は同じで、いずれもIPv6に対応している。
重箱の隅をつつくと、CDNによってTCP MSSに大きく開きがある。EdgecastもGoogleもCloudflarevideo-cf.twimg.com
もそうだが、QUICに対応すると同時に、TCPのMSSもまるでEDNSバッファサイズのように小さくしてしまう実装が多い。
日本のフレッツ系回線ではIPv6のデータグラムが逆転して大きいという状態なのに、ここにきてIPv6だけデータグラムが小さいCDNがあるとなっては書き換えるしかない。
server:
local-zone: "pbs.twimg.com." redirect
# pbs-ec.twimg.com IPv6 MSS: 1220, IPv4: 1420
# pbs-ft.twimg.com IPv6 MSS: 1394, IPv4: 1414
local-data: "pbs.twimg.com. 3600 IN CNAME dualstack.twimg.twitter.map.fastly.net."
local-zone: "abs.twimg.com." redirect
local-data: "abs.twimg.com. 3600 IN CNAME dualstack.twimg.twitter.map.fastly.net."
local-zone: "video.twimg.com." redirect
# video-cf.twimg.com IPv6 MSS: 1360
local-data: "video.twimg.com. 3600 IN CNAME dualstack.video.twitter.map.fastly.net."
純粋にTCP MSSの長さだけ考えるならAAAAをブロックするのも手。block_aaaa
は存在しないが、transparent
で表現できなくもない。
# local-zone: "pbs.twimg.com." transparent 暗黙
local-data: "pbs.twimg.com. 3600 IN A ..." # 静的にIPアドレスを設定
Fastlyでは、ドメイン名にdualstack.
を付けるだけでIPv6に対応できる場合がある。また、2a04:4e42::/32
をインクリメントして、IPv6対応ホストを見つけるプロジェクトもある。
画像やスクリプト関係は接尾辞159
、動画は158
だが、実はtwimg.com.
は2a04:4e42::157
から2a04:4e42::160
まで許可されている。3へクステット目が0000
ならAnycastで、日本では15
,1a
,36
,8c
辺りが使えるのではないかと思う。
openssl s_client -connect [2a04:4e42::157]:443 | openssl x509 -noout -text | grep DNS:
openssl s_client -connect [2a04:4e42::160]:443 | openssl x509 -noout -text | grep DNS:
# DNS:*.twimg.com, DNS:twimg.com, DNS:platform.twitter.com, DNS:cdn.syndication.twimg.com
IPv6Data/fastly at master · miyurusankalpa/IPv6Data · GitHub
画像の読み込みが遅い 見出しにジャンプ
現在𝕏は画像や動画のCDNにFastlyを使っている。こちらのGSLBがIPv6でうまく機能しておらず、海外にルーティングされる不具合が長期にわたって日本の複数のISP利用者から報告されている。
Fastly CDN causing slow loading of images from X(Twitter) - Network Services - Fastly Community
原因は、IPv6対応環境の𝕏利用者が、画像を読み込むHTTPリクエストhttps://pbs.twimg.com/media/...
の直前に発生する端末からのDNSクエリIN AAAA pbs.twimg.com.
に対し、ISPのキャッシュDNSサーバがIPv6アドレスを応答する際、Fastlyの権威DNSサーバが日本のISPからのクエリに対し海外リージョンのIPv6アドレスを応答してしまっているためだ。
dig pbs.twimg.com. aaaa +noall +answer pbs.twimg.com. 104 IN CNAME dualstack.twimg.twitter.map.fastly.net. dualstack.twimg.twitter.map.fastly.net. 26 IN AAAA 2a04:4e42:7a::159
sudo traceroute6 --tcp 2a04:4e42:7a::159 traceroute to 2a04:4e42:7a::159 (2a04:4e42:7a::159), 30 hops max, 80 byte packets 1 2001:db8:b0ba:10ee:271:12ff:fe34:5678 (2001:db8:b0ba:10ee:271:12ff:fe34:5678) 0.498 ms * * 2 * * * 3 * * * 4 * * * 5 * * * 6 2001:380:a320:9::1 (2001:380:a320:9::1) 9.182 ms 7.904 ms 10.231 ms 7 2001:380:a310:14::1 (2001:380:a310:14::1) 9.468 ms 2001:380:a310:13::1 (2001:380:a310:13::1) 9.108 ms 2001:380:a310:14::1 (2001:380:a310:14::1) 9.111 ms 8 2001:380:a140:4::1 (2001:380:a140:4::1) 15.295 ms 2001:380:a300:9::1 (2001:380:a300:9::1) 9.877 ms 2001:380:a140:4::1 (2001:380:a140:4::1) 13.204 ms 9 2001:380:a140:5::2 (2001:380:a140:5::2) 13.756 ms 11.541 ms 12.105 ms 10 ae-8.a04.tokyjp05.jp.bb.gin.ntt.net (2001:218:2000:5000::841) 16.332 ms 12.943 ms ae-9.a03.tokyjp05.jp.bb.gin.ntt.net (2001:218:2000:5000::839) 13.698 ms 11 * ae-6.r32.tokyjp05.jp.bb.gin.ntt.net (2001:218:0:2000::262) 12.226 ms * 12 ae-5.r26.sttlwa01.us.bb.gin.ntt.net (2001:218:0:2000::199) 112.225 ms 122.048 ms 114.652 ms 13 ae-18.a02.sttlwa01.us.bb.gin.ntt.net (2001:418:0:2000::61) 121.018 ms 118.345 ms 117.603 ms 14 2001:418:0:5000::3bd (2001:418:0:5000::3bd) 102.803 ms 103.740 ms 101.280 ms 15 2a04:4e42:7a::159 (2a04:4e42:7a::159) 124.614 ms 119.880 ms 127.666 ms
※sttlwa
はシアトル・ワシントンのデータセンターを示す。海底ケーブルで太平洋を超えた先のサーバから、どうでもいい画像を取得するのに時間がかかってイライラしたと分かる。
Submarine Cable Map
海外との通信では、速度に問題がなくともバッファが多く遅延が大きいために、画像表示が遅れる。バッファと遅延と速度の関係を理解して対抗する術は、過去にNEC IXシリーズのQoSの解説で述べているが、今回は日本にルーティングされるように仕向ければ自ずと遅延は小さくなるはずだ。
ユーザの間で、端末設定でISPのキャッシュDNSからPublic DNSに変更して問題解消したとされるツイートも散見された。CDN事業者によっては、EDNS Client Subnetの制御がキャッシュDNSによって変わる例もあるので否定はできないが、前述の通り応答内容を決定しているのはあくまでもCDN事業者側で(ISPは基本的に応答を改ざんしない)、またデベロッパーツールやdigを使っていない体感の報告と思われアテにならないと考えている。特にIPv4共有技術が使われているフレッツIPv6やNURO利用者が8.8.8.8を端末に設定すると、別のパフォーマンスの問題を起こすため推奨されない。
通常の利用者はこの問題に成すすべはないが、Unboundをセルフホストをすれば権威DNSの応答を容易に改ざんできる。Fastlyでは3ヘクステット目が:0000:
、つまり2a04:4e42::159
はAnycastとして振る舞うようなので、これに書き換えればよい。
あるいは、俗に首振りDNSと言われるFQDN毎にフォワード先を決める機能を持ったヤマハルータやNEC IXを利用して、Public DNSにフォワードするのも良い。Cloudflare Zero Trust DNSの上書き機能も使えるだろう。
注意点として、Appleデバイスにはそのスタブリゾルバの動作に沿った対処が必要だ。IN AやIN AAAAの応答に含まれるCNAMEをScrubbingし、iOS 14/macOS 11以降はIN HTTPSも含まれる(pcapあるよ)。これを踏まえて、下記に設定例を示す。
- IPv4非対応。IPv6が確実に動作していれば画像を読み込める。access-control-tagで、
pbs.twimg.com
がIPv6シングルスタックでも構わない端末に限定してIN AAAAのみ応答する。
server:
local-zone: 'pbs.twimg.com.' static
local-zone-tag: 'pbs.twimg.com.' dualstack
local-data: 'pbs.twimg.com. 300 IN AAAA 2a04:4e42::159'
# 動画
local-zone: 'video.twimg.com.' static
local-zone-tag: 'pbs.twimg.com.' dualstack
local-data: 'video.twimg.com. 300 IN AAAA 2a04:4e42::158'
# Twitter Web等
local-zone: 'abs.twimg.com.' static
local-zone-tag: 'abs.twimg.com.' dualstack
local-data: 'abs.twimg.com. 300 IN AAAA 2a04:4e42::159'
- IPv4対応。
IN A
やIN HTTPS
からIN AAAA dualstack.twimg.twitter.map.fastly.net.
がScrubされるのを許容し、local-zoneで受け止める。数が多くないので、IN HTTPSに非対応の首振りDNSでも使える手法。
server:
local-zone: 'pbs.twimg.com.' typetransparent
local-data: 'pbs.twimg.com. 300 IN AAAA 2a04:4e42::159'
local-zone: 'dualstack.twimg.twitter.map.fastly.net.' typetransparent
local-data: 'dualstack.twimg.twitter.map.fastly.net. 300 IN AAAA 2a04:4e42::159'
- 主に欧米で使われている
pbs-cloudflare.twimg.com
にCNAME。海外Fastlyよりマシだが、日本で利用する分にはキャッシュヒット率が低いのかFastlyが優秀なのかパフォーマンスが劣る。
server:
local-zone: 'pbs.twimg.com.' redirect
local-data: 'pbs.twimg.com. 300 IN CNAME pbs.twimg.com.cdn.cloudflare.net.'
local-zone: 'video.twimg.com.' redirect
local-data: 'video.twimg.com. 300 IN CNAME video.twimg.com.cdn.cloudflare.net.'

RIPE Atlas - Measurement Detail
HTTP/3化 見出しにジャンプ
※現在は使用不可
Edgecastpbs-ec.twimg.com.
はHTTP/3に対応しているが、Alt-SvcにないためHTTP/3にアップグレードされることはない。
HTTPSの接続情報を通知する "HTTPS DNSレコード" の提案仕様 (2021/07更新) - ASnoKaze blog
HTTPS RRを用意すれば、AppleデバイスでHTTP/3接続される。
TN3102: HTTP/3 in your app | Apple Developer Documentation
server:
local-zone: 'pbs.twimg.com.' typetransparent
local-data: "pbs.twimg.com. 60 IN AAAA 2606:2800:248:1707:10d3:19d0:1ba2:1a23"
local-data: 'pbs.twimg.com. 60 IN HTTPS 1 . alpn="h3" ipv6hint=2606:2800:248:1707:10d3:19d0:1ba2:1a23'
Appleデバイスでは、Alt-Svcがあれば従う(HTTP/2にダウングレードする)が、無ければHTTPS RRを見ているような気がする。
RFC 9460: Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records)
アプリの表示崩れを修正 見出しにジャンプ
最近𝕏(通称Twitter)利用者から「興味がない」が押せない広告や、画面いっぱいにユーザ名も何もない謎の余白が広がるといった不具合が寄せられているが、当環境では再現しなかった。
iOSユーザーなら
— ハイオメガシシャモ (@SHISHAMO_SEA) July 21, 2024
"https://t.co/wJX2ke8Wcm"
このURLブロックしたらtwitterのブロックできない系の広告全部消えるんだよね https://t.co/hFQuZZhSsS pic.twitter.com/a9ONvLQVmk
TwitterにはTwitter広告があるのでまさかと思ったが、確かにアプリからdoubleclick.net.
のクエリがなされていた。
unbound-control set_option log-queries: yes
[1722315240] unbound[4955:3] info: 10.0.0.13 firebaselogging-pa.googleapis.com. HTTPS IN
[1722315240] unbound[4955:3] info: 10.0.0.13 googleads.g.doubleclick.net. A IN
[1722315240] unbound[4955:3] info: 10.0.0.13 googleads.g.doubleclick.net. AAAA IN
[1722315240] unbound[4955:3] info: 10.0.0.13 googleads.g.doubleclick.net. HTTPS IN
[1722315240] unbound[4955:3] info: 10.0.0.13 dualstack.video.twitter.map.fastly.net. HTTPS IN
unbound-control set_option log-queries: no
doubleclick.net.
はGoogle広告に使われているドメインで、これはMalvertisingを含む場合があるため既にセキュリティ対策が施されていた。
server:
local-zone: "doubleclick.net." static
local-data: "doubleclick.net. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1 1800 900 604800 86400"
Twitter(現𝕏)では"とりあえず動くからリリース"した様子が伺えた。
動きを減らす 見出しにジャンプ
Twitter Appには、画面遷移のアニメーションやライブエンゲージメントを減らすアクセシビリティ設定「動きを減らす」がある。
アニメーションはUIの軽快さに一役買っている一方、ライブエンゲージメントは常にprobe.twitter.com
へTCPコネクションを張り続けて、RTやふぁぼをリアルタイムに取得するため、バッテリーへの影響が大きい。発熱の問題を報告するユーザも少なくない。
そこで私はアニメーションはそのままに、ライブエンゲージメントを止めるルールを書いた。
server:
module-config: "respip iterator"
# probe.twitter.com
response-ip: 104.244.42.3/32 always_nxdomain
response-ip: 104.244.42.67/32 always_nxdomain
response-ip: 104.244.42.131/32 always_nxdomain
response-ip: 104.244.42.195/32 always_nxdomain
local-zone: "api-stream.twitter.com." static
local-zone: "api-stream.x.com." static
local-zone: "probe.twitter.com." static
local-zone: "probe.x.com." static
local-zone: 'albtls.t.co.' static
local-zone: 'cftls.t.co.' static
local-zone: "dc-api.twitter.com." static
local-zone: "api-0-4-0.twitter.com." static
local-zone: "api-0-4-2.twitter.com." static
local-zone: "api-0-4-3.twitter.com." static
# local-zone: "api-0-4-4.twitter.com."
local-zone: "api-0-4-5.twitter.com." static
local-zone: "api-0-4-6.twitter.com." static
local-zone: "api-0-4-7.twitter.com." static
local-zone: "api-0-4-8.twitter.com." static
local-zone: "api-0-5-0.twitter.com." static
local-zone: "api-1-0-0.twitter.com." static
# local-zone: "api-2-0-0.twitter.com."
# local-zone: "api-3-0-0.twitter.com."
# local-zone: "api-20-0-0.twitter.com."
# local-zone: "api-21-0-0.twitter.com."
# local-zone: "api-23-0-0.twitter.com."
# local-zone: "api-24-0-0.twitter.com."
# local-zone: "api-26-0-0.twitter.com."
# local-zone: "api-28-0-0.twitter.com."
# local-zone: "api-30-0-0.twitter.com."
local-zone: "api-31-0-0.twitter.com." static
# local-zone: "api-32-0-0.twitter.com."
local-zone: "api-33-0-0.twitter.com." static
# local-zone: "api-34-0-0.twitter.com."
# local-zone: "api-36-0-0.twitter.com."
local-zone: "api-37-0-0.twitter.com." static
# local-zone: "api-38-0-0.twitter.com."
# local-zone: "api-39-0-0.twitter.com."
# local-zone: "api-40-0-0.twitter.com."
# local-zone: "api-41-0-0.twitter.com."
# local-zone: "api-42-0-0.twitter.com."
local-zone: "api-43-0-0.twitter.com." static
local-zone: "api-44-0-0.twitter.com." static
# local-zone: "api-45-0-0.twitter.com."
local-zone: "api-46-0-0.twitter.com." static
local-data: "publish.twitter.com. 1800 IN A 104.244.42.3"
local-data: "publish.twitter.com. 1800 IN A 104.244.42.67"
local-data: "publish.twitter.com. 1800 IN A 104.244.42.131"
local-data: "publish.twitter.com. 1800 IN A 104.244.42.195"
Apple Push Notification service 見出しにジャンプ
Apple, Google含めプッシュ通知と呼ばれるものは大概、息の長いTCPコネクションで行われる。
この際、Keep-Alive間隔の長さゆえCPEやAFTRのNAPTキャッシュからセッション情報が消される場合がある。こうなると最悪、端末がKeep-Alive間隔待ってから新たにTCPコネクションを張りなおすまでプッシュ通知は届かない。逆にRFC通りに動作するとAterm病と汚名を付けられるくらいには難のあるところだ。
ともあれNATしないIPv6を通せば何ら問題ない。
Appleは17.0.0.0/8
つまり世界のIPv4アドレスプールの1/256を占める米国企業だ。そのためIPv4でも満足に事業を展開できるはず。
しかしながら、2016年という早い時期からアプリストアの審査条件にDNS64/NAT64対応を加えている。
IPv6のみのネットワークのサポート - サポート - Apple Developer
AppleがIPv6を推進する理由はひとえに、顧客側の接続性にあると言えよう。
(思想が強いだけかもしれないが)
Appleのプッシュ通知サービス(APNs)には5223/tcpが使われており、管理者向けにIPv6アドレスの範囲も公開されている。
エンタープライズネットワークで Apple 製品を使う - Apple サポート (日本)
Apple 製のデバイスで Apple プッシュ通知が届かない場合 - Apple サポート (日本)
ところが、iPhoneをtcp port 5223
でパケットキャプチャしても17.0.0.0/8
の通信しか流れていなかった。どうやら2022年の一時期を除き、IPv4シングルスタックで提供されてるようだ(iOS 18現在)。
syslog_20220325_223125:
45732: 2022/03/20 16:45:57: [INSPECT] LAN2[out][63900] TCP 2001:db8:b0ba:10ee::53e1.52859 > 2620:149:a44:30c::9.5223 (2022/03/20 16:41:57)
45734: 2022/03/20 16:45:59: [INSPECT] LAN2[out][63900] TCP 2001:db8:b0ba:10ee::53e1.52858 > 2620:149:a44:30c::6.5223 (2022/03/20 16:41:57)
62272: 2022/03/23 03:07:40: [INSPECT] LAN2[out][63900] TCP 2001:db8:b0ba:10ee::9395.52401 > 2a01:b740:a42:50c::9.5223 (2022/03/23 03:03:41)
62648: 2022/03/23 08:48:05: [INSPECT] LAN2[out][63900] TCP 2001:db8:b0ba:10ee::9395.52400 > 2a01:b740:a42:50c::b.5223 (2022/03/23 03:03:40)
syslog_20220402_163206:
21582: 2022/03/27 02:32:29: [INSPECT] LAN2[out][63900] TCP 2001:db8:b0ba:10ee::7436.59855 > 2620:149:a44:1100::6.5223 (2022/03/27 02:10:43)
syslog_20220605_135143:
28059: 2022/05/22 01:06:29: [INSPECT] LAN2[out][63900] TCP 2001:db8:b0ba:10ee::36f4.62427 > 2403:300:a42:c0a::7.5223 (2022/05/21 17:09:51)
syslog_20220924_122548:
71022: 2022/09/17 16:32:44: [INSPECT] LAN2[out][63900] TCP 2001:db8:b0ba:10ee::af64.49708 > 2620:149:a44:a0c::8.5223 (2022/09/17 16:24:31)
syslog_20221118_010457:
80047: 2022/11/17 15:14:45: [INSPECT] LAN2[out][63900] TCP 2001:db8:b0ba:10ee::ccab.49916 > 2620:149:a44:80c::c.5223 (2022/11/17 08:49:52)
dig 0-courier.push.apple.com aaaa +noall +answer
0-courier.push.apple.com. 7716 IN CNAME 0.courier-push-apple.com.akadns.net.
0.courier-push-apple.com.akadns.net. 28 IN CNAME apac-asia-courier-4.push-apple.com.akadns.net.
私にそんな趣味はないが、プッシュ通知が遅れて一コメを取れなかったら泣いてしまう人もいるだろう。
プッシュ通知は再送を保証されていない[要出典]ので、単に新着動画を見逃すこともあるだろうし、例えば推しのVTuberの枠を開いたころには既に打ち上っていて軌道に乗っていたら、再突入試験でもない限りげんなりするだろう。
アクセスログにあるホストへTCP接続すると、courier2.push.apple.com
がAAAAレコードを持つと分かった。
openssl s_client -connect [2403:300:a42:20c::6]:5223 | openssl x509 -noout -text | grep DNS:
# DNS:courier.push.apple.com, DNS:courier2.push.apple.com, DNS:windows.courier.push.apple.com
dig courier2.push.apple.com aaaa +noall +answer
# courier2.push.apple.com. 28800 IN CNAME 1.courier2-push-apple.com.akadns.net.
# 1.courier2-push-apple.com.akadns.net. 60 IN CNAME apac-asia-courier-vs.push-apple.com.akadns.net.
# apac-asia-courier-vs.push-apple.com.akadns.net. 60 IN AAAA 2403:300:a42:c0a::5
# ...
これをUnboundに設定すると下記のようになる。
server:
local-zone: "init-p01st.push.apple.com." transparent
local-zone: "homekit.push.apple.com." transparent
local-zone: "init.push.apple.com." transparent
local-zone: "sandbox.push.apple.com." transparent # TestFlight init., n-courier., courier.
local-zone: "push.apple.com." inform_redirect
local-zone-tag: "push.apple.com" dualstack
local-data: "push.apple.com. 60 IN AAAA 2403:300:a42:902::6"
local-data: "push.apple.com. 60 IN AAAA 2403:300:a42:902::7"
local-data: "push.apple.com. 60 IN AAAA 2403:300:a42:902::8"
local-data: "push.apple.com. 60 IN AAAA 2403:300:a42:902::9"
local-data: "push.apple.com. 60 IN AAAA 2403:300:a42:902::a"
...
local-data: "push.apple.com. 60 IN AAAA 2403:300:a42:902::13"
0-courier.push.apple.com
から50-courier.push.apple.com
までを一括設定するために、push.apple.com.
ゾーンをリダイレクトする。
これにinit.push.apple.com.
等が巻き込まれないためにtransparent
で除外する。
これにより*push.apple.comはlocal-dataの内容だけを応答する。後述するが、IPv4接続性が必要でもtypetransparent
は適合しない。- APNsへのIPv4接続性が必要な場合、このlocal-zoneの応答をIPv6接続性のあるaccess-control-tagに限定する。
typetransparent
やCNAMEにredirect
するとIPv4を優先してしまうので、AAAAをハードコーディングした。 CNAMEがある場合、それ以外のレコードは存在してはならないため、空のAを追加することはできない。 RPZとblock_a
を組み合わせる方法もあるが、- いずれにせよ
apac-asia-courier-vs.push-apple.com.akadns.net.
にCNAMEを向けるのは、APACのCDNをランダムに応答するためかえって遅延が大きくなる。
IPinfo.ioでのregion確認、SecurityTrailsで別リージョンの発見行った。nmapの計測では東西を跨がない方が遅延が小さかった。
# 2403:300:a42:900::/56 Tokyo
nmap -6 -Pn -p 5223 2403:300:a42:900::6 # apac-china-courier-vs.
nmap -6 -Pn -p 5223 2403:300:a42:900::8 # apac-china-courier-vs.
nmap -6 -Pn -p 5223 2403:300:a42:901::6
nmap -6 -Pn -p 5223 2403:300:a42:901::13
nmap -6 -Pn -p 5223 2403:300:a42:902::6
nmap -6 -Pn -p 5223 2403:300:a42:902::13
nmap -6 -Pn -p 5223 2403:300:a42:903::6
nmap -6 -Pn -p 5223 2403:300:a42:903::15
# 2403:300:a42:200::/56 Osaka
nmap -6 -Pn -p 5223 2403:300:a42:20c::6
nmap -6 -Pn -p 5223 2403:300:a42:20c::7
# 2403:300:a42:b00::/56 apac-taiwan-courier-vs.
nmap -6 -Pn -p 5223 2403:300:a42:b0c::6
# 2403:300:a42:c00::/56 Singapore
nmap -6 -Pn -p 5223 2403:300:a42:c0a::6
APNsホストはpingに応答しないが、当然TCPはリッスンしているのでRTTを計測できる。nmapならWindows 10のWSL1でも動く。
apac-asia-courier-vs.
に含まれる最も遅延が小さいapac-china-courier-vs.
は、やんごとなき理由か5223/tcpでAPNsに繋がらなかったため、除外した。- 調査の副産物で、
apac-china-courier-vs.
への5223/tcpが失敗且つAがNODATAだと443/tcpにフォールバックするが、このように空のlocal-data(ANSWER: 1)で応答すると5223/tcpを試行し続けた。
未知の要因で発生する443/tcpへのフォールバックを回避するのに使えるが、5223/tcpが壊れ気味なのでおすすめしない。
local-data: "push.apple.com. 60 IN A"
任意の回線をスピードテストする 見出しにジャンプ
Webのスピードテストは、IPv4/IPv6どちらをテストしたか不明瞭だったり、選べないことが多い。
そんなときはDNSで振り分けるのが簡単だ。Speedtest by OoklaはIPv6計測専用にしたいし、Wi-FiミレルはIPv4計測用に使いたかった。
server:
local-zone: "prod.hosts.ooklaserver.net." block_a
local-zone: "wifimireru.inonius.net" typetransparent
local-data: "wifimireru.inonius.net 3600 IN AAAA"
Minecraft 見出しにジャンプ
Minecraft Launcherの高速化 見出しにジャンプ
IPv4を優先したりするので。突然ランチャーアップデートが来ても、一秒でも早くゲームに参加したい。
server:
local-zone: "redstone-launcher.mojang.com." block_a
local-zone: "api.mojang.com." block_a
local-zone: "pc.realms.minecraft.net." block_a
local-zone: "textures.minecraft.net." block_a
api.minecraftservices.com
とsessionserver.mojang.com
にもAAAAがあるが、これらはJavaがIPv4優先-Djava.net.preferIPv6Addresses=false
なことを背景に機能しているので、クライアント・サーバいずれもIN Aをブロックしてはいけない。
Mojira - Issue MC-172541 サーバは"logging in from another location"
Mojira - Issue MC-253237 クライアントは"Failed to verify username"
Minecraftサーバの管理者にメッセージを送る 見出しにジャンプ
Minecraftサーバへの接続は、SRV, A, AAAAの順で試行される。接続に使われたSRVレコードの内容はサーバ側に伝わる。
Protocol - wiki.vg
例えば、下記のように設定をするとmc.example.net.
への接続にmake-peace-with.cat.
を利用でき、猫と和解するよう相手に要求できる。管理者がコネクション確立時のログを読んでいれば、の話だが。
server:
local-zone: "cat." static
local-data: "_minecraft._tcp.make-peace-with.cat. 60 IN SRV 0 0 25565 mc.example.net."
DoT downstream 見出しにジャンプ
Unbound <--> キャッシュDNS間のupstreamにDo53を使っているにも拘らず、端末 <--> Unbound間のdownstreamにDoTを使う馬鹿みたいな設定を行った。
Do53はキャッシュポイズニングの軽減にポートランダマイゼーションを行うが、フルトラストネットワークでは無駄なオーバーヘッドでしかないからだ。
DoTはApple macOS 11+/iOS 14+、Android 9+、Windows 11と主にOSの対応が主なのに対し、DoHはブラウザでの対応が中心だから、自動設定に重きを置くならDoT、パフォーマンスに重きを置くならDoHになるだろう。
実際のところ、例えばApple iOSが繰り出すDo53クエリは、リゾルバへのDoSではないかと思うほど高速だ。主に非同期なURLSessionを使っているためと思われる。それでも、Appleデバイスは通常のHappy Eyeballs比で1.5 - 3倍のクエリを発するため、暗号化のオーバーヘッドを超えるかも分からんな、程度のモチベーションだ。
server:
interface: 10.0.3.5@853
port: 853
tcp-idle-timeout: 120000
edns-tcp-keepalive: yes
# edns-tcp-keepalive-timeout: 120000
# unbound-control-setup で生成した自己署名証明書
tls-service-key: "/usr/local/etc/unbound/unbound_server.key"
tls-service-pem: "/usr/local/etc/unbound/unbound_server.pem"
tls-port: 853
tcp-idle-timeout
はdownstreamにも有効らしく、頻繫にコネクションを張りなおしていては意味がないので2分(2MSL)に設定した。edns-tcp-keepalive: yes
時はedns-tcp-keepalive-timeout
を優先するとか。
Reduce number of TLS connections to forwarded (DoT) when using "forward-tls-upstream" · Issue #47 · NLnetLabs/unbound
# WSL2
dig @10.0.3.5 www.google.com aaaa +tls
DDR 見出しにジャンプ
Do53をListenするリゾルバを端末に自動設定するなら、多くのOSが対応しているDHCPやDHCPv6, RAオプションが使用できる。
ただし、DoTやDoHはIPアドレスだけでなくホスト名や接続方式、ポート番号、DoHなら更にパスの情報を用いて接続するため、端末が_dns.resolver.arpa. IN SVCB
をクエリすることで機能するDDRと、DHCP/DHCPv6オプションで通知するDNRを使う。
RFC 9462 - Discovery of Designated Resolvers
RFC 9463 - DHCP and Router Advertisement Options for the Discovery of Network-designated Resolvers (DNR)
Apple macOS 13+/iOS 16+はDDRやMDMによるDoT/DoHの自動設定に対応している。後述の構成プロファイルで暗号化リゾルバを手動設定できるが、Do53はできない。
AppとサーバのDNSセキュリティの改善 - WWDC22 - ビデオ - Apple Developer
Android 9以降はOpportunistic Discovery(Do53リゾルバのアドレスにDoTを試行)&日和見暗号化する。
DNS over HTTPS/TLS (DoH/DoT)の設定方法 | IIJ Engineers Blog
RubyKaigi 2023でのセキュアなDNSリゾルバの運用 (DNSOPS.JP BoF IW2023) - 20231121-hanazuki.pdf
Windows 11はDDRはBuild 22489で正式対応、DNRはBuild 25982以降で対応する。NEC IXのdhcpdにDNRを設定したが、24H2で動作している様子はない。
Introducing DNR support for Windows Insiders
Unboundが管理するのはDDRの方だ。
server:
local-zone: '_dns.resolver.arpa.' static
local-zone-tag: '_dns.resolver.arpa.' windows
local-data: '_dns.resolver.arpa. 60 IN SVCB 1 dot.example.com. alpn="dot" port=853 ipv4hint=10.0.3.5'
# DNS rebinding対策から除外
local-zone: 'dot.example.com' static
local-data: 'dot.example.com 300 IN A 10.0.3.5'
# certbot
tls-service-key: "/etc/letsencrypt/live/dot.example.com/privkey.pem"
tls-service-pem: "/etc/letsencrypt/live/dot.example.com/fullchain.pem"
- プライベートアドレス範囲の応答はDNS rebinding対策で潰しているため、通常の名前解決は行わずにlocal-zoneで静的に設定した。
dig +tls
は自己署名証明書でも通ったが、後述の構成プロファイルの挙動からAppleがVerified Discoveryのみ対応しているとみて、正規の証明書を設定した。
certbot certonly --manual --preferred-challenges dns -d dot.example.com -m mail@example.com
openssl s_client -connect 10.0.3.5:853 | openssl x509 -noout -text | grep -A1 'Subject Alternative Name'
# X509v3 Subject Alternative Name:
# DNS:dot.example.com
とりあえずcertbotでSSL証明書を発行する #Let’sEncrypt - Qiita
- Appleデバイス(iOS 17.6.1時点)は、DDRを試行するものの下記エラーが出てDoT接続が持続しなかった。自己署名かは関係なかった。
[1723521202] unbound[6426:1] error: ssl handshake failed crypto error:0A000416:SSL routines::ssl/tls alert certificate unknown
SSL handshake failed · Issue #561 · NLnetLabs/unbound
- Appleデバイスは、構成プロファイルでのDoT/DoH設定に対応しており、DDRと違って機能した。
DNSSettings | Apple Developer Documentation
下記の例では、OnDemandRules
によって既知のWi-Fi接続時にプライベートなDoTサーバへ接続する設定になっている。これにより同じくOnDemandRules
で自動接続するVPNプロファイルと協調して動作する。
ユニークなPayloadUUID
を指定する。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>DNSSettings</key>
<dict>
<key>DNSProtocol</key>
<string>TLS</string>
<key>ServerAddresses</key>
<array>
<string>10.0.3.5</string>
</array>
<key>ServerName</key>
<string>dot.example.com</string>
</dict>
<key>OnDemandRules</key>
<array>
<dict>
<key>InterfaceTypeMatch</key>
<string>WiFi</string>
<key>SSIDMatch</key>
<array>
<string>ssid1</string>
<string>ssid2</string>
</array>
<key>Action</key>
<string>Connect</string>
</dict>
<dict>
<key>Action</key>
<string>Disconnect</string>
</dict>
</array>
<key>PayloadDescription</key>
<string>com.example.dotpayload</string>
<key>PayloadDisplayName</key>
<string>com.example.dotpayload</string>
<key>PayloadIdentifier</key>
<string>com.example.dotpayload</string>
<key>PayloadType</key>
<string>com.apple.dnsSettings.managed</string>
<key>PayloadUUID</key>
<string>6d16eda2-3a25-4db1-8f51-5c7ea915a7f1</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Unbound DoT</string>
<key>PayloadIdentifier</key>
<string>com.example.dot</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>703ddb36-2a72-46f3-b566-2bd1970e4b5b</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>