I want to query a single-label hostname (simple hostnames without any . in it, in the following example, I will use abc) over tailscale dns of singbox. But tailscale dns of sing-box will only resolve hostnames with the full tailnet-suffix.ts.net postfix. I.e. ping abc will fail, but ping abc.tailnet-suffix.ts.net will succeed.
I have been connecting to hosts only by the hostname over vanilla tailscale for a while. On both windows and linux (specifically, systemd-resolved described the mechinism to turn single label names to fully qualified domain names in systemd-resolved and VPNs), the default dns resolution mechinism will append the search domain while resolving single label names. Below is the output of resolvectl query --interface tailscale0 abc on a machine running tailscale daemon.
abc: 100.106.51.119 -- link: tailscale0
(abc.tailnet-suffix.ts.net)
-- Information acquired via protocol DNS in 3.7ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network
This also works for on windows machine. Resolve-DnsName -Name def -Server 100.100.100.100 outputs
Name Type TTL Section NameHost
---- ---- --- ------- --------
def CNAME 60480 Answer def.tailnet-suffix.ts.net.
0
Name : def.tailnet-suffix.ts.net.
QueryType : A
TTL : 604800
Section : Answer
IP4Address : 100.104.216.55
We can see from resolvectl status tailscale0 that tailscale set the search domain to tailnet-suffix.ts.net, thus ping abc just works.
Link 5 (tailscale0)
Current Scopes: DNS
Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 100.100.100.100
DNS Servers: 100.100.100.100
DNS Domain: tailnet-suffix.ts.net ~0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa
~100.100.in-addr.arpa ~101.100.in-addr.arpa ~102.100.in-addr.arpa
~103.100.in-addr.arpa ~104.100.in-addr.arpa ~105.100.in-addr.arpa
~106.100.in-addr.arpa ~107.100.in-addr.arpa ~108.100.in-addr.arpa
~109.100.in-addr.arpa ~110.100.in-addr.arpa ~111.100.in-addr.arpa
~112.100.in-addr.arpa ~113.100.in-addr.arpa ~114.100.in-addr.arpa
~115.100.in-addr.arpa ~116.100.in-addr.arpa ~117.100.in-addr.arpa
~118.100.in-addr.arpa ~119.100.in-addr.arpa ~120.100.in-addr.arpa
~121.100.in-addr.arpa ~122.100.in-addr.arpa ~123.100.in-addr.arpa
~124.100.in-addr.arpa ~125.100.in-addr.arpa ~126.100.in-addr.arpa
~127.100.in-addr.arpa ~64.100.in-addr.arpa ~65.100.in-addr.arpa
~66.100.in-addr.arpa ~67.100.in-addr.arpa ~68.100.in-addr.arpa
~69.100.in-addr.arpa ~70.100.in-addr.arpa ~71.100.in-addr.arpa
~72.100.in-addr.arpa ~73.100.in-addr.arpa ~74.100.in-addr.arpa
~75.100.in-addr.arpa ~76.100.in-addr.arpa ~77.100.in-addr.arpa
~78.100.in-addr.arpa ~79.100.in-addr.arpa ~80.100.in-addr.arpa
~81.100.in-addr.arpa ~82.100.in-addr.arpa ~83.100.in-addr.arpa
~84.100.in-addr.arpa ~85.100.in-addr.arpa ~86.100.in-addr.arpa
~87.100.in-addr.arpa ~88.100.in-addr.arpa ~89.100.in-addr.arpa
~90.100.in-addr.arpa ~91.100.in-addr.arpa ~92.100.in-addr.arpa
~93.100.in-addr.arpa ~94.100.in-addr.arpa ~95.100.in-addr.arpa
~96.100.in-addr.arpa ~97.100.in-addr.arpa ~98.100.in-addr.arpa
~99.100.in-addr.arpa ~ts.net
Default Route: no
But this is not the case for sing-box tailscale dns. I have abc: 'abc' not found while running resolvectl query --interface singtun abc. If I append the domain suffix to the hostname resolvectl query --interface singtun abc.tailnet-suffix.ts.net, then everything works.
abc.tailnet-suffix.ts.net: 100.106.51.119 -- link: singtun
-- Information acquired via protocol DNS in 2.8ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network
I searched the code base and found out the relevant code to handle dns query over tailscale here
|
for domainSuffix, transports := range t.routes { |
|
if strings.HasSuffix(question.Name, domainSuffix) { |
|
if len(transports) == 0 { |
|
return &mDNS.Msg{ |
|
MsgHdr: mDNS.MsgHdr{ |
|
Id: message.Id, |
|
Rcode: mDNS.RcodeNameError, |
|
Response: true, |
|
}, |
|
Question: []mDNS.Question{question}, |
|
}, nil |
|
} |
|
var lastErr error |
|
for _, dnsTransport := range transports { |
|
response, err := dnsTransport.Exchange(ctx, message) |
|
if err != nil { |
|
lastErr = err |
|
continue |
|
} |
|
return response, nil |
|
} |
|
return nil, lastErr |
|
} |
.
Why do you think appending the search domain obtained from tailscale control plane while resolving single label names in the code above? This would match the behavior of tailscale daemon's current behavior and make tailscale dns much more usable.
Here is my sing-box configuration.
{
"dns": {
"client_subnet": "223.5.5.0/24",
"final": "foreign",
"independent_cache": true,
"reverse_mapping": true,
"rules": [
{
"domain_regex": "(.*ts.net$|^[^.]+$)",
"ip_cidr": "100.64.0.0/10",
"server": "ts-dns"
},
{
"clash_mode": "direct",
"server": "domestic"
},
{
"clash_mode": "global",
"server": "fakeip"
},
{
"rule_set": "geosite-cn",
"server": "domestic"
},
{
"action": "reject",
"query_type": "HTTPS"
},
{
"query_type": [
"A",
"AAAA"
],
"rewrite_ttl": 1,
"server": "fakeip"
}
],
"servers": [
{
"accept_default_resolvers": false,
"endpoint": "ts-endpoint",
"tag": "ts-dns",
"type": "tailscale"
},
{
"server": "223.6.6.6",
"tag": "domestic",
"type": "https"
},
{
"detour": "auto",
"server": "8.8.8.8",
"tag": "foreign",
"type": "https"
},
{
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18",
"tag": "fakeip",
"type": "fakeip"
}
],
"strategy": "ipv4_only"
},
"endpoints": [
{
"accept_routes": true,
"ephemeral": false,
"exit_node_allow_lan_access": true,
"hostname": "test",
"tag": "ts-endpoint",
"type": "tailscale"
}
],
"experimental": {
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "ui",
"external_ui_download_detour": "auto"
}
},
"inbounds": [
{
"listen": "::",
"listen_port": 7890,
"set_system_proxy": false,
"type": "mixed"
},
{
"address": [
"172.19.0.1/30",
"fdfe:dcba:9876::1/126"
],
"auto_redirect": true,
"auto_route": true,
"exclude_interface": [],
"interface_name": "singtun",
"mtu": 9000,
"platform": {
"http_proxy": {
"enabled": true,
"server": "127.0.0.1",
"server_port": 7890
}
},
"route_address": [
"0.0.0.0/1",
"128.0.0.0/1",
"::/1",
"8000::/1"
],
"route_exclude_address": [],
"stack": "mixed",
"tag": "tun-in",
"type": "tun"
}
],
"log": {
"level": "debug"
},
"outbounds": [
{
"tag": "direct",
"type": "direct"
},
{
"method": "chacha20-ietf-poly1305",
"password": "password",
"server": "ssserver.example.com",
"server_port": 443,
"tag": "shadowsocks_ssserver",
"type": "shadowsocks"
},
{
"outbounds": [
"shadowsocks_ssserver"
],
"tag": "auto",
"type": "urltest"
},
{
"idle_timeout": "2d",
"interval": "1d",
"outbounds": [
"direct",
"shadowsocks_ssserver"
],
"tag": "bootstrap-auto",
"type": "urltest",
"url": "https://gh-proxy.com/github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo-lite/geoip/apple.srs"
},
{
"default": "shadowsocks_ssserver",
"outbounds": [
"shadowsocks_ssserver",
"auto"
],
"tag": "proxy",
"type": "selector"
},
{
"default": "proxy",
"outbounds": [
"proxy",
"auto",
"direct",
"shadowsocks_ssserver"
],
"tag": "final",
"type": "selector"
}
],
"route": {
"auto_detect_interface": true,
"default_domain_resolver": {
"server": "domestic"
},
"final": "final",
"rule_set": [
{
"download_detour": "bootstrap-auto",
"format": "binary",
"tag": "geosite-ads",
"type": "remote",
"url": "https://gh-proxy.com/github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/category-ads.srs"
},
{
"download_detour": "bootstrap-auto",
"format": "binary",
"tag": "geoip-cn",
"type": "remote",
"url": "https://gh-proxy.com/github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/cn.srs"
},
{
"download_detour": "bootstrap-auto",
"format": "binary",
"tag": "geosite-cn",
"type": "remote",
"url": "https://gh-proxy.com/github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/cn.srs"
}
],
"rules": [
{
"action": "sniff",
"sniffer": [
"http",
"tls",
"quic",
"dns"
]
},
{
"action": "hijack-dns",
"protocol": "dns"
},
{
"action": "hijack-dns",
"port": 53
},
{
"ip_cidr": "100.64.0.0/10",
"outbound": "ts-endpoint"
},
{
"ip_is_private": true,
"outbound": "direct"
},
{
"outbound": "direct",
"rule_set": "geoip-cn"
},
{
"outbound": "direct",
"rule_set": "geosite-cn"
},
{
"action": "reject",
"rule_set": "geosite-ads"
},
{
"action": "resolve"
}
]
}
}
I want to query a single-label hostname (simple hostnames without any
.in it, in the following example, I will useabc) over tailscale dns of singbox. But tailscale dns of sing-box will only resolve hostnames with the fulltailnet-suffix.ts.netpostfix. I.e.ping abcwill fail, butping abc.tailnet-suffix.ts.netwill succeed.I have been connecting to hosts only by the hostname over vanilla tailscale for a while. On both windows and linux (specifically, systemd-resolved described the mechinism to turn single label names to fully qualified domain names in systemd-resolved and VPNs), the default dns resolution mechinism will append the search domain while resolving single label names. Below is the output of
resolvectl query --interface tailscale0 abcon a machine running tailscale daemon.This also works for on windows machine.
Resolve-DnsName -Name def -Server 100.100.100.100outputsWe can see from
resolvectl status tailscale0that tailscale set the search domain totailnet-suffix.ts.net, thusping abcjust works.But this is not the case for sing-box tailscale dns. I have
abc: 'abc' not foundwhile runningresolvectl query --interface singtun abc. If I append the domain suffix to the hostnameresolvectl query --interface singtun abc.tailnet-suffix.ts.net, then everything works.I searched the code base and found out the relevant code to handle dns query over tailscale here
sing-box/protocol/tailscale/dns_transport.go
Lines 241 to 263 in 00ec311
Why do you think appending the search domain obtained from tailscale control plane while resolving single label names in the code above? This would match the behavior of tailscale daemon's current behavior and make tailscale dns much more usable.
Here is my sing-box configuration.
{ "dns": { "client_subnet": "223.5.5.0/24", "final": "foreign", "independent_cache": true, "reverse_mapping": true, "rules": [ { "domain_regex": "(.*ts.net$|^[^.]+$)", "ip_cidr": "100.64.0.0/10", "server": "ts-dns" }, { "clash_mode": "direct", "server": "domestic" }, { "clash_mode": "global", "server": "fakeip" }, { "rule_set": "geosite-cn", "server": "domestic" }, { "action": "reject", "query_type": "HTTPS" }, { "query_type": [ "A", "AAAA" ], "rewrite_ttl": 1, "server": "fakeip" } ], "servers": [ { "accept_default_resolvers": false, "endpoint": "ts-endpoint", "tag": "ts-dns", "type": "tailscale" }, { "server": "223.6.6.6", "tag": "domestic", "type": "https" }, { "detour": "auto", "server": "8.8.8.8", "tag": "foreign", "type": "https" }, { "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18", "tag": "fakeip", "type": "fakeip" } ], "strategy": "ipv4_only" }, "endpoints": [ { "accept_routes": true, "ephemeral": false, "exit_node_allow_lan_access": true, "hostname": "test", "tag": "ts-endpoint", "type": "tailscale" } ], "experimental": { "clash_api": { "external_controller": "127.0.0.1:9090", "external_ui": "ui", "external_ui_download_detour": "auto" } }, "inbounds": [ { "listen": "::", "listen_port": 7890, "set_system_proxy": false, "type": "mixed" }, { "address": [ "172.19.0.1/30", "fdfe:dcba:9876::1/126" ], "auto_redirect": true, "auto_route": true, "exclude_interface": [], "interface_name": "singtun", "mtu": 9000, "platform": { "http_proxy": { "enabled": true, "server": "127.0.0.1", "server_port": 7890 } }, "route_address": [ "0.0.0.0/1", "128.0.0.0/1", "::/1", "8000::/1" ], "route_exclude_address": [], "stack": "mixed", "tag": "tun-in", "type": "tun" } ], "log": { "level": "debug" }, "outbounds": [ { "tag": "direct", "type": "direct" }, { "method": "chacha20-ietf-poly1305", "password": "password", "server": "ssserver.example.com", "server_port": 443, "tag": "shadowsocks_ssserver", "type": "shadowsocks" }, { "outbounds": [ "shadowsocks_ssserver" ], "tag": "auto", "type": "urltest" }, { "idle_timeout": "2d", "interval": "1d", "outbounds": [ "direct", "shadowsocks_ssserver" ], "tag": "bootstrap-auto", "type": "urltest", "url": "https://gh-proxy.com/github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo-lite/geoip/apple.srs" }, { "default": "shadowsocks_ssserver", "outbounds": [ "shadowsocks_ssserver", "auto" ], "tag": "proxy", "type": "selector" }, { "default": "proxy", "outbounds": [ "proxy", "auto", "direct", "shadowsocks_ssserver" ], "tag": "final", "type": "selector" } ], "route": { "auto_detect_interface": true, "default_domain_resolver": { "server": "domestic" }, "final": "final", "rule_set": [ { "download_detour": "bootstrap-auto", "format": "binary", "tag": "geosite-ads", "type": "remote", "url": "https://gh-proxy.com/github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/category-ads.srs" }, { "download_detour": "bootstrap-auto", "format": "binary", "tag": "geoip-cn", "type": "remote", "url": "https://gh-proxy.com/github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/cn.srs" }, { "download_detour": "bootstrap-auto", "format": "binary", "tag": "geosite-cn", "type": "remote", "url": "https://gh-proxy.com/github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/cn.srs" } ], "rules": [ { "action": "sniff", "sniffer": [ "http", "tls", "quic", "dns" ] }, { "action": "hijack-dns", "protocol": "dns" }, { "action": "hijack-dns", "port": 53 }, { "ip_cidr": "100.64.0.0/10", "outbound": "ts-endpoint" }, { "ip_is_private": true, "outbound": "direct" }, { "outbound": "direct", "rule_set": "geoip-cn" }, { "outbound": "direct", "rule_set": "geosite-cn" }, { "action": "reject", "rule_set": "geosite-ads" }, { "action": "resolve" } ] } }