背景: 在实际的互联网使用过程中,大家熟知的是使用域名来直接访问一个服务,但随着互联网业务架构的不断优化,可能对用用户来说访问一个域名获取到相关的资源是很简单的一步,但其实对于互联网整个请求过程其实是做了很多次调用,那最开始的一步就是dns解析。当然在linux环境下,用来做dns解析的工具有很多,比如
dig
和nslookup
之类的,但是通常对于复杂问题的排查直接去机器上去很显然是不太现实的,因此打算使用golang的接口来封装域名解析服务,来提供后期的操作.
1. net包的使用
和dns相关结构体方法
代码语言:javascript复制# nameserver结构体
type NS struct {
Host string
}
# srv记录 指定该域名由哪个DNS服务器来进行解析
type SRV struct {
Target string
Port uint16
Priority uint16
Weight uint16
}
# dns正向解析(域名解析到cname或者实际的ip地址)
## 仅返回指定域名name的cname地址
func LookupCNAME(name string) (cname string, err error)
## 直接返回域名解析到地址.
func LookupHost(host string) (addrs []string, err error)
## 直接返回域名解析到地址,[]IP结构体.可以对具体ip进行相关操作(是否回环地址,子网,网络号等)
# type IP []byte
func LookupIP(host string) (addrs []IP, err error)
## DNS反向解析(ip地址反向解析查找解析到的域名)
# 根据ip地址查找主机名地址(必须得是可以解析到的域名)[dig -x ipaddress]
func LookupAddr(addr string) (name []string, err error)
使用net包进行dns解析查询
代码语言:javascript复制$ cat dns-test.go
package main
import (
"net"
"fmt"
"os"
)
func main() {
dns := "xxbandy.github.io"
// 解析cname
cname,_ := net.LookupCNAME(dns)
// 解析ip地址
ns, err := net.LookupHost(dns)
if err != nil {
fmt.Fprintf(os.Stderr, "Err: %s", err.Error())
return
}
// 反向解析(主机必须得能解析到地址)
dnsname,_ := net.LookupAddr("127.0.0.1")
fmt.Println("hostname:",dnsname)
// 对域名解析进行控制判断
// 有些域名通常会先使用cname解析到一个别名上,然后再解析到实际的ip地址上
switch {
case cname != "":
fmt.Println("cname:",cname)
if len(ns) != 0 {
fmt.Println("vips:")
for _, n := range ns {
fmt.Fprintf(os.Stdout, "%sn", n)
}
}
case len(ns) != 0:
for _, n := range ns {
fmt.Fprintf(os.Stdout, "%sn", n)
}
default:
fmt.Println(cname,ns)
}
}
# 输出了本地127.0.0.1的主机名
# xxbandy.github.io的cname域名和实际解析的ip地址
$ go run dns-test.go
hostname: [localhost]
cname: xxbandy.github.io.
vips:
185.199.110.153
185.199.111.153
185.199.109.153
185.199.108.153
2. 分析dns解析过程以及系统调用
注意:在linux环境下可以使用dig trace来追踪域名解析过程
我们都知道,在计算机的世界,建立连接都是需要依靠五元组的(源ip,源端口,目的ip,目的端口,协议),而在实际用户使用过程中,浏览器会帮我们识别和管理源ip和端口以及协议(http,https),协议确定后其实目的端口也就确定了(80或443). 因此整个DNS系统要解决的问题就是将用户在浏览器中输入的域名最终转换成可识别的目的ip
,进而进行连接通信。下面以一个简单例子来分析下dns解析的过程.
$ cat dns-test2.go
package main
import (
"net"
"fmt"
"os"
)
func main() {
dns := "xxbandy.github.io"
ns, err := net.LookupHost(dns)
if err != nil {
fmt.Fprintf(os.Stderr, "Err: %s", err.Error())
return
}
fmt.Println(ns)
}
# 构建程序
$ go build -o dns-test dns-test2.go
# 运行程序
bash-4.1# ./dns-test
[185.199.110.153 185.199.111.153 185.199.109.153 185.199.108.153]
# 使用linux系统工具trace分析整个dns解析过程的系统调用
# strace ./dns-test
## 开始执行程序
execve("./dns-test", ["./dns-test"], [/* 24 vars */]) = 0
brk(0) = 0x5ab000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6da42000
## 开始访问系统相关库
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=35815, ...}) = 0
mmap(NULL, 35815, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5c6da39000
close(3) = 0
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF211 3 >