🐡

macOSで *.localhost を 127.0.0.1 に解決する

2021/07/03に公開

はじめに

手元の計算機で ping -c1 foobar.localhost を試してみてほしい。どこにpingが送られるだろうか?Linuxでは localhost と同様ループバックアドレス 127.0.0.1 に解決されることが多いようだ。このふるまいについて "Let 'localhost' be localhost." という標準化への草稿がある

https://twitter.com/zimbatm/status/1407694013520617473

一方、現時点ではmacOSだと Unknown host とエラーになる

$ ping -c1 foobar.localhost
ping: cannot resolve foobar.localhost: Unknown host

*.localhost がループバックアドレスに解決されるとどのような嬉しみがあるか。例えばGitHub Pagesのような、サブドメインがパラメタとして機能するWebアプリケーションの開発に便利だろう

ここではmacOSでも *.localhost がループバックアドレスに解決されるようにする一方法を解説する。この方法はまず何でもループバックアドレスに解決するDNSリゾルバをローカルで起動し、それを /etc/resolver に登録するという2つのステップからなる

単純なDNSリゾルバ

DNSリゾルバを書くのに、ここではGoと github.com/miekg/dns を使う。このDNSリゾルバがすることは10053番ポートで待ち受けて、クエリの種類が A のときにループバックアドレスを返すことだけ

package main

import (
	"net"

	"github.com/miekg/dns"
)

func main() {
	if err := dns.ListenAndServe(":10053", "udp4", dns.HandlerFunc(handle)); err != nil {
		panic(err)
	}
}

func handle(w dns.ResponseWriter, r *dns.Msg) {
	if r.Opcode != dns.OpcodeQuery {
		return
	}

	m := dns.Msg{Compress: true}
	m.SetReply(r)
	for _, q := range m.Question {
		if q.Qtype != dns.TypeA {
			continue
		}
		
		m.Answer = append(m.Answer, &dns.A{
			Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET},
			A:   net.IPv4(127, 0, 0, 1),
		})
	}
	if err := w.WriteMsg(&m); err != nil {
		panic(err)
	}
}

これを実行すると10053番ポートでDNSリゾルバが起動する。停止する際には Ctrl-C を入力する

$ go run main.go 
# Ctrl-C でDNSリゾルバを停止する
$ dig @127.0.0.1 -p10053 foobar.localhost A

; <<>> DiG 9.10.6 <<>> @127.0.0.1 -p10053 foobar.localhost A
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56078
;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;foobar.localhost.              IN      A

;; ANSWER SECTION:
foobar.localhost.       0       IN      A       127.0.0.1

;; Query time: 0 msec
;; SERVER: 127.0.0.1#10053(127.0.0.1)
;; WHEN: Fri Jul 02 10:12:58 JST 2021
;; MSG SIZE  rcvd: 50

DNSリゾルバの登録

DNSリゾルバを起動したら、以下のようなテキストファイルを /etc/resolver/localhost として保存することで登録される

nameserver 127.0.0.1
port 10053
$ cat <<EOF | sudo tee /etc/resolver/localhost
> nameserver 127.0.0.1
> port 10053
> EOF
Password: # パスワードを入力する
nameserver 127.0.0.1
port 10053

登録が完了すると、任意の *.localhost127.0.0.1 に解決されるようになる

$ ping -c1 foobar.localhost
PING foobar.localhost (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.035 ms

--- foobar.localhost ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.035/0.035/0.035/0.000 ms

Ctrl-C でDNSリゾルバを停止したら、登録も削除しておくべき

$ sudo rm /etc/resolver/localhost 
Password: # パスワードを入力する

おわりに

macOSでも *.localhost127.0.0.1 に解決されるようにする方法を解説した

もともと自分で使うために github.com/ichiban/llbl というユーティリティを書いていたが、冒頭のtweetで思い出し、他のmacOSユーザにも役立つかもと思い書いた

https://github.com/ichiban/llbl

Discussion