#16 hosts 文件与 systemd 的域名解析
DNS 2026-06-27案例复盘:systemd-resolved 合成记录导致 AAAA 解析“幽灵失败”
coding in a complicated world
案例复盘:systemd-resolved 合成记录导致 AAAA 解析“幽灵失败”
$ go install github.com/mr-karan/doggo/cmd/doggo@latest
$ doggo
NAME:
doggo 🐶 DNS Client for Humans
USAGE:
doggo [--] [query options] [arguments...]
VERSION:
unknown - unknown
EXAMPLES:
doggo mrkaran.dev Query a domain using defaults.
doggo mrkaran.dev CNAME Query for a CNAME record.
doggo mrkaran.dev MX @9.9.9.9 Uses a custom DNS resolver.
doggo -q mrkaran.dev -t MX -n 1.1.1.1 Using named arguments.
doggo mrkaran.dev --aa --ad Query with Authoritative Answer and Authenticated Data flags set.
doggo mrkaran.dev --cd --do Query with Checking Disabled and DNSSEC OK flags set.
doggo mrkaran.dev --gp-from Germany Query using Globalping API from a specific location.
FREE FORM ARGUMENTS:
Supply hostnames, query types, and classes without flags. Example:
doggo mrkaran.dev A @1.1.1.1
TRANSPORT OPTIONS:
Specify the protocol with a URL-type scheme.
UDP is used if no scheme is specified.
@udp:// eg: @1.1.1.1 initiates a UDP query to 1.1.1.1:53.
@tcp:// eg: @tcp://1.1.1.1 initiates a TCP query to 1.1.1.1:53.
@https:// eg: @https://cloudflare-dns.com/dns-query initiates a DOH query to Cloudflare via DoH.
@tls:// eg: @tls://1.1.1.1 initiates a DoT query to 1.1.1.1:853.
@sdns:// initiates a DNSCrypt or DoH query using a DNS stamp.
@quic:// initiates a DOQ query.
SUBCOMMANDS:
completions [bash|zsh|fish] Generate the shell completion script for the specified shell.
QUERY OPTIONS:
-q, --query=HOSTNAME Hostname to query the DNS records for (eg mrkaran.dev).
-t, --type=TYPE Type of the DNS Record (A, MX, NS etc).
-n, --nameserver=ADDR Address of a specific nameserver to send queries to (9.9.9.9, 8.8.8.8 etc).
-c, --class=CLASS Network class of the DNS record (IN, CH, HS etc).
-x, --reverse Performs a DNS Lookup for an IPv4 or IPv6 address. Sets the query type and class to PTR and IN respectively.
--any Query all supported DNS record types (A, AAAA, CNAME, MX, NS, PTR, SOA, SRV, TXT, CAA).
RESOLVER OPTIONS:
--strategy=STRATEGY Specify strategy to query nameserver listed in etc/resolv.conf. (all, random, first).
--ndots=INT Specify ndots parameter. Takes value from /etc/resolv.conf if using the system namesever or 1 otherwise.
--search Use the search list defined in resolv.conf. Defaults to true. Set --search=false to disable search list.
--timeout=DURATION Specify timeout for the resolver to return a response (e.g., 5s, 400ms, 1m).
-4, --ipv4 Use IPv4 only.
-6, --ipv6 Use IPv6 only.
--tls-hostname=HOSTNAME Provide a hostname for verification of the certificate if the provided DoT nameserver is an IP.
--skip-hostname-verification Skip TLS Hostname Verification in case of DOT Lookups.
QUERY FLAGS:
--aa Set Authoritative Answer flag.
--ad Set Authenticated Data flag.
--cd Set Checking Disabled flag.
--rd Set Recursion Desired flag (default: true).
--z Set Z flag (reserved for future use).
--do Set DNSSEC OK flag.
OUTPUT OPTIONS:
-J, --json Format the output as JSON.
--short Short output format. Shows only the response section.
--color Defaults to true. Set --color=false to disable colored output.
--debug Enable debug logging.
--time Shows how long the response took from the server.
GLOBALPING OPTIONS:
--gp-from=Germany Query using Globalping API from a specific location.
--gp-limit=INT Limit the number of probes to use from Globalping.
$ doggo sendcloud.net a @223.5.5.5
NAME TYPE CLASS TTL ADDRESS NAMESERVER
sendcloud.net. A IN 60s 106.75.106.173 223.5.5.5:53
sendcloud.net. A IN 60s 106.75.106.166 223.5.5.5:53
$ doggo sendcloud.net a @223.5.5.5 --json
{
"responses": [
{
"answers": [
{
"name": "sendcloud.net.",
"type": "A",
"class": "IN",
"ttl": "60s",
"address": "106.75.106.173",
"status": "",
"rtt": "67ms",
"nameserver": "223.5.5.5:53"
},
{
"name": "sendcloud.net.",
"type": "A",
"class": "IN",
"ttl": "60s",
"address": "106.75.106.166",
"status": "",
"rtt": "67ms",
"nameserver": "223.5.5.5:53"
}
],
"authorities": null,
"questions": [
{
"name": "sendcloud.net.",
"type": "A",
"class": "IN"
}
]
}
]
}
$ doggo sendcloud.net a @223.5.5.5 --json | jq ".responses[].answers[].address"
"106.75.106.166"
"106.75.106.173"
$ doggo --reverse 101.44.172.1 @223.5.5.5
NAME TYPE CLASS TTL ADDRESS NAMESERVER
1.172.44.101.in-addr.arpa. PTR IN 300s hwsg1c1.email.engagelab.com. 223.5.5.5:53
$ doggo hwsg1c1.email.engagelab.com. a @223.5.5.5
NAME TYPE CLASS TTL ADDRESS NAMESERVER
hwsg1c1.email.engagelab.com. A IN 600s 101.44.172.1 223.5.5.5:53
$ doggo markjour.com --gp-from Germany,Japan --gp-limit 2
LOCATION NAME TYPE CLASS TTL ADDRESS NAMESERVER
Falkenstein, DE, EU, Hetzner
Online GmbH (AS24940)
markjour.com. A IN 600s 121.42.82.115 private
Osaka, JP, AS, Oracle
Corporation (AS31898)
markjour.com. A IN 600s 121.42.82.115 8.8.8.8
天涯社区将回归!创始人:明天,全面恢复业务!此前被申请破产审查,官网仍“无法访问”
说是 5/1 前回归,我刚查了一下,天涯现在还打不开。搜索一下域名解析的时候,发现了一个 HINFO 记录,这我还是第一次看到。
package main
import (
"fmt"
"net"
)
func main() {
// 方法 1
// func ResolveIPAddr(network, address string) (*IPAddr, error)
ipAddr, err := net.ResolveIPAddr("ip", "www.google.com")
if err != nil {
fmt.Println("解析IP地址失败:", err)
} else {
fmt.Println("IP地址是:", ipAddr.IP)
}
// 方法 2
// func LookupMX(name string) ([]*MX, error)
// func LookupTXT(name string) ([]string, error)
// func LookupIP(host string) ([]IP, error)
// func LookupHost(host string) ([]string, error) // only IPv4
ips, err := net.LookupHost("www.google.com")
if err != nil {
fmt.Println("解析主机名失败:", err)
} else {
for _, ip := range ips {
fmt.Println("IP地址是:", ip)
}
}
}
package main
import (
"fmt"
"net"
)
func main() {
records, err := net.LookupMX("qq.com")
if err != nil {
fmt.Println("解析MX记录失败:", err)
} else {
fmt.Printf("%#v", records)
}
}
func queryMX(dns string, domain string) ([]*net.MX, error) {
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
dialer := &net.Dialer{}
return dialer.DialContext(ctx, "udp", dns)
},
}
return resolver.LookupMX(context.Background(), domain)
}
package main
import (
"context"
"fmt"
"net"
"strings"
"time"
)
var dnsServers = []string{
"223.5.5.5:53",
"114.114.114.114:53",
"8.8.8.8:53",
}
type IPType int
const (
NotIP IPType = iota
IPv4
IPv6
)
const (
DNSLookupTimeout = 500 * time.Millisecond // time.Duration
)
func GetIPType(host string) IPType {
ip := net.ParseIP(host)
if ip == nil {
return NotIP
}
if ip.To4() != nil {
return IPv4
}
if ip.To16() != nil {
return IPv6
}
return NotIP
}
type Resolver struct {
DNS string
Resolver *net.Resolver
}
func NewResolver(dns string) Resolver {
resolver := Resolver{}
resolver.DNS = dns
resolver.Resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
dialer := &net.Dialer{}
return dialer.DialContext(ctx, "udp", dns)
},
}
return resolver
}
func (r Resolver) QueryA(domain string) ([]string, error) {
return r.Resolver.LookupHost(context.Background(), domain)
}
func (r Resolver) QueryMX(domain string) ([]*net.MX, error) {
ctx, cancel := context.WithTimeout(context.Background(), DNSLookupTimeout)
defer cancel()
return r.Resolver.LookupMX(ctx, domain)
}
func (r Resolver) Query(domain string) ([]*net.MX, error) {
addrs, err := r.QueryMX(domain)
if err != nil {
return nil, err
}
var result []*net.MX
for _, addr := range addrs {
// hostname := strings.TrimRight(addr.Host, ".")
ipType := GetIPType(addr.Host)
fmt.Printf("%s: %#v\n", addr.Host, ipType)
if ipType == NotIP {
ips, err := r.QueryA(addr.Host)
if err != nil {
continue
}
for _, ip := range ips {
result = append(result, &net.MX{Host: ip, Pref: addr.Pref})
}
}
}
return result, nil
}
var resolvers map[string]Resolver
func Query(domain string) ([]*net.MX, error) {
var addrs []*net.MX
var err error
for _, dns := range dnsServers {
var r Resolver
if r, ok := resolvers[dns]; !ok {
r = NewResolver(dns)
resolvers[dns] = r
}
addrs, err = r.Query(domain)
if err == nil {
break
}
fmt.Printf("Error: %s: %s: %#v\n", dns, domain, err)
}
return addrs, err
}
func QueryAsync(domain string, ch chan<- []*net.MX, errCh chan<- error) {
addrs, err := Query(domain)
if err != nil {
errCh <- err
return
}
ch <- addrs
}
func init() {
resolvers = make(map[string]Resolver)
}
func main() {
{
domains := []string{"qq.com", "gmail.com", "google.com"}
for _, domain := range domains {
fmt.Println(strings.Repeat("=", 100))
addrs, err := Query(domain)
if err != nil {
fmt.Printf("Error: %#v\n", err)
} else {
for _, addr := range addrs {
fmt.Printf("%#v\n", addr)
}
}
}
}
{
fmt.Println(strings.Repeat("=", 100))
ch := make(chan []*net.MX)
errCh := make(chan error)
go QueryAsync("google.com", ch, errCh)
select {
case addrs := <-ch:
fmt.Println("MX Records:")
for _, addr := range addrs {
fmt.Printf("%#v\n", addr)
}
case err := <-errCh:
fmt.Println("Error:", err)
}
}
}
DNS_PROBE_FINISHED_NXDOMAIN。172.16.0.1。223.5.5.5, 223.6.6.6 之后就好了。我应该在出现问题的时候先尝试 nslookup 一下,看看 DNS 解析出来的到底是个什么结果。
下次遇到再继续更新。
C:\Users\Administrator>ipconfig /all | findstr DNS
主 DNS 后缀 . . . . . . . . . . . :
连接特定的 DNS 后缀 . . . . . . . :
DNS 服务器 . . . . . . . . . . . : 172.16.0.1
连接特定的 DNS 后缀 . . . . . . . :
DNS 服务器 . . . . . . . . . . . : fec0:0:0:ffff::1%1
连接特定的 DNS 后缀 . . . . . . . :
连接特定的 DNS 后缀 . . . . . . . :
连接特定的 DNS 后缀 . . . . . . . :
连接特定的 DNS 后缀 . . . . . . . :
C:\Users\Administrator>nslookup zhihu.com
服务器: UnKnown
Address: 172.16.0.1
非权威应答:
名称: zhihu.com
Address: 103.41.167.234
rdataclassRESERVED0 = 0
IN = 1
INTERNET = 1
CH = 3
CHAOS = 3
HESIOD = 4
HS = 4
NONE = 254
ANY = 255
rdatatypeA IPv4 AddressAAAA IPv4 AddressCNAME Canonical Name,“规范名称”,其实就是别名,指向另一个域名。MX 记录和 NS 记录不能指向一个 CNAME 记录。NS Name Server,权威 DNS 服务器位置。MX Mail Exchange,邮件服务位置。SRV 通用的服务类型记录,取代 MX 这种专用记录。_xmpp-client._tcp.example.net. 86400 IN SRV 5 0 5222 example.net.
_xmpp-server._tcp.example.net. 86400 IN SRV 5 0 5269 example.net.
# TTL Priority Weight Port
TXTSOA Start Of Authority,权威记录起始PTR 反解记录记录名称:IP 翻转过来,加上 .in-addr.arpa 后缀
记录类型:PTR
dig -x 106.75.80.125
dig +noall +answer -tPTR 125.80.75.106.in-addr.arpa
nslookup
ipconfig /displaydns
ipconfig /flushdns
dig markjour.com A
dig @8.8.8.8 markjour.com
dig qq.com MX
dig MX qq.com
dig -t MX qq.com
dig +noall +answer qq.com
dig +short qq.com
dig qq.com ANY
dig +trace +nssearch markjour.com
| Field | Bits | Desc |
|---|---|---|
| QR | 1 | query 0, reply 1 |
| OPCODE | 4 | QUERY 0, IQUERY 1, STATUS 2 |
| AA | 1 | Authoritative Answer |
| TC | 1 | TrunCation |
| RD | 1 | Recursion Desired |
| RA | 1 | Recursion Available |
| Z | 3 | Zero 填零,保留 |
| RCODE | 4 | Response Code |
RCODE:
NXDOMAIN (3, Nonexistent domain)
body
question
| Field | Bits | Desc |
|---|---|---|
| NAME | 可变 | 资源名称 |
| TYPE | 2 | 记录类型 |
| CLASS | 2 | Class Code |
CLASS Code rdata.class:
RESERVED0 = 0
IN = 1
INTERNET = 1
CH = 3
CHAOS = 3
HESIOD = 4
HS = 4
NONE = 254
ANY = 255
answer