🖥️

Dnsmasq で おうち DNS on Ubuntu 20.04 LTS

2022/06/19に公開

GitLab の Pages とか Environments とかは DNS、しかもワイルドカードレコードが必須なわけですが、手許でさくっと試せるようにしたくて、おうち DNS を立てることにしました。
用意した環境は、とりあえず、今使っている Windows 10 マシンに VMware Workstation Player で立てた Ubuntu 20.04 LTS の VM です。省エネで常時動作させるために Rapsberry Pi 4 でやってみたかったんだけど、超品薄[1]なため一旦断念。そのうち買って移したいところです。

始める前に: Ubuntu のローカルリゾルバー

今回これをやり始めて初めて知ったんですが[2]、Ubuntu は "systemd-resolved" というローカルリゾルバーサービスを介して名前解決をしています。図にするとこんな感じ。

で、どうやら /etc/systemd/resolved.confDNSStubListenerExtra= に外向きのアドレスを書けば、ローカルマシンだけでなく外向きにもリゾルバーサービスを提供できそう[3]、つまり Dnsmasq を入れなくてもよいかも[4]、と思ったのですが、残念ながら DNSStubListenerExtra=systemd v247 からサポートでした。20.04 に入っているのは v245。

# systemd --version
systemd 245 (245.4-4ubuntu3.17)

ところで、ちょっと横道に逸れますが、systemd-resolved は 127.0.0.53 をリッスンしています。localhost に代表されるローカルループバックアドレスは 127.0.0.0/8 なので、こういうこともできるわけですが、実用的に意味がある目的に使われている例を初めて見た気がします。どういう意味かは少し後で書きます。

Dnsmasq のインストールと設定

というわけで、systemd-resolved だけでやるのはさっくり諦めて、素直に Dnsmasq を入れることにします。sudo apt install dnsmasq だけで入ります。

OS 標準の仕組みを弄くりまくったり止めたりすると、後々めんどくさいことが起きがちなので、既存の systemd-resolved まわりはできる限りそのまま活かして、そこにうまく Dnsmasq をはめ込もうと考えました。ここで前述の 127.0.0.53 がうまい具合に働きます [5]。Dnsmasq を、それとは別のループバックアドレス、まぁ素直に普通の 127.0.0.1 にバインドすれば、コンフリクトしないわけです。図にするとこんな感じ。ここで、外向きのアドレスは 192.168.1.160 にしてありますが、空いてた以外に特に意味はありません。言わずもがなですが、このアドレスは DHCP サーバーで固定に設定してあります。

これを設定に落としていきます。

/etc/dnsmasq.conf (デフォルト値を変えた箇所のみ)

no-resolv
no-poll
server=192.168.1.1
bind-interfaces
listen-address=127.0.0.1
listen-address=::1
listen-address=192.168.1.160
listen-address=fe80::20c:29ff:fef9:a2a1
address=/pages.internal/192.168.1.161
no-dhcp-interface=

no-resolv, no-poll: /etc/resolv.conf が systemd-resolved を見に行くようになっているので、Dnsmasq がそうやってループしてしまわないよう /etc/resolv.conf 参照を抑止
server=: その代わりにローカルネットワークの DNS に直接問い合わせるように設定
bind-interfaces: liseten-address に指定したアドレスのインタフェースだけバインドするように指定
listen-address=: バインドするインターフェースのアドレス
address=: ワイルドカードドメインの指定 (/etc/hosts*.pages.internal のように書いても効かなくて、ここにこう書く必要があります)
no-dhcp-interface=: DHCP は既にあるやつを使うので抑止

/etc/systemd/resolved.conf (デフォルト値を変えた箇所のみ)

[Resolve]
DNS=127.0.0.1

DNS=: systemd-resolved が問い合わせに行く上流の DNS。ここに Dnsmasq の listen-address を書く

設定したら、両者を restart して

# systemctl restart dnsmasq systemd-resolved

netstat がこんな感じになれば OK。

# netstat -nlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN      15153/dnsmasq
tcp        0      0 192.168.1.160:53        0.0.0.0:*               LISTEN      15153/dnsmasq
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      13765/systemd-resol
tcp6       0      0 ::1:53                  :::*                    LISTEN      15153/dnsmasq
tcp6       0      0 fe80::20c:29ff:fef9::53 :::*                    LISTEN      15153/dnsmasq
udp        0      0 127.0.0.1:53            0.0.0.0:*                           15153/dnsmasq
udp        0      0 192.168.1.160:53        0.0.0.0:*                           15153/dnsmasq
udp        0      0 127.0.0.53:53           0.0.0.0:*                           13765/systemd-resol
udp6       0      0 ::1:53                  :::*                                15153/dnsmasq
udp6       0      0 fe80::20c:29ff:fef9::53 :::*                                15153/dnsmasq

クライアントの設定

Linux

Ubuntu (16.10 以降)

上の図で Remote machine のところに書いたように、/etc/systemd/resolved.conf を下記のように変更します。

[Resolve]
DNS=192.168.1.160

その他のディストリビューション

調べてません、ごめんなさい。

WSL

/etc/resolv.conf がこんな風になっていて、

# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 172.21.0.1

vEthernet (WSL) というアダプターを通じて Windows ホストを参照するようになっています。なので、Windows に任せておけばよいのですが、次項で書くように Windows 側がどうも不安定な感じがあるので、指示されているように /etc/wsl.conf を作って下記のように入れて、

[network]
generateResolvConf = false

/etc/resolv.con を下記のように変更して、直接 Dnsmasq を参照するようにしました。

nameserver 192.168.1.160

Windows 10

ネットワークアダプタのプロパティの、インターネット プロトコル パージョン 4 (TCP/IPv4)インターネット プロトコル パージョン 6 (TCP/IPv6) のそれぞれのプロパティを開き、

下図のように、優先 DNS サーバー に Dnsmasq のアドレスを設定します。代替 DNS サーバー の方にはもともとの DNS サーバーのアドレスを残しておきましょう。

nslookup では引けるのに ping が DNS エラーになる場合

設定は以上なのですが、nslookup では名前解決できているのに ping では ホストが見つかりません エラーになってしまうという、ググるとたくさんヒットする現象に見舞われます。この違いは、nslookup は DNS を直接参照するのに対して、ping などの WinSock クライアントは Windows の DNS クライアントサービスを使っているから生じているらしいです。
この場合は、Windows の コマンドプロンプト を管理者権限で起動して下記のコマンドを実行すれば

ipconfig /registerdns

だいたい解決します。そうです、「だいたい」です。解決しないことや、解決しても何かの拍子にまただめになっていることもあって、これが上で「不安定」と言っていたやつです。
DNS サーバー (Dnsmasq) が Windows 上の VM なので、Windows のネットワークサービスの起動と、VM のサスペンドからの復帰とのタイミングとかが関係してるような気がします。早く Rapsberry Pi 4 買って常時動作にしたい (いや普通に PC 買えよ (だって先立つ物がですね...

プライベート TLD (Top Level Domain)

.local を使ってはいけない

.local は、Multicast DNS 用に IETF によって予約されているので、プライベート TLD として使ってはいけません。
DNS リゾルバーの中には、この理由で .local を特別扱いするものもあります。実際、dig ではローカルマシンは引けるけど全インタフェースのアドレスが返る、ping では Temporary failure in name resolution エラーになる、という謎現象が発生します (しました)。

プライベート TLD に使える名前

じゃあ何を使えばいいかというと、厳密には使っていい名前は現状ありません。ただし、そういう名前を制定しようと議論はされているようです[6]
今のところ .internal[7] が有力候補で、かつ比較的安全なのではないかと勝手に判断して、自己責任で使うことにしました。この記事を読まれた方が TLD に .internal をお使いになってもし何か起きたとしても、私は責任を追うことはできないので、ご承知置きください。

脚注
  1. 2022年6月現在 ↩︎

  2. 知識が 16.04 LTS あたりで止まってました... ↩︎

  3. How to allow systemd-resolved to listen to an interface other than loopback? ↩︎

  4. 実は systemd-resolved の前は Dnsmasq がローカルリゾルバーをやっていたようです ↩︎

  5. どうもそこを狙った設定のような気もします ↩︎

  6. SAC113 SSAC Advisory on Private-Use TLDs ↩︎

  7. The .internal TLD ↩︎

Discussion