一、获取域名 IP 地址
要获取某个域名对应的 IP 地址,可以使用 net 包中的 LookupIP 函数:
func LookupIP(host string) ([]IP, error)
它接受一个域名参数,返回一个 IPAddr 切片和错误。IPAddr 结构体中包含了 IP 地址,可以处理 IPv4 和 IPv6 两种情况:
type IPAddr { IP IP} type IP []byte
使用示例:
package main import ( "fmt" "net") func main() { ips, err := net.LookupIP("www.baidu.com") if err != nil { fmt.Println(err) return } for _, ip := range ips { fmt.Println(ip) }} 输出:120.232.145.1852409:8c54:870:67:0:ff:b0c2:ad75
上例中打印出了 www.baidu.com 目前解析到的两个 IP 地址,一个 IPv4 地址,一个 IPv6 地址。
二、获取 IP 地址信息
对应地,若已经有一个 IP 地址,想要知道它的域名是什么,就可以使用反向查询函数 net.LookupAddr:
func LookupAddr(addr string) (names []string, err error)
它会将 IP 地址解析为一个或多个域名,返回字符串 slice。例如:
package main import ( "fmt" "net") func main() { names, _ := net.LookupAddr("8.8.8.8") fmt.Println(names)} 输出:[dns.google.]
三、探测域名是否可用
要检查某个域名是否已经被注册,或者是否可供注册,可以使用 net.LookupNS 函数探测域名是否可用:
func LookupNS(host string) (names []*NS, err error)
它会查找指定主机/域名的名称服务器(Name Server),返回 NS 记录 slice。
如果域名没有被注册,它会返回错误。如果域名可以使用,它会返回域名正在使用的名称服务器记录。这可以用来判断域名是否可用:
package main import ( "fmt" "net") func main() { ns, err := net.LookupNS("example.com") if err != nil { fmt.Println("域名不可用") } else { fmt.Println("域名可用") fmt.Println(ns) }} 输出:域名不可用
上例中,example.com 是一个虚构的域名,结果显示此域名未被注册,不可用。
四、获取域名的 MX 记录
MX 记录指定了电子邮件服务器的信息。要获取一个域名的 MX 记录,可以使用 net.LookupMX 函数:
func LookupMX(name string) ([]*MX, error)
它返回一个 MX 记录的 slice,每个 MX 记录包含 mail exchange 域名与优先级:
type MX struct { Host string Pref uint16}
使用示例:
package main import ( "fmt" "net") func main() { mxs, err := net.LookupMX("baidu.com") if err != nil { fmt.Println(err) return } for _, mx := range mxs { fmt.Printf("Host: %s, Pref: %d \n", mx.Host, mx.Pref) }} 输出:Host: mx.maillb.baidu.com., Pref: 10Host: mx.n.shifen.com., Pref: 15 Host: usmx01.baidu.com., Pref: 20 Host: mx50.baidu.com., Pref: 20 Host: mx1.baidu.com., Pref: 20 Host: jpmx.baidu.com., Pref: 20
结果显示了 baidu.com 使用的 5 个邮件服务器域名及其优先级。
五、实现 DNS 查询
Go 标准库 net 包中提供了 LookupHost 函数可以通过域名查询 IP:
func LookupHost(hostname string) (addrs []string, err error)
它接受主机域名作为参数,返回其 IP 地址切片。
package main import ( "fmt" "net") func main() { ips, err := net.LookupHost("www.example.com") if err != nil { fmt.Println(err) return } for _, ip := range ips { fmt.Println(ip) }}
如果想要自定义更灵活的 DNS 查询,可直接使用 UDP 套接字,按照 DNS 协议组织数据包,和 DNS 服务器进行通信。
下面是一个简单的 DNS 客户端查询 A 记录的实现:
package main import ( "bytes" "encoding/binary" "fmt" "net") func main() { // 连接DNS服务器 conn, err := net.Dial("udp", "8.8.8.8:53") if err != nil { fmt.Println(err) return } defer conn.Close() // 构造DNS查询数据包 buf := new(bytes.Buffer) // Transaction ID var tranID uint16 = 0xABCD binary.Write(buf, binary.BigEndian, tranID) flags := uint16(0x0100) // Standard query binary.Write(buf, binary.BigEndian, flags) var qds uint16 = 1 // Query Count binary.Write(buf, binary.BigEndian, qds) // Only one question var ans uint16 = 0 binary.Write(buf, binary.BigEndian, ans) var auth uint16 = 0 binary.Write(buf, binary.BigEndian, auth) var add uint16 = 0 binary.Write(buf, binary.BigEndian, add) // 数据段开始// QNAMEbuf.WriteString("www.example.com") buf.WriteByte(0) // QTYPE 和 QCLASSbinary.Write(buf, binary.BigEndian, uint16(1))binary.Write(buf, binary.BigEndian, uint16(1)) // 发送DNS查询请求conn.Write(buf.Bytes()) // 读取响应 reply := make([]byte, 512)_, err = conn.Read(reply)if err != nil { fmt.Println("No reply") return} // 解析响应// 校验事务ID匹配tid := binary.BigEndian.Uint16(reply[0:2]) if tid != tranID { fmt.Println("Wrong transaction ID") return } // Response Coderescode := binary.BigEndian.Uint16(reply[3:5])if rescode != 0 { fmt.Println("Server returned error") return} // 查询数应为1qdcount := binary.BigEndian.Uint16(reply[4:6]) if qdcount != 1 { fmt.Println("Invalid query count") return} // 回答Resource记录数 >= 1ancount := binary.BigEndian.Uint16(reply[6:8])if ancount < 1 { fmt.Println("No answer") return} // 跳过问题portionstart := 12 for { if reply[start] == 0 { break } start++}start += 5 // 解析回答for i := 0; i < int(ancount); i++ { // 跳过NAME nameLen := int(reply[start]) start += 1 + nameLen typ := binary.BigEndian.Uint16(reply[start : start+2]) start += 2 class := binary.BigEndian.Uint16(reply[start : start+2]) start += 2 ttl := binary.BigEndian.Uint32(reply[start : start+4]) start += 4 rdlen := binary.BigEndian.Uint16(reply[start : start+2]) start += 2 // A记录为IP if typ == 1 { ip := net.IP(reply[start : start+rdlen]).String() fmt.Println(ip) } start += int(rdlen)} }
上例实现了完整的 DNS 查询 client,自定义构造请求数据包,解析返回结果,打印查询 IP。使用 UDP socket 和 DNS 服务器通信。
这只是最基础的查询 A 记录的示例,可以扩展实现其他类型的 Resource Record 查询。
六、更多 DNS 记录查询
除了 A 记录(地址)查询,Go 标准库还提供了查询其他 DNS 记录的函数:
查询 TXT 记录
func LookupTXT(host string) ([]string, error)
查询 SRV 记录
func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error)
使用示例:
package main import ( "fmt" "net") func main() { texts, _ := net.LookupTXT("baidu.com") for _, text := range texts { fmt.Println(text) } _, srvs, _ := net.LookupSRV("xmpp-server", "tcp", "baidu.com") for _, srv := range srvs { fmt.Println(srv.Target) }}
七、域名/IP 相关实用包
除了标准库,Go 社区还提供了很多域名和 IP 地址操作相关的实用包,可以大大简化相关开发:
godns
godns 提供了便捷的域名查询和更新 API,支持不同类型的记录查询:
import "github.com/timshannon/godns" A, _ := godns.Lookup("baidu.com", "A")MXs, _ := godns.Lookup("baidu.com", "MX"))
hosts
hosts 包实现了 hosts 文件(像 Linux 中的/etc/hosts)相关操作:
import "github.com/jaytaylor/go-hostsfile" hosts, _ := hostsfile.NewHostsfile()ip := hosts.IP("localserver")hosts.Add("192.168.1.5", "test")
以上两个库可以大大简化域名/IP 相关操作。
八、模拟 NSLOOKUP
可编写一个类似 NSLOOKUP 命令行工具的程序,输入域名和 DNS 服务器地址,显示域名 lookup 的结果:
package main import ( "flag" "fmt" "net" "os") func main() { domain := flag.String("d", "baidu.com", "Domain to lookup") server := flag.String("s", "8.8.8.8:53", "DNS server") flag.Parse() // UDP连接DNS服务器 conn, err := net.Dial("udp", *server) if err != nil { fmt.Println(err) os.Exit(1) } // 模拟A记录查询 ips, err := lookupA(conn, *domain) if err != nil { fmt.Println(err) } else { fmt.Printf("Domain: %s\n", *domain) fmt.Println(ips) } conn.Close() } // 模拟A记录查询func lookupA(conn net.Conn, domain string) ([]string, error) { // 构造DNS查询请求数据包 buf := new(bytes.Buffer) // ..... 省略请求数据包组装过程 conn.Write(buf.Bytes()) // 解析返回数据包 reply := make([]byte, 512) n, err := conn.Read(reply) // ...... 省略解析过程获取IP var ips []string ips = append(ips, ip.String()) return ips, nil}
通过命令行参数指定域名和服务器地址模拟 NSLOOKUP 查询 IP 的功能。可以封装更多其他类型的查询。