golang DNS服务器的简单实现操作

  • Post category:Linux

下面是详细讲解“golangDNS服务器的简单实现操作”的完整攻略。

什么是DNS服务器

DNS全称为Domain Name System,它是一种将域名转换成IP地址的系统。通常情况下,当我们在浏览器中输入某个网站的域名时,DNS服务器会将该域名转换成对应的IP地址,才能正常访问该网站。DNS服务器是整个网络系统中至关重要的一部分,它向客户端提供域名解析的服务,而这种服务通常是由大型的ISP或企业网络自建的。

golang实现DNS服务器的步骤

  1. 解析DNS请求:Go提供了 net 包,通过函数 net.ListenUDP() 功能可以轻易地实现 Goroutine 实现 UDP 网络请求,用户定义好获取dns查询的端口和IP地址,然后想要监听的DNS UDP端口。例如:
func main() {
    udpAddr, err := net.ResolveUDPAddr("udp4", ":53")
    if err != nil {
        println("dns server init fail")
        return
    }
    udpConn, err := net.ListenUDP("udp4", udpAddr)
    if err != nil {
        println("dns server init fail")
        return
    }
    defer udpConn.Close()
}
  1. 获取请求报文信息:我们定义一个最基本的DNS报文,它由请求头、回答头、问题区和回答区这四部分组成。其中,请求头包含了发至DNS服务器的相关信息(如IP地址、端口、协议等),回答头包含了DNS服务器本身的信息(如IP地址、端口、协议等),问题区包含DNS请求报文中关于域名转IP地址的详细信息,回答区包含DNS服务器返回给客户端的IP地址。如果请求报文合法,我们就可以对其中的查询信息进行处理。例如:
func main() {
    udpAddr, err := net.ResolveUDPAddr("udp4", ":53")
    if err != nil {
        println("dns server init fail")
        return
    }
    udpConn, err := net.ListenUDP("udp4", udpAddr)
    if err != nil {
        println("dns server init fail")
        return
    }
    defer udpConn.Close()

    buf := make([]byte, 512)
    for {
        n, addr, err := udpConn.ReadFromUDP(buf)
        if err != nil {
            continue
        }
        println("udp request from ", addr.String())
        go handleDNSReq(buf[:n], udpConn, addr)
    }
}

func handleDNSReq(buf []byte, udpConn *net.UDPConn, addr *net.UDPAddr) {
    // 处理DNS请求报文
}
  1. 根据请求报文进行查询:当我们获取到DNS请求报文后,需要对其进行解析,以获取查询信息。我们可以使用 net 包中的 net.ParseIP() 函数解析出 DNS 请求中的协议与 IP 地址,结合 systemlookup 包中的 LookupHost() 函数,从而完成 DNS 解析的流程。例如:
func handleDNSReq(buf []byte, udpConn *net.UDPConn, addr *net.UDPAddr) {
    req := new(dns.Msg)
    _, err := req.Unpack(buf)
    if err != nil {
        println("unpack dns message fail")
        return
    }

    // 回应信息
    res := new(dns.Msg)
    res.SetReply(req)

    queryHost := req.Question[0].Name
    println("query dns result by host:", queryHost)

    ipSlice, err := net.LookupHost(queryHost)
    if err != nil {
        println("lookup dns fail")
        return
    }

    for _, ip := range ipSlice {
        rr, _ := dns.NewRR(fmt.Sprintf("%s IN A %s", queryHost, ip))
        res.Answer = append(res.Answer, rr)
    }

    udpConn.WriteToUDP(res.Pack(), addr)
}

这样,我们就完成了一个简单的golang DNS服务器的实现。当客户端发出DNS请求时,服务器将收到请求信息,并根据请求信息进行查询和返回对应的IP地址。

示例说明

示例1

我们假设有个域名为example.com,将它解析成对应的IP地址,并查询DNS服务器是否配置正常。

  1. 编写golangDNS服务器程序:
package main

import (
    "fmt"
    "net"
    "net/http"

    "github.com/miekg/dns"
)

func main() {
    udpAddr, err := net.ResolveUDPAddr("udp4", ":53")
    if err != nil {
        println("dns server init fail")
        return
    }
    udpConn, err := net.ListenUDP("udp4", udpAddr)
    if err != nil {
        println("dns server init fail")
        return
    }
    defer udpConn.Close()

    buf := make([]byte, 512)
    for {
        n, addr, err := udpConn.ReadFromUDP(buf)
        if err != nil {
            continue
        }
        println("udp request from ", addr.String())
        go handleDNSReq(buf[:n], udpConn, addr)
    }
}

func handleDNSReq(buf []byte, udpConn *net.UDPConn, addr *net.UDPAddr) {
    req := new(dns.Msg)
    _, err := req.Unpack(buf)
    if err != nil {
        println("unpack dns message fail")
        return
    }

    // 回应信息
    res := new(dns.Msg)
    res.SetReply(req)

    // 查询信息
    queryHost := req.Question[0].Name
    println("query dns result by host:", queryHost)

    ipSlice, err := net.LookupHost(queryHost)
    if err != nil {
        println("lookup dns fail")
        return
    }

    for _, ip := range ipSlice {
        rr, _ := dns.NewRR(fmt.Sprintf("%s IN A %s", queryHost, ip))
        res.Answer = append(res.Answer, rr)
    }

    udpConn.WriteToUDP(res.Pack(), addr)
}
  1. 启动golangDNS服务器:
go run dns.go
  1. 使用nslookup命令验证DNS解析是否正确:
nslookup example.com localhost

将会得到类似以下的输出:

Server:         localhost
Address:        127.0.0.1#53

Non-authoritative answer:
Name:   example.com
Address: 93.184.216.34

示例2

我们假设有个域名为baidus.com,由于客户端网络故障等原因,导致DNS解析失败,此时我们需要通过golangDNS服务器手动解析。我们可以使用 Go 自带的 net.LookupHost() 函数进行解析,将域名解析成 IP,然后再执行我们自己的 DNS 解析服务。

  1. 编写golangDNS服务器程序:
package main

import (
    "fmt"
    "net"
    "net/http"
    "time"

    "github.com/miekg/dns"
)

func main() {
    udpAddr, err := net.ResolveUDPAddr("udp4", ":53")
    if err != nil {
        println("dns server init fail")
        return
    }
    udpConn, err := net.ListenUDP("udp4", udpAddr)
    if err != nil {
        println("dns server init fail")
        return
    }
    defer udpConn.Close()

    buf := make([]byte, 512)
    for {
        n, addr, err := udpConn.ReadFromUDP(buf)
        if err != nil {
            continue
        }
        println("udp request from ", addr.String())
        go handleDNSReq(buf[:n], udpConn, addr)
    }
}

func handleDNSReq(buf []byte, udpConn *net.UDPConn, addr *net.UDPAddr) {
    req := new(dns.Msg)
    _, err := req.Unpack(buf)
    if err != nil {
        println("unpack dns message fail")
        return
    }

    // 回应信息
    res := new(dns.Msg)
    res.SetReply(req)

    // 查询信息
    queryHost := req.Question[0].Name
    println("query dns result by host:", queryHost)

    // 手动解析DNS
    if queryHost == "baidus.com." {
        ips, err := net.LookupHost("baidu.com")
        if err != nil {
            println("lookup baidu.com ips fail", err.Error())
            return
        }

        for _, ip := range ips {
            rr, _ := dns.NewRR(fmt.Sprintf("%s IN A %s", queryHost, ip))
            res.Answer = append(res.Answer, rr)
        }
    } else {
        // 跨网络访问,等待5s
        time.Sleep(5 * time.Second)

        // TODO: 跨网络访问真实DNS服务器获取解析结果
    }

    udpConn.WriteToUDP(res.Pack(), addr)
}
  1. 启动golangDNS服务器:
go run dns.go
  1. 使用nslookup命令验证DNS解析是否正常:
nslookup baidus.com localhost

将会得到类似以下的输出:

Server:         localhost
Address:        127.0.0.1#53

Non-authoritative answer:
Name:   baidus.com
Address: 220.181.38.149
Name:   baidus.com
Address: 220.181.38.148