概要

DNS协议属于比较简单的网络协议,最近用golang实现了对于dns协议的解包和打包,暂时只实现了一个查询问题与一个回答问题,代码如下。
本文代码地址
https://github.com/changjixiong/goNotes/tree/master/dnsnotes

DNS报文解包与打包
package dnsKit

import (
	"bytes"
	"encoding/binary"
	"net"
	"strings"
)

/*
DNS报文格式,不论是请求报文,还是DNS服务器返回的应答报文,都使用统一的格式。
2个字节(16bit),标识字段,客户端会解析服务器返回的DNS应答报文,获取ID值与请求报文设置的ID值做比较
如果相同,则认为是同一个DNS会话。
QR	标示该消息是请求消息(该位为0)还是应答消息(该位为1)
QR这一段位 Flag 16bit长度
header
  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|  opcode   |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

question
  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     ...                       |
|                    QNAME                      |
|                     ...                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QTYPE                      |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QCLASS                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/

// DNSHeader 头
type DNSHeader struct {
	ID uint16

	//golang 没有bit类型,只能这样处理
	QR                  uint16 //1bit
	OperationCode       uint16 //4bit
	AuthoritativeAnswer uint16 //1bit
	Truncation          uint16 //1bit
	RecursionDesired    uint16 //1bit
	RecursionAvailable  uint16 //1bit
	Zero                uint16 //3bit
	ResponseCode        uint16 //4bit

	QuestionCount uint16
	AnswerRRs     uint16
	AuthorityRRs  uint16
	AdditionalRRs uint16
}

func (dh *DNSHeader) ToBytes() []byte {
	var buffer bytes.Buffer

	binary.Write(&buffer, binary.BigEndian, dh.ID)

	bits := dh.QR<<15 + dh.OperationCode<<11 + dh.AuthoritativeAnswer<<10 + dh.Truncation<<9
	bits += dh.RecursionDesired<<8 + dh.RecursionAvailable<<7 + dh.ResponseCode
	binary.Write(&buffer, binary.BigEndian, bits)

	binary.Write(&buffer, binary.BigEndian, dh.QuestionCount)
	binary.Write(&buffer, binary.BigEndian, dh.AnswerRRs)
	binary.Write(&buffer, binary.BigEndian, dh.AuthorityRRs)
	binary.Write(&buffer, binary.BigEndian, dh.AdditionalRRs)

	return buffer.Bytes()
}

func NewDNSHeader(buffer *bytes.Buffer) *DNSHeader {
	// 读12byte
	id := binary.BigEndian.Uint16(buffer.Next(2))
	flag := binary.BigEndian.Uint16(buffer.Next(2))
	return &DNSHeader{
		ID: id,

		QR:                  flag >> 15,
		OperationCode:       (flag >> 11) % (1 << 4),
		AuthoritativeAnswer: (flag >> 10) % (1 << 1),
		Truncation:          (flag >> 9) % (1 << 1),
		RecursionDesired:    (flag >> 8) % (1 << 1),
		RecursionAvailable:  (flag >> 7) % (1 << 1),
		Zero:                (flag >> 4) % (1 << 3),
		ResponseCode:        flag % (1 << 4),

		QuestionCount: binary.BigEndian.Uint16(buffer.Next(2)),
		AnswerRRs:     binary.BigEndian.Uint16(buffer.Next(2)),
		AuthorityRRs:  binary.BigEndian.Uint16(buffer.Next(2)),
		AdditionalRRs: binary.BigEndian.Uint16(buffer.Next(2)),
	}
}

//DNSQuestion 请求
type DNSQuestion struct {
	QuestionName  string
	QuestionType  uint16
	QuestionClass uint16
}

func (dq *DNSQuestion) ToBytes() []byte {
	var buffer bytes.Buffer

	segments := strings.Split(dq.QuestionName, ".")
	for _, seg := range segments {
		binary.Write(&buffer, binary.BigEndian, byte(len(seg)))
		binary.Write(&buffer, binary.BigEndian, []byte(seg))
	}
	binary.Write(&buffer, binary.BigEndian, byte(0x00))

	binary.Write(&buffer, binary.BigEndian, dq.QuestionType)
	binary.Write(&buffer, binary.BigEndian, dq.QuestionClass)

	return buffer.Bytes()
}

func NewDNSQuestion(buffer *bytes.Buffer) *DNSQuestion {
	//8bit标记每一级域名的长度
	// buf := bytes.NewBuffer(data)
	length := uint8(0)
	binary.Read(buffer, binary.BigEndian, &length)
	segments := []string{}
	for length > 0 {
		seg := make([]byte, length)
		binary.Read(buffer, binary.BigEndian, &seg)
		segments = append(segments, string(seg))
		binary.Read(buffer, binary.BigEndian, &length)
		// fmt.Println(length)
	}

	question := &DNSQuestion{
		QuestionName: strings.Join(segments, "."),
	}

	question.QuestionType = binary.BigEndian.Uint16(buffer.Next(2))
	question.QuestionClass = binary.BigEndian.Uint16(buffer.Next(2))

	return question
}

//DNSResourceRecode 回答字段,授权字段,附加字段
type DNSResourceRecode struct {
	Name     string
	NamePos  uint16
	RRType   uint16
	Class    uint16
	TTL      uint32
	RDLength uint16
	RData    string
}

func (drr *DNSResourceRecode) ToBytes() []byte {
	var buffer bytes.Buffer
	if drr.NamePos > 0 {
		binary.Write(&buffer, binary.BigEndian, (0x01<<15)|(0x01<<14)|drr.NamePos)
	} else {
		segments := strings.Split(drr.Name, ".")
		for _, seg := range segments {
			binary.Write(&buffer, binary.BigEndian, byte(len(seg)))
			binary.Write(&buffer, binary.BigEndian, []byte(seg))
		}
		binary.Write(&buffer, binary.BigEndian, byte(0x00))
	}

	binary.Write(&buffer, binary.BigEndian, drr.RRType)
	binary.Write(&buffer, binary.BigEndian, drr.Class)
	binary.Write(&buffer, binary.BigEndian, drr.TTL)
	binary.Write(&buffer, binary.BigEndian, drr.RDLength)
	if drr.Class == 1 {
		binary.Write(&buffer, binary.BigEndian, []byte(net.ParseIP(drr.RData).To4()))
	} else if drr.Class == 5 {

	}

	return buffer.Bytes()
}

func NewDNSResourceRecode(buffer *bytes.Buffer) *DNSResourceRecode {
	tag := buffer.Next(1)[0] >> 6
	buffer.UnreadByte()
	drr := &DNSResourceRecode{}
	if tag == 3 {
		//最高两位11,右移后是3
		drr.NamePos = (binary.BigEndian.Uint16(buffer.Next(2)) << 2) >> 2
	} else {

	}
	drr.RRType = binary.BigEndian.Uint16(buffer.Next(2))
	drr.Class = binary.BigEndian.Uint16(buffer.Next(2))
	drr.TTL = binary.BigEndian.Uint32(buffer.Next(4))
	drr.RDLength = binary.BigEndian.Uint16(buffer.Next(2))

	if drr.RRType == 1 && drr.RDLength == 4 {
		drr.RData = net.IPv4(buffer.Next(1)[0], buffer.Next(1)[0], buffer.Next(1)[0], buffer.Next(1)[0]).String()
	} else {
		//FIXME:
		//域名处理
	}

	return drr
}

// DNSMessage 消息体
type DNSMessage struct {
	Header          *DNSHeader
	Questions       []*DNSQuestion
	ResourceRecodes []*DNSResourceRecode
}

func (dm *DNSMessage) ToBytes() []byte {
	result := []byte{}
	result = append(result, dm.Header.ToBytes()...)

	for i := uint16(0); i < dm.Header.QuestionCount; i++ {
		result = append(result, dm.Questions[i].ToBytes()...)
	}

	for _, rr := range dm.ResourceRecodes {
		result = append(result, rr.ToBytes()...)
	}

	return result
}

func NewDNSMessage(buffer *bytes.Buffer) *DNSMessage {
	//用bytes.Buffer类型来逐个字节读取后处理的优点就是不需要自己计算读取偏移值
	//这个对于Question和Answer这种第一段长度不固定的内容处理非常方便
	dnsMsg := &DNSMessage{
		Header: NewDNSHeader(buffer),
	}

	//FIXME: deal more then 1 QuestionCount
	dnsMsg.Questions = append(dnsMsg.Questions, NewDNSQuestion(buffer))

	//FIXME: deal more then 1 ResourceRecode
	if buffer.Len() > 0 {
		dnsMsg.ResourceRecodes = append(dnsMsg.ResourceRecodes, NewDNSResourceRecode(buffer))
	}

	return dnsMsg
}
客户端
package main

import (
	"bytes"
	"fmt"
	. "goNotes/dnsnotes/dnsKit"
	"net"
)

func main() {

	dnsMsg1 := DNSMessage{
		Header: &DNSHeader{
			ID:                  0x0010,
			QR:                  0,
			OperationCode:       0,
			AuthoritativeAnswer: 0,
			Truncation:          0,
			RecursionDesired:    1,
			RecursionAvailable:  0,
			Zero:                0,
			ResponseCode:        0,
			QuestionCount:       1,
			AnswerRRs:           0,
			AuthorityRRs:        0,
			AdditionalRRs:       0,
		},
		Questions: []*DNSQuestion{
			&DNSQuestion{
				QuestionName:  "www.test1.com",
				QuestionType:  1,
				QuestionClass: 1,
			},
		},
	}

	dnsMsg2 := DNSMessage{
		Header: &DNSHeader{
			ID:                  0x0010,
			QR:                  0,
			OperationCode:       0,
			AuthoritativeAnswer: 0,
			Truncation:          0,
			RecursionDesired:    1,
			RecursionAvailable:  0,
			Zero:                0,
			ResponseCode:        0,
			QuestionCount:       1,
			AnswerRRs:           0,
			AuthorityRRs:        0,
			AdditionalRRs:       0,
		},
		Questions: []*DNSQuestion{
			&DNSQuestion{
				QuestionName:  "www.test2.com",
				QuestionType:  1,
				QuestionClass: 1,
			},
		},
	}

	dnsServer := "127.0.0.1:11153"
	var conn net.Conn
	var err error
	if conn, err = net.Dial("udp", dnsServer); err != nil {
		fmt.Println(err.Error())
		return
	}
	defer conn.Close()

	if _, err := conn.Write(dnsMsg1.ToBytes()); err != nil {
		fmt.Println(err.Error())
		return
	}
	buf := make([]byte, 1024)

	if length, err := conn.Read(buf); err == nil {
		result := NewDNSMessage(bytes.NewBuffer(buf[0:length]))
		fmt.Println("query:", result.Questions[0].QuestionName, ", get ip:", result.ResourceRecodes[0].RData)
	} else {
		fmt.Println(err.Error())
	}

	if _, err = conn.Write(dnsMsg2.ToBytes()); err != nil {
		fmt.Println(err.Error())
		return
	}

	buf = make([]byte, 1024)

	if length, err := conn.Read(buf); err == nil {
		result := NewDNSMessage(bytes.NewBuffer(buf[0:length]))
		fmt.Println("query:", result.Questions[0].QuestionName, ", get ip:", result.ResourceRecodes[0].RData)
	} else {
		fmt.Println(err.Error())
	}

}
服务端

对域名返回的ip从10.0.0.1开始,新域名返回的ip加1

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	. "goNotes/dnsnotes/dnsKit"
	"net"
	"sync"
)

const RECV_BUF_LEN = 1024

var addr2IP Address2IP

type Address2IP struct {
	lastIP uint32 //167772160 -> 10.0.0.0
	sync.RWMutex
	address2ip map[string]uint32
}

func (a *Address2IP) getIP(address string) string {
	a.RLock()
	ip := uint32(0)
	ok := false
	if ip, ok = a.address2ip[address]; ok {
		a.RUnlock()
	} else {
		a.RUnlock()

		a.Lock()
		a.lastIP++
		a.address2ip[address] = a.lastIP
		ip = a.lastIP
		a.Unlock()

	}

	ipByte := []byte{0, 0, 0, 0}
	binary.BigEndian.PutUint32(ipByte, ip)
	return net.IPv4(ipByte[0], ipByte[1], ipByte[2], ipByte[3]).String()
}

func main() {
	addr2IP = Address2IP{
		lastIP:     167772160,
		address2ip: map[string]uint32{},
	}
	udpAddr, err := net.ResolveUDPAddr("udp", ":11153")
	if err != nil {
		panic("error listening:" + err.Error())
	}
	fmt.Println("Starting the server")

	conn, err := net.ListenUDP("udp", udpAddr)
	defer conn.Close()

	for {

		buf := make([]byte, RECV_BUF_LEN)
		n, raddr, err := conn.ReadFromUDP(buf)
		if err != nil {
			panic("Error accept:" + err.Error())
		}
		fmt.Println("Accepted the Connection :", conn.RemoteAddr())
		go echoServer(conn, raddr, buf[0:n])
	}
}

func echoServer(conn *net.UDPConn, raddr *net.UDPAddr, data []byte) {

	dnsMsg := NewDNSMessage(bytes.NewBuffer(data))

	dnsMsg.Header.QR = 1
	dnsMsg.Header.RecursionAvailable = 1
	dnsMsg.Header.AnswerRRs = 1

	dnsMsg.ResourceRecodes = append(
		dnsMsg.ResourceRecodes,
		&DNSResourceRecode{
			Name:     "",
			NamePos:  12,
			RRType:   1,
			Class:    1,
			TTL:      384,
			RDLength: 4,
			RData:    addr2IP.getIP(dnsMsg.Questions[0].QuestionName),
		})

	fmt.Println("query:", dnsMsg.Questions[0].QuestionName, ", ip:", dnsMsg.ResourceRecodes[0].RData)
	conn.WriteToUDP(dnsMsg.ToBytes(), raddr)

}

更多代码

golang通过反射使用json字符串调用struct的指定方法及返回json结果
golang根据配置的时间和时区计算定时任务是否到了刷新时间
golang利用模板生成数据库表对应的模型及操作函数
golang实时消息平台NSQ的使用
golang使用服务发现系统consul
golang实现基于redis和consul的可水平扩展的排行榜服务范例
DNS协议解析与DNS模拟服务器-基于golang实现