使用golang的net包进行域名解析过程分析

背景: 在实际的互联网使用过程中,大家熟知的是使用域名来直接访问一个服务,但随着互联网业务架构的不断优化,可能对用用户来说访问一个域名获取到相关的资源是很简单的一步,但其实对于互联网整个请求过程其实是做了很多次调用,那最开始的一步就是dns解析。当然在linux环境下,用来做dns解析的工具有很多,比如dignslookup之类的,但是通常对于复杂问题的排查直接去机器上去很显然是不太现实的,因此打算使用golang的接口来封装域名解析服务,来提供后期的操作.

1. net包的使用

和dns相关结构体方法

# 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解析查询

$ 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, "%s\n", n)
            }
         }
    case len(ns) != 0:
        for _, n := range ns {
            fmt.Fprintf(os.Stdout, "%s\n", 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, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220]\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=805909, ...}) = 0
mmap(NULL, 2212768, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5c6d605000
mprotect(0x7f5c6d61d000, 2093056, PROT_NONE) = 0
mmap(0x7f5c6d81c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17000) = 0x7f5c6d81c000
mmap(0x7f5c6d81e000, 13216, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f5c6d81e000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\371\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=10163394, ...}) = 0
mmap(NULL, 3861032, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5c6d256000
mprotect(0x7f5c6d3fc000, 2093056, PROT_NONE) = 0
mmap(0x7f5c6d5fb000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a5000) = 0x7f5c6d5fb000
mmap(0x7f5c6d601000, 14888, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f5c6d601000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6da38000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6da37000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6da36000
arch_prctl(ARCH_SET_FS, 0x7f5c6da37700) = 0
mprotect(0x7f5c6d5fb000, 16384, PROT_READ) = 0
mprotect(0x7f5c6d81c000, 4096, PROT_READ) = 0
mprotect(0x7f5c6da43000, 4096, PROT_READ) = 0
munmap(0x7f5c6da39000, 35815)           = 0
set_tid_address(0x7f5c6da379d0)         = 31369
set_robust_list(0x7f5c6da379e0, 0x18)   = 0
futex(0x7fffb36496ec, FUTEX_WAKE_PRIVATE, 1) = 0
futex(0x7fffb36496ec, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 1, NULL, 7f5c6da37700) = -1 EAGAIN (Resource temporarily unavailable)
rt_sigaction(SIGRTMIN, {0x7f5c6d60ac10, [], SA_RESTORER|SA_SIGINFO, 0x7f5c6d614520}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {0x7f5c6d60aca0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x7f5c6d614520}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=10240*1024, rlim_max=RLIM_INFINITY}) = 0
brk(0)                                  = 0x5ab000
brk(0x5cc000)                           = 0x5cc000
sched_getaffinity(0, 8192,  { f, 0 })   = 16
mmap(0xc000000000, 65536, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc000000000
munmap(0xc000000000, 65536)             = 0
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6d9f6000
mmap(0xc420000000, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc420000000
mmap(0xc41fff8000, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc41fff8000
mmap(0xc000000000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc000000000
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6d9e6000
clock_gettime(CLOCK_MONOTONIC, {4672175, 535562591}) = 0
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6d9d6000
clock_gettime(CLOCK_MONOTONIC, {4672175, 535651726}) = 0
clock_gettime(CLOCK_MONOTONIC, {4672175, 535669542}) = 0
rt_sigprocmask(SIG_SETMASK, NULL, [], 8) = 0
clock_gettime(CLOCK_MONOTONIC, {4672175, 535849510}) = 0
clock_gettime(CLOCK_MONOTONIC, {4672175, 535878262}) = 0
sigaltstack(NULL, {ss_sp=0, ss_flags=SS_DISABLE, ss_size=0}) = 0
sigaltstack({ss_sp=0xc420002000, ss_flags=0, ss_size=32672}, NULL) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
gettid()                                = 31369
## 上报一些实时信号
rt_sigaction(SIGHUP, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGHUP, {0x4555b0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x7f5c6d614520}, NULL, 8) = 0
rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {0x4555b0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x7f5c6d614520}, NULL, 8) = 0
rt_sigaction(SIGQUIT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGRT_32, {0x4555b0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x7f5c6d614520}, NULL, 8) = 0
.....
.....
clock_gettime(CLOCK_MONOTONIC, {4672175, 560948747}) = 0
rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], [], 8) = 0
mmap(NULL, 10489856, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f5c6c855000
mprotect(0x7f5c6c855000, 4096, PROT_NONE) = 0
clone(child_stack=0x7f5c6d254ff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f5c6d2559d0, tls=0x7f5c6d255700, child_tidptr=0x7f5c6d2559d0) = 31370
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], [], 8) = 0
mmap(NULL, 10489856, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f5c6be54000
mprotect(0x7f5c6be54000, 4096, PROT_NONE) = 0
clone(child_stack=0x7f5c6c853ff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f5c6c8549d0, tls=0x7f5c6c854700, child_tidptr=0x7f5c6c8549d0) = 31371
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], [], 8) = 0
mmap(NULL, 10489856, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f5c6b453000
mprotect(0x7f5c6b453000, 4096, PROT_NONE) = 0
clone(child_stack=0x7f5c6be52ff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f5c6be539d0, tls=0x7f5c6be53700, child_tidptr=0x7f5c6be539d0) = 31372
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
## 快速同步
futex(0xc420060110, FUTEX_WAKE, 1)      = 1
futex(0xc42002cd10, FUTEX_WAKE, 1)      = 1
futex(0x58bd50, FUTEX_WAIT, 0, NULL)    = -1 EAGAIN (Resource temporarily unavailable)

## 读取程序的执行命令
readlinkat(AT_FDCWD, "/proc/self/exe", "/root/dns-test", 128) = 14
futex(0xc420060110, FUTEX_WAKE, 1)      = 1
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6d996000
## 读取系统配置(端口最大的监听队列的长度:TCP backlog)
openat(AT_FDCWD, "/proc/sys/net/core/somaxconn", O_RDONLY|O_CLOEXEC) = 3
read(3, "65535\n", 4096)                = 6
read(3, "", 4090)                       = 0
close(3)                                = 0
## 开始创建socket进行调用
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
close(3)                                = 0
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 3
setsockopt(3, SOL_IPV6, IPV6_V6ONLY, [1], 4) = 0
bind(3, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 4
setsockopt(4, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
bind(4, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
close(4)                                = 0
close(3)                                = 0
## 读取网络相关配置(名字服务切换配置)
## 读取hosts:      files dns(即获取域名和主机地址可以从/etc/hosts文件或使用dns服务)
openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 1024) = 1024
read(3, "w:     files\ngroup:      files\n\n"..., 1024) = 664
read(3, "", 1024)                       = 0
close(3)                                = 0
## 读取/etc/resolv.conf 获取dnsserver
openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=176, ...}) = 0
read(3, "search 10.13.6.2"..., 4096) = 176
read(3, "", 3920)                       = 0
read(3, "", 4096)                       = 0
close(3)                                = 0
## 检查并读取/etc/hosts文件是否有解析记录
stat("/etc/hosts", {st_mode=S_IFREG|0644, st_size=517, ...}) = 0
openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
read(3, "127.0.0.1   localhost localhost."..., 4096) = 517
read(3, "", 3579)                       = 0
read(3, "", 4096)                       = 0
close(3)                                = 0
....
....

## 开始建立创建链接去请求dns
socket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 5
setsockopt(5, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
## 链接到本地的dnsserver(/etc/resolv.conf)
connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.13.6.2")}, 16) = 0
epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT|EPOLLET|0x2000, {u32=1838509632, u64=140034952228416}}) = 0
getsockname(5, {sa_family=AF_INET, sin_port=htons(38024), sin_addr=inet_addr("10.13.15.218")}, [16]) = 0
getpeername(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.13.6.2")}, [16]) = 0
clock_gettime(CLOCK_REALTIME, {1555730258, 381131545}) = 0
clock_gettime(CLOCK_MONOTONIC, {4672175, 563905109}) = 0
clock_gettime(CLOCK_MONOTONIC, {4672175, 563982851}) = 0
futex(0x58b3d8, FUTEX_WAKE, 1)          = 1
futex(0x58b3c0, FUTEX_WAKE, 1)          = 1
clock_gettime(CLOCK_REALTIME, {1555730258, 381309493}) = 0
## 向链接中写入xxbandy.github.io(tcp数据传输)
write(5, "\20z\1\0\0\1\0\0\0\0\0\0\7xxbandy\6github\2io\0\0"..., 35) = 35
read(5, 0xc4200aa000, 512)              = -1 EAGAIN (Resource temporarily unavailable)
futex(0x58bd50, FUTEX_WAIT, 0, NULL)    = 0
epoll_wait(4, {}, 128, 0)               = 0
## 获取从dnsserver中解析的地址
epoll_wait(4, [185.199.111.153 185.199.110.153 185.199.108.153 185.199.109.153]
 <unfinished ... exit status 0>

如上整个系统调用过程,基本上可以再一次验证整个DNS的整个查询过程:

  • 1.检查本地hosts文件是否存在解析记录,存在即返回解析地址
  • 2.不存在即根据resolv.conf中读取的dnsserver发起递归查询
  • 3.dnsserver不断的向上级dnsserver发起迭代查询
  • 4.dnsserver最终返回查询结果给请求者

其实,以上整个分析过程,你也可用尝试在修改/etc/hosts,/etc/resolv.conf配置文件来验证整个查询过程,这里就不再赘述了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券