Domain name resolution analysis using golang's net package

Keywords: Operation & Maintenance DNS github socket Linux

Background: In the actual use of the Internet, it is well known that the domain name is used to access a service directly, but with the continuous optimization of the Internet business structure, it may be a simple step for users to access a domain name to obtain related resources, but in fact, for the entire request process of the Internet is to doMany calls, and the first step is dns resolution.Of course, there are many tools for dns resolution in linux environment, such as dig and nslookup, but it is obviously unrealistic to directly investigate complex problems on the machine, so we intend to use golang's interface to encapsulate the domain name resolution service to provide later operation.

1. Use of net packages

Structural Methods Related to dns

# nameserver structure
type NS struct {
    Host string
}


# The srv record specifies which DNS server resolves the domain name
type SRV struct {
    Target   string
    Port     uint16
    Priority uint16
    Weight   uint16
}


# dns forward resolution (domain name resolved to cname or actual ip address)
## Return only the cname address of the specified domain name
func LookupCNAME(name string) (cname string, err error)

## Return directly to the domain name to resolve to the address.
func LookupHost(host string) (addrs []string, err error)

## Return the domain name directly to the address, [] ip structure. You can do related operations on specific IPS (whether loopback address, subnet, network number, etc.)
# type IP []byte
func LookupIP(host string) (addrs []IP, err error)

## DNS Reverse Resolution (ip Address Reverse Resolution Lookup Resolved Domain Name)
# Find hostname address based on ip address (must be resolvable domain name) [dig-x ipaddress]
func LookupAddr(addr string) (name []string, err error)


dns parsing query using net package

$ cat dns-test.go
package main

import (
  "net"
  "fmt"
  "os"
)


func main() {
  dns := "xxbandy.github.io"

  // Resolve cname
  cname,_ := net.LookupCNAME(dns)

  // Resolve ip address
  ns, err := net.LookupHost(dns)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Err: %s", err.Error())
    return
  }

  // Reverse Resolution (Host must be able to resolve to address)
  dnsname,_ := net.LookupAddr("127.0.0.1")
  fmt.Println("hostname:",dnsname)

  // Control and Judgment of Domain Name Resolution
  // Some domain names usually resolve to an individual name using cname before resolving to the actual ip address
  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)

  }

}

# Output host name for local 127.0.0.1
# The cname domain name of xxbandy.github.io and the actual resolved ip address
$ 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. Analyzing dns parsing process and system calls

Note: You can use dig +trace to track domain name resolution in a linux Environment

We all know that in the world of computers, connections are made on a five-tuple basis (source ip, source port, destination ip, destination port, protocol). While in real-world use, browsers help us identify and manage source IP and ports and protocols (http,https). When a protocol is determined, its destination port is also determined (80).Or 443). Therefore, the problem that the whole DNS system needs to solve is to convert the domain name that the user enters in the browser into a recognizable destination ip, and then connect to communicate.Here is a simple example of the DNS resolution process.

$ 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)
}

# Build program
$ go build -o dns-test dns-test2.go

# Run the program
bash-4.1# ./dns-test
[185.199.110.153 185.199.111.153 185.199.109.153 185.199.108.153]


# Use the linux system tool trace to analyze system calls for the entire dns parsing process
# strace ./dns-test
## Start executing program
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
## Start accessing system related libraries
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
## Report some real-time signals
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
## Fast Synchronization
futex(0xc420060110, FUTEX_WAKE, 1)      = 1
futex(0xc42002cd10, FUTEX_WAKE, 1)      = 1
futex(0x58bd50, FUTEX_WAIT, 0, NULL)    = -1 EAGAIN (Resource temporarily unavailable)

## Reader Execution Command
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
## Read system configuration (maximum listening queue length for port: 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
## Start creating socket s for invocation
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
## Read network-related configuration (Name Service Switch configuration)
## Read hosts: files dns (that is, get the domain name and host address from the / etc/hosts file or use the dns service)
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
## Read/etc/resolv.conf to get 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
## Check and read/etc/hosts file for parsing records
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
....
....

## Start building a create link to request dns
socket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 5
setsockopt(5, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
## Link to local 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
## Write xxbandy.github.io(tcp data transfer) to link
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
## Get addresses parsed from dnsserver
epoll_wait(4, [185.199.111.153 185.199.110.153 185.199.108.153 185.199.109.153]
 <unfinished ... exit status 0>

As with the whole system call process above, you can basically re-validate the entire DNS query process:

  • 1. Check the local hosts file for parsing records and return the parsing address if it exists
  • 2. Non-existent initiates recursive queries based on dnsserver read from resolv.conf
  • 3.dnsserver continuously initiates iterative queries to superior dnsserver
  • 4.dnsserver eventually returns the query results to the requester

In fact, throughout the above analysis process, you can also try to validate the entire query process by modifying the / etc/hosts,/etc/resolv.conf configuration file, which will not be repeated here. Welcome to my Public Number

Posted by jbog91 on Mon, 09 Sep 2019 17:55:44 -0700