好好爱自己!

【转】Simple Golang DNS Server

原文: https://gist.github.com/walm/0d67b4fb2d5daf3edd4fad3e13b162cb

 

这篇也可以参考下?? https://blog.csdn.net/qq_27068845/article/details/104597845

 

 

使用Go语言写一个DNS服务 https://baijiahao.baidu.com/s?id=1619288823505810734&wfr=spider&for=pc

要求:可以转发和缓存DNS查询的本地DNS服务器

额外1:为它提供一个记录管理界面(HTTP处理程序)

额外2:给它起个名字

关于DNS服务器的一些事情:

DNS服务器将名称转换为IPDNS主要在端口53上使用UDP协议DNS消息最大长度为512字节,更长的必须使用EDNS我们需要的成分:

UDPDNS消息解析器转发高速缓存HTTP处理程序配方:

UDP:支持std net packageDNS消息解析器:根据特定协议处理来自线路的数据包将需要一些工作,为了快速实现,我们将使用golang.org/x/net/dns/dnsmessage转发:除了让我们使用Cloudflare公共解析器1.1.1.1缓存:内存和持久性,对于持久性写入,我们将使用std gob包对数据进行编码HTTP处理程序:应该创建,读取,更新和删除DNS记录。 无需配置文件。打开监听端口53的UDP套接字,这将接收传入的DNS查询。 请注意,UDP只需要1个套接字来处理多个“连接”,同时TCP是每个连接1个套接字。 所以我们将在整个程序中重用conn。

解析数据包以查看它是否是DNS消息。

如果你好奇DNS消息是怎样的:

向公共解析器转发消息

解析器将回复答案,我们将获取该消息并将其提供给客户端

conn对于并发使用也是安全的,所以那些WriteToUDP应该在goroutine中。

记住答案

我们将使用map,只需按问题键入答案,它使查找变得非常容易,也不要忘记RWMutex,映射对于并发使用是不安全的。 请注意,理论上DNS查询中可能存在多个问题,但大多数DNS服务器只接受1个问题。

持久缓存

我们需要将s.data写入文件并在以后检索它。 要没有自定义解析,我们将使用std gob

注意gob在编码之前需要知道数据类型:

记录管理

这很容易,Create处理程序看起来像这样

——————————————————————————————

 

dns.go

package main

import (
	"fmt"
	"log"
	"strconv"

	"github.com/miekg/dns"
)

var records = map[string]string{
	"test.service.": "192.168.0.2",
}

func parseQuery(m *dns.Msg) {
	for _, q := range m.Question {
		switch q.Qtype {
		case dns.TypeA:
			log.Printf("Query for %s\n", q.Name)
			ip := records[q.Name]
			if ip != "" {
				rr, err := dns.NewRR(fmt.Sprintf("%s A %s", q.Name, ip))
				if err == nil {
					m.Answer = append(m.Answer, rr)
				}
			}
		}
	}
}

func handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)
	m.Compress = false

	switch r.Opcode {
	case dns.OpcodeQuery:
		parseQuery(m)
	}

	w.WriteMsg(m)
}

func main() {
	// attach request handler func
	dns.HandleFunc("service.", handleDnsRequest)

	// start server
	port := 5353
	server := &dns.Server{Addr: ":" + strconv.Itoa(port), Net: "udp"}
	log.Printf("Starting at %d\n", port)
	err := server.ListenAndServe()
	defer server.Shutdown()
	if err != nil {
		log.Fatalf("Failed to start server: %s\n ", err.Error())
	}
}

  

1. 开启一个终端,运行 go run dns.go

 

 

2. 另一个终端(同一台服务器): dig @localhost -p 5353 test.service

 ————————————————————————————————————————————————————————————

DNS QUERY MESSAGE FORMAT

WRITTEN BY ADMINISTRATOR. POSTED IN DOMAIN NAME SYSTEM (DNS)

3.658536585365911111 Rating 3.66 (41 Votes)
 
 
 
Pin It

This section will deal with the analysis of the DNS packets by examining how DNS messages are formatted and the options and variables they contain. To fully understand a protocol, you must understand the information the protocol carries from one host to another, along with any options available.

Because the DNS message format can vary, depending on the query and the answer, we've broken this analysis into two parts:

Part 1 analyses the DNS format of a query, in other words, it shows the contents of a DNS query packet to a DNS server, requesting to resolve a domain.

Part 2 analyses the DNS format of a response, that is, when the DNS server is responding to our inital DNS query.

This breakdown help make our analysis easier to understand and follow, rather than analyzing DNS queries and answers on the same page.

 

DNS ANALYSIS - HOST QUERY

As mentioned in the previous sections of the DNS Protocol, a DNS query is generated when the client needs to resolve a domain name into an IP Address. This could be the result of entering "www.firewall.cx" in the url field of your web browser, or simply by launching a program that uses the Internet and therefore generates DNS queries in order to successfully communicate with the host or server it needs.

We've also included a live example (using a packet analyser), to help better understander the packets contents. Later on we'll be analysing each field within the DNS packet. For now, let's check out what a packet containing a DNS query would look like on our network:

dns-query-format-1

The above captured DNS query was generated by typing ping www.firewall.cx from the prompt of our Linux server. The command generated this packet, which was then placed on our network and sent to a DNS server on the Internet. 

Notice the Destination Port which is set to 53, the port the DNS protocol. In addition, you'll notice that the transport protocol  used is UDP:

dns-query-format-2

        dns-query-format-3
Ethernet II (Check Ethernet Frames section for more info) is the most common type of frame found on LANs, in fact it probably is the only type you will find on 95% of all networks if you're only running TCP/IP and Windows or Unix-like machines. This particular one contains a DNS section, which could be either a Query or Response. We are assuming a Query, so it can fit nicely in our example. 

We are going to take the DNS Section above and analyse its contents, which are already shown in the picture above (Right hand side, labeled "Capture") taken from my packet analyser. 

Here they are again in a cool 3D diagram:

dns-query-format-4

 

From this whole packet, the DNS Query Section is the part we're interested in (analysed shortly), the rest is more or less overhead and information to let the server know a bit more information about our query.

The analysis of each 3D block (field) is shown in the left picture below so you can understand the function of each field and the DNS Query Section captured by my packet sniffer on the right:

dns-query-format-5        dns-query-format-6

All fields in the DNS Query section except the DNS Name field (underlined in red in the picture above), have set lengths. The DNS Name field has no set length because it varies depending on the domain name length as we are going to see soon.

For example, a query for www.cisco.com will require DNS Name field to be smaller than a query for support.novell.com simply because the second domain is longer.

 

THE DNS NAME FIELD

To prove this I captured a few packets that show different lengths for the domain names I just mentioned but, because the DNS section in a packet provides no length field, we need to look one level above, which is the UDP header, in order to calculate the DNS section length. By subtracting the UDP header length (always 8 bytes - check the UDP article for more information) from the bytes in the Length field, we are left with the length of the DNS section:

dns-query-format-7

     dns-query-format-8

The two examples clearly show that the Length Field in the UDP header varies depending on the domain we are trying to resolve. The UDP header is 8 bytes in both examples and all fields in the DNS Section, except for the DNS Name field, are always 2 bytes.

 

THE FLAGS/PARAMETERS FIELD

The Parameter Field (labeled Flags) is one of the most important fields in DNS because it is responsible for letting the server or client know a lot of important information about the DNS packet. For example, it contains information as to whether the DNS packet is a query or response and, in the case of a query, if it should be a recursive or non-recursive type. This is most important because as we've already seen, it determines how the query is handled by the server.

Let's have a closer look at the flags and explain the meaning of each one. We've marked the bit numbers with black on the left hand side of each flag parameter so you can see which ones are used during a response. The picture on the right hand side explains the various bits. You won't see all 16 bits used in a query as the rest are used during a response or might be reserved:

dns-query-format-9

   dns-query-format-10

As you can see, only bits 1, 2-578 and 12 are used in this query. The rest will be a combination of reserved bits and bits that are used only in responses. When you read the DNS response message format page, you will find a similar packet captured which is a reponse to the above query and the rest of the bits used are analysed.

And that just about does it for the DNS Query message format. Next up is the DNS Response message format page which we are sure you will find just as interesting!

 

 

package main

import (
        "fmt"
        "net"

        "golang.org/x/net/dns/dnsmessage"
)

func main() {
        conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 8088})
        if err != nil {
                panic(err)
        }
        defer conn.Close()
        fmt.Println("Listing ...")
        for {
                buf := make([]byte, 512)
                _, addr, _ := conn.ReadFromUDP(buf)

                var msg dnsmessage.Message
                if err := msg.Unpack(buf); err != nil {
                        fmt.Println(err)
                        continue
                }
                go ServerDNS(addr, conn, msg)
        }
}

// address books
var (
        addressBookOfA = map[string][4]byte{
                "www.baidu.com.": [4]byte{220, 181, 38, 150},
        }
        addressBookOfPTR = map[string]string{
                "150.38.181.220.in-addr.arpa.": "www.baidu.com.",
        }
)

// ServerDNS serve
func ServerDNS(addr *net.UDPAddr, conn *net.UDPConn, msg dnsmessage.Message) {
        // query info
        if len(msg.Questions) < 1 {
                return
        }
        question := msg.Questions[0]
        var (
                queryTypeStr = question.Type.String()
                queryNameStr = question.Name.String()
                queryType    = question.Type
                queryName, _ = dnsmessage.NewName(queryNameStr)
        )
        fmt.Printf("[%s] queryName: [%s]\n", queryTypeStr, queryNameStr)

        // find record
        var resource dnsmessage.Resource
        switch queryType {
        case dnsmessage.TypeA:
                if rst, ok := addressBookOfA[queryNameStr]; ok {
                        resource = NewAResource(queryName, rst)
                } else {
                        fmt.Printf("not fount A record queryName: [%s] \n", queryNameStr)
                        Response(addr, conn, msg)
                        return
                }
        case dnsmessage.TypePTR:
                if rst, ok := addressBookOfPTR[queryName.String()]; ok {
                        resource = NewPTRResource(queryName, rst)
                } else {
                        fmt.Printf("not fount PTR record queryName: [%s] \n", queryNameStr)
                        Response(addr, conn, msg)
                        return
                }
        default:
                fmt.Printf("not support dns queryType: [%s] \n", queryTypeStr)
                return
        }
        
        // send response
        msg.Response = true
        msg.Answers = append(msg.Answers, resource)
        Response(addr, conn, msg)
}       

// Response return
func Response(addr *net.UDPAddr, conn *net.UDPConn, msg dnsmessage.Message) {
        packed, err := msg.Pack() 
        if err != nil {
                fmt.Println(err)
                return
        }
        if _, err := conn.WriteToUDP(packed, addr); err != nil {
                fmt.Println(err)
        }       
}

// NewAResource A record
func NewAResource(query dnsmessage.Name, a [4]byte) dnsmessage.Resource {
        return dnsmessage.Resource{
                Header: dnsmessage.ResourceHeader{
                        Name:  query,
                        Class: dnsmessage.ClassINET,
                        TTL:   600,
                },
                Body: &dnsmessage.AResource{
                        A: a,
                },
        }
}

// NewPTRResource PTR record
func NewPTRResource(query dnsmessage.Name, ptr string) dnsmessage.Resource {
        name, _ := dnsmessage.NewName(ptr)
        return dnsmessage.Resource{
                Header: dnsmessage.ResourceHeader{
                        Name:  query,
                        Class: dnsmessage.ClassINET,
                },
                Body: &dnsmessage.PTRResource{
                        PTR: name,
                },
        }
}


                                                                                                             

  

 

 

 

posted @ 2020-12-17 20:49  立志做一个好的程序员  阅读(1709)  评论(0编辑  收藏  举报

不断学习创作,与自己快乐相处