【轉】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,
                },
        }
}


                                                                                                             

  

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM