要实现标题所描述的目的,在 macOS 上有两种手段。
本文所述命令操作均使用
macOS Ventura 13.0
完成。
BSD style,resolver file #
从很古老的 OS X 时期起,mac 上就支持通过配置 resolver
文件的方式来设置 DNS。文件的具体格式可以通过 man 5 resolver
了解。
具体用法可以简要概述为,只需要在 /etc/resolver
文件夹下(需要自行创建这个文件夹,默认不存在)下创建一个含有 resolver 格式内容的文件,比如 /etc/resolver/example.com
,就能让 example.com
的域名解析都使用这个文件内指定的 DNS 服务器地址。不再需要的话只要删除掉对应文件即可。
以我家中网络使用的 lan
这个 TLD 为例,文件最简单的内容只需要写成这样:
domain lan
search lan
nameserver 192.168.15.1
创建或修改了这个文件后执行 scutil --dns
查看是否出现了对应的 resolver:
$ scutil --dns
DNS configuration
resolver #1
nameserver[0] : fe80::800c:67ff:fe1d:fd64%12d
nameserver[1] : 172.20.10.1
if_index : 12 (en1)
flags : Request A records, Request AAAA records
reach : 0x00020002 (Reachable,Directly Reachable Address)
resolver #2
domain : local
options : mdns
timeout : 5
flags : Request A records, Request AAAA records
reach : 0x00000000 (Not Reachable)
order : 300000
resolver #3
domain : 254.169.in-addr.arpa
options : mdns
timeout : 5
flags : Request A records, Request AAAA records
reach : 0x00000000 (Not Reachable)
order : 300200
resolver #4
domain : 8.e.f.ip6.arpa
options : mdns
timeout : 5
flags : Request A records, Request AAAA records
reach : 0x00000000 (Not Reachable)
order : 300400
resolver #5
domain : 9.e.f.ip6.arpa
options : mdns
timeout : 5
flags : Request A records, Request AAAA records
reach : 0x00000000 (Not Reachable)
order : 300600
resolver #6
domain : a.e.f.ip6.arpa
options : mdns
timeout : 5
flags : Request A records, Request AAAA records
reach : 0x00000000 (Not Reachable)
order : 300800
resolver #7
domain : b.e.f.ip6.arpa
options : mdns
timeout : 5
flags : Request A records, Request AAAA records
reach : 0x00000000 (Not Reachable)
order : 301000
resolver #8
domain : lan
search domain[0] : lan
nameserver[0] : 192.168.15.1
flags : Request A records, Request AAAA records
reach : 0x00000002 (Reachable)
DNS configuration (for scoped queries)
resolver #1
nameserver[0] : fe80::800c:67ff:fe1d:fd64%12d
nameserver[1] : 172.20.10.1
if_index : 12 (en1)
flags : Scoped, Request A records, Request AAAA records
reach : 0x00020002 (Reachable,Directly Reachable Address)
可以看见 #8
,表明配置已经生效。
这个
scutil
命令将在下一章描述。如果是 macOS 10.10.4+ 的系统,在创建或修改了这个文件后,建议给
mDNSResponder
发送HUP
信号来清除系统的 DNS 缓存。killall -HUP mDNSResponder
测试配置是否生效可以使用 dns-sd
命令:
$ dns-sd -q netbox
DATE: ---Wed 09 Nov 2022---
23:21:40.760 ...STARTING...
Timestamp A/R Flags IF Name Type Class Rdata
23:21:40.836 Add 3 0 netbox.lan. CNAME IN unicorn.lan.
23:21:40.836 Add 2 0 unicorn.lan. Addr IN 192.168.15.2
^C
也可以在 -G
后指定 v4
/ v6
来查询 A
/ AAAA
:
$ dns-sd -G v4v6 netbox
DATE: ---Wed 09 Nov 2022---
23:23:41.669 ...STARTING...
Timestamp A/R Flags IF Hostname Address TTL
23:23:41.686 Add 2 0 unicorn.lan. 192.168.15.2 15
23:23:41.700 Add 2 0 netbox. 0000:0000:0000:0000:0000:0000:0000:0000%<0> 77 No Such Record
^C
System Configuration Framework,scutil #
在 macOS 上,还有一套称为 System Configuration Framework
的高级系统框架,最早可能可以追述到 Mac OS X Leopard
时代 1。这套框架用于动态管理系统中的各个配置项,当然也包括配置系统 DNS 的能力。根据官方文档 System Configuration 页面上的描述,它基本支持了 Apple 所有的系统,如 iOS / iPadOS / macOS 等等。像 iOS 上的 APP 可以通过调用这套框架对应的系统 API SCDynamicStore 来设置 DNS,而对 macOS 上的管理员 / 用户来说,更实用的手段是通过上一章提到过的 scutil
命令。
sc
是 System Configuration 首字母的缩写。
通过查看 scutil
的 man page,可以在 --dns
这个选项下的描述中发现,系统上的 DNS 解析库依然保持着对旧有的 BSD 风格 resolver 文件的兼容和适配:
结合上一章节中的实际表现也确实如此。只是按照 Apple 的风格,突然未来的某一天系统更新就不支持这个特性了也说不准,所以提前适应新工具也是有一定必要的。下面就展示下如何使用 scutil
进行 DNS 的配置。
scutil
执行指令的方法是交互式的,执行它会进入一个类似 shell 的场景。但是你也可以像调用其他普通命令那样来调用它,比如使用管道方法传递 stdin:
$ echo help | scutil
Available commands:
help : list available commands
f.read file : process commands from file
quit : quit
d.init : initialize (empty) dictionary
d.show : show dictionary contents
d.add key [*#?%] val [v2 ...] : add information to dictionary
(*=array,#=number,?=boolean,%=hex data)
d.remove key : remove key from dictionary
list [pattern] : list keys in data store
add key ["temporary"] : add key in data store w/current dict
get key : get dict from data store w/key
set key : set key in data store w/current dict
show key ["pattern"] : show values in data store w/key
remove key : remove key from data store
notify key : notify key in data store
n.list ["pattern"] : list notification keys
n.add key ["pattern"] : add notification key
n.remove key ["pattern"] : remove notification key
n.changes : list changed keys
n.watch : watch for changes
n.cancel : cancel notification requests
或者使用 heredoc:
$ scutil << EOF
heredoc> help
heredoc> EOF
Available commands:
help : list available commands
f.read file : process commands from file
quit : quit
d.init : initialize (empty) dictionary
d.show : show dictionary contents
d.add key [*#?%] val [v2 ...] : add information to dictionary
(*=array,#=number,?=boolean,%=hex data)
d.remove key : remove key from dictionary
list [pattern] : list keys in data store
add key ["temporary"] : add key in data store w/current dict
get key : get dict from data store w/key
set key : set key in data store w/current dict
show key ["pattern"] : show values in data store w/key
remove key : remove key from data store
notify key : notify key in data store
n.list ["pattern"] : list notification keys
n.add key ["pattern"] : add notification key
n.remove key ["pattern"] : remove notification key
n.changes : list changed keys
n.watch : watch for changes
n.cancel : cancel notification requests
如果编写脚本,建议使用 help 中提到的
f.read
直接从文件中读取指令,或者使用 heredoc 的方法。这两种方法在连续执行多个指令的情况下比管道传递 stdin 能够提供更好的阅读性。管道传递 stdin 方法中多个指令之间需要包含
\n
。
具体如何配置 DNS,scutil
的文档并未做出过多解释,毕竟它也只是调用的 SystemConfiguration.framework SCDynamicStore APIs
去设置 K / V,但让人无语的是,框架的文档页只列出了配置 DNS 的 Key:DNS Entity Keys,具体这些 Key 代表什么,每个 Key 值的格式等也并未做出详细说明。
好在有 Stack Exchange
上的 mecki 的回答 2 3,给出了使用 scutil
配置的完整例子:
#!/bin/bash
sudo scutil << EOF
d.init
d.add ServerAddresses * 9.9.9.9
d.add SearchDomains * stackexchange.com
d.add SupplementalMatchDomains * stackexchange.com
set State:/Network/Service/whatever-you-want-as-long-as-unique/DNS
exit
EOF
注意 SearchDomains
这行,这个 key 在 mecki 的回答中没有提及,是我自己对照 DNS Entity Keys
后测试的,实际结果显示它没什么用。
对于 mecki 怎么知道使用什么 Key 这回事,从他能给出 Apple 这些组件源码在 GitHub 的地址 这情况看,十有八九是他看过代码。
语法中的 *
是一个特殊标识,用来表示后面值是 Array 类型。
d.add SearchDomains stackexchange.com
创建出的对象:
> get State:/Network/Service/whatever-you-want-as-long-as-unique/DNS
> d.show
<dictionary> {
SearchDomains : stackexchange.com
ServerAddresses : <array> {
0 : 9.9.9.9
}
SupplementalMatchDomains : <array> {
0 : stackexchange.com
}
}
d.add SearchDomains * stackexchange.com
创建出的对象:
> get State:/Network/Service/whatever-you-want-as-long-as-unique/DNS
> d.show
<dictionary> {
SearchDomains : <array> {
0 : stackexchange.com
}
ServerAddresses : <array> {
0 : 9.9.9.9
}
SupplementalMatchDomains : <array> {
0 : stackexchange.com
}
}
配置完后还是老样子,通过 scutil --dns
查看是否出现了对应的 resolver:
$ scutil --dns
DNS configuration
resolver #1
search domain[0] : stackexchange.com
search domain[1] : lan
nameserver[0] : 192.168.15.1
nameserver[1] : fd6f:9316:1db::1
if_index : 12 (en1)
flags : Request A records, Request AAAA records
reach : 0x00020002 (Reachable,Directly Reachable Address)
resolver #2
domain : stackexchange.com
nameserver[0] : 9.9.9.9
flags : Supplemental, Request A records, Request AAAA records
reach : 0x00000002 (Reachable)
order : 101800
...
可以看出明显的与 resolver file 配置时不一样的输出。
使用 dns-sd
测试:
$ dns-sd -q apple
DATE: ---Thu 10 Nov 2022---
00:13:03.209 ...STARTING...
Timestamp A/R Flags IF Name Type Class Rdata
00:13:03.211 Add 40000003 0 apple.stackexchange.com. Addr IN 172.64.144.30
00:13:03.211 Add 40000002 0 apple.stackexchange.com. Addr IN 104.18.43.226
^C
如果想要移除这个配置只需删除这个 Key 即可:
sudo scutil
remove State:/Network/Service/whatever-you-want-as-long-as-unique/DNS
要在
scutil
中查看还有其他哪些 DNS 的 Keys 可以使用list ".*DNS"
:$ echo 'list ".*DNS"' | scutil subKey [0] = State:/Network/Global/DNS subKey [1] = State:/Network/MulticastDNS subKey [2] = State:/Network/PrivateDNS subKey [3] = State:/Network/Service/C6BA5C2B-B3FB-4A34-99EA-EE62154A2BD6/DNS subKey [4] = State:/Network/Service/whatever-you-want-as-long-as-unique/DNS
其他使用
scutil
的例子可以参考:
Tinc integration #
如果有读者像我一样使用 tinc,可以参考一下我简单的 up / down 脚本。
resolver file #
/opt/homebrew/etc/tinc/z10n0110/tinc-up
:
#!/bin/sh
ifconfig $INTERFACE 10.0.7.3 10.0.7.9 netmask 255.255.0.0
route add -net 10.0.7.0/24 -interface $INTERFACE
route add -net 192.168.15.0/24 -interface $INTERFACE
mkdir -p /etc/resolver
cat > /etc/resolver/lan << HERE
domain lan
search lan
nameserver 192.168.15.1
HERE
cat > /etc/resolver/z10n0110.men << HERE
domain z10n0110.men
search z10n0110.men
nameserver 192.168.15.1
HERE
killall -HUP mDNSResponder
/opt/homebrew/etc/tinc/z10n0110/tinc-down
:
#!/bin/sh
route delete -net 192.168.15.0/24 -interface $INTERFACE
route delete -net 10.0.7.0/24 -interface $INTERFACE
ifconfig $INTERFACE down
if [ -f /etc/resolver/lan ]; then
rm -rf /etc/resolver/lan
fi
if [ -f /etc/resolver/z10n0110.men ]; then
rm -rf /etc/resolver/z10n0110.men
fi
killall -HUP mDNSResponder
scutil #
/opt/homebrew/etc/tinc/z10n0110/tinc-up
:
#!/bin/sh
ifconfig $INTERFACE 10.0.7.3 10.0.7.9 netmask 255.255.0.0
route add -net 10.0.7.0/24 -interface $INTERFACE
route add -net 192.168.15.0/24 -interface $INTERFACE
cat > /opt/homebrew/etc/tinc/z10n0110/up-cmds.scutil << HERE
d.init
d.add ServerAddresses * 192.168.15.1
d.add SupplementalMatchDomains * lan z10n0110.men
set State:/Network/Service/tinc-z10n0110/DNS
exit
HERE
echo 'f.read /opt/homebrew/etc/tinc/z10n0110/up-cmds.scutil' | scutil
killall -HUP mDNSResponder
/opt/homebrew/etc/tinc/z10n0110/tinc-down
:
#!/bin/sh
route delete -net 192.168.15.0/24 -interface $INTERFACE
route delete -net 10.0.7.0/24 -interface $INTERFACE
ifconfig $INTERFACE down
echo 'remove State:/Network/Service/tinc-z10n0110/DNS' | scutil
killall -HUP mDNSResponder