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