本文将详细讲解如何用go语言一步一步实现dns域名解析的过程,并简单介绍点dns有关的知识,直接开始正题吧。

  首先我们要了解dns解析的过程,没有了解的请看这里DNS入门(转)很详细。扫盲结束后,我们需要了解下dns报文格式,知道了报文的格式是怎样的,才可以写代码构造dns请求包:

  dns请求和应答都是用相同的报文格式,分成5个段(有的报文段在不同的情况下可能为空),如下:

  

  Header段是报文的头部,它定义了报文是请求还是应答,也定义了其他段是否需要存在,以及是标准查询还是其他。

  Header包含如下字段:

  

  各字段分别解释如下:

  ID:请求客户端设置的16位标示,服务器给出应答的时候会带相同的标示字段回来,这样请求客户端就可以区分不同的请求应答了。

  QR:1个比特位用来区分是请求(0)还是应答(1)。

  OPCODE:4个比特位用来设置查询的种类,应答的时候会带相同值,可用的值如下: 0 标准查询 (QUERY) 1 反向查询 (IQUERY) 2 服务器状态查询 (STATUS) 3-15保留值,暂时未使用

  AA:授权应答(Authoritative Answer) - 这个比特位在应答的时候才有意义,指出给出应答的服务器是查询域名的授权解析服务器。注意因为别名的存在,应答可能存在多个主域名,这个AA位对应请求名,或者应答中的第一个主域名。

  TC:截断(TrunCation) - 用来指出报文比允许的长度还要长,导致被截断。

  RD:期望递归(Recursion Desired) - 这个比特位被请求设置,应答的时候使用的相同的值返回。如果设置了RD,就建议域名服务器进行递归解析,递归查询的支持是可选的。

  RA:支持递归(Recursion Available) - 这个比特位在应答中设置或取消,用来代表服务器是否支持递归查询。

  Z:保留值,暂时未使用。在所有的请求和应答报文中必须置为0。

  RCODE:应答码(Response code) - 这4个比特位在应答报文中设置,代表的含义如下:

    0 没有错误。

    1 报文格式错误(Format error) - 服务器不能理解请求的报文。

    2 服务器失败(Server failure) - 因为服务器的原因导致没办法处理这个请求。

    3 名字错误(Name Error) - 只有对授权域名解析服务器有意义,指出解析的域名不存在。

    4 没有实现(Not Implemented) - 域名服务器不支持查询类型。

    5 拒绝(Refused) - 服务器由于设置的策略拒绝给出应答。比如,服务器不希望对某些请求者给出应答,或者服务器不希望进行某些操作(比如区域传送zone transfer)。

    6-15 保留值,暂时未使用。

  QDCOUNT 无符号16位整数表示报文请求段中的问题记录数。

  ANCOUNT 无符号16位整数表示报文回答段中的回答记录数。

  NSCOUNT 无符号16位整数表示报文授权段中的授权记录数。

  ARCOUNT 无符号16位整数表示报文附加段中的附加记录数。

  根据这些,dns头部的数据结构可以定义如下:

  type dnsHeader struct {

   Id                                 uint16

    Bits                               uint16

     Qdcount, Ancount, Nscount, Arcount uint16

  }

  构造头部信息我们主要处理Bits,可以直接根据需求对相应位置值,也可以定义好每一个字段,通过移位的方式然后相加构造请求的头部各个字段。推荐后一种方法,这样就有:

  header.Bits = QR<<15 + OperationCode<<11 + AuthoritativeAnswer<<10 + Truncation<<9 + RecursionDesired<<8 + RecursionAvailable<<7 + ResponseCode

其他的头部信息就比较简单了:

  requestHeader := dnsHeader{

Id:      0x0010,

Qdcount: 1,

Ancount: 0,

Nscount: 0,

Arcount: 0,

  }

  报文头搞定后,接下来就是查询问题Question:

  Question段描述了查询的问题,包括查询类型(QTYPE),查询类(QCLASS),以及查询的域名(QNAME)。字段含义如下   QNAME:域名被编码为一些labels序列,每个labels包含一个字节表示后续字符串长度,以及这个字符串,以0长度和空字符串来表示域名结束。注意这个字段可能为奇数字节,不需要进行边界填充对齐。   QTYPE:2个字节表示查询类型,.取值可以为任何可用的类型值,以及通配码来表示所有的资源记录。   QCLASS:2个字节表示查询的协议类,比如,IN代表Internet。所以我们直接定义查询的结构体如下:

  type dnsQuery struct {

  QuestionType  uint16

  QuestionClass uint16

  }

查询的域名不定义在查询的结构体中,由函数接收参数的方式接收。

  剩下的3个段包含相同的格式:一系列可能为空的资源记录(RRs)。Answer段包含回答问题的RRs;授权段包含授权域名服务器的RRs;附加段包含和请求相关的,但是不是必须回答的RRs。而在发送请求的时候,我们是发起请求方,所以这些字段放空就好。

完整代码:

// 002 project main.go
package main import (
"bytes"
"encoding/binary"
"fmt"
"net"
"strings"
"time"
) type dnsHeader struct {
Id uint16
Bits uint16
Qdcount, Ancount, Nscount, Arcount uint16
} func (header *dnsHeader) SetFlag(QR uint16, OperationCode uint16, AuthoritativeAnswer uint16, Truncation uint16, RecursionDesired uint16, RecursionAvailable uint16, ResponseCode uint16) {
header.Bits = QR<< + OperationCode<< + AuthoritativeAnswer<< + Truncation<< + RecursionDesired<< + RecursionAvailable<< + ResponseCode
} type dnsQuery struct {
QuestionType uint16
QuestionClass uint16
} func ParseDomainName(domain string) []byte {
var (
buffer bytes.Buffer
segments []string = strings.Split(domain, ".")
)
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)) return buffer.Bytes()
}
func Send(dnsServer, domain string) ([]byte, int, time.Duration) {
requestHeader := dnsHeader{
Id: 0x0010,
Qdcount: ,
Ancount: ,
Nscount: ,
Arcount: ,
}
requestHeader.SetFlag(, , , , , , ) requestQuery := dnsQuery{
QuestionType: ,
QuestionClass: ,
} var (
conn net.Conn
err error
buffer bytes.Buffer
) if conn, err = net.Dial("udp", dnsServer); err != nil {
fmt.Println(err.Error())
return make([]byte, ), ,
}
defer conn.Close() binary.Write(&buffer, binary.BigEndian, requestHeader)
binary.Write(&buffer, binary.BigEndian, ParseDomainName(domain))
binary.Write(&buffer, binary.BigEndian, requestQuery) buf := make([]byte, )
t1 := time.Now()
if _, err := conn.Write(buffer.Bytes()); err != nil {
fmt.Println(err.Error())
return make([]byte, ), ,
}
length, err := conn.Read(buf)
t := time.Now().Sub(t1)
return buf, length, t
}
func main() {
remsg, n, _ := Send("114.114.114.114:53", "www.baidu.com")
fmt.Println(remsg, n)
}

抓个包看看:

这是发出去的,看看详细的Questions信息:

我们设置的请求类型是1,class是1,意味着是请求A记录,class IN。下一节我们在来讨论下如何处理服务器端响应的内容。

golang实现dns域名解析(一)的更多相关文章

  1. golang实现dns域名解析(三):响应报文分析

    前面说了构造请求发送报文,接下来我们好好研究下如何解析服务器端发回来的应答信息. 首先还是用前面的程序代码发一个请求,用抓包工具看看应答的内容有哪些: 截图的第一部分是返回信息的统计,表明这个返回的包 ...

  2. golang实现dns域名解析(二)

    上一节已经讲了如何构造dns请求包的情况,这一节接着上一节的情况,谈谈dns查询报文中的问题部分.问题部分中每个问题的格式如下: 查询名是要查找的名字,它是一个或者多个标识符的序列.每个标识符以首字母 ...

  3. C++实现DNS域名解析

    一.概述 现在来搞定DNS域名解析,其实这是前面一篇文章C++实现Ping里面的遗留问题,要干的活是ping的过程中画红线的部分: cmd下域名解析的命令是nslookup,比如“nslookup w ...

  4. DNS域名解析过程

    图1-10是DNS域名解析的主要请求过程实例图. 如图1-10所示,当一个用户在浏览器中输入www.abc.com时,DNS解析将会有将近10个步骤,这个过程大体描述如下.当用户在浏览器中输入域名并按 ...

  5. 配置DNS域名解析服务器

    bind这个DNS域名解析服务器解析好后,执行下面的语句实现开启服务 named -c named.conf & -c指配置脚本named.conf的文件地址 named.conf主要有下面几 ...

  6. 域名下Web项目重定向出现DNS域名解析错误问题

    问题: 项目使用的是阿里云的ESC,前几天为IP地址添加了域名 发现发送正常请求时跳转没问题,但发送重定向请求时,页面就会出现DNS域名解析错误的情况 1.我在Tomcat的server.xml中配置 ...

  7. 每天进步一点点——负载均衡之DNS域名解析

    转载请说明出处:http://blog.csdn.net/cywosp/article/details/38017027     在上一篇文章(http://blog.csdn.net/cywosp/ ...

  8. 一张图看懂DNS域名解析全过程

    DNS域名解析是互联网上非常重要的一项服务,上网冲浪(还有人在用这个词吗?)伴随着大量DNS服务来支撑,而对于网站运营来说,DNS域名解析的稳定可靠,意味着更多用户的喜欢,更好的SEO效果和更大的访问 ...

  9. DNS域名解析过程,域名的认识

    DNS域名解析过程 参考知乎:https://www.zhihu.com/question/23042131 当你通过浏览器输入url访问资源时,会请求DNS解析域名成对应的IP地址,由IP地址在去与 ...

随机推荐

  1. exp分析

    1 from pwn import* 2 3 local =1 4 debug = 1 5 6 if local: 7 p = process('./pwn1') 8 else: 9 p = remo ...

  2. LeetCode(117) Populating Next Right Pointers in Each Node II

    题目 Follow up for problem "Populating Next Right Pointers in Each Node". What if the given ...

  3. 《鸟哥的Linux私房菜》学习笔记(3)——根文件系统

    一.Linux目录结构 rootfs:根文件系统,根是"/". 1./boot 系统启动相关的文件,如内核.intrd.以及grub(bootloader) root@hao:~# ...

  4. 如何用treap写luogu P3391

    treap大法好!!! splay什么的都是异端 --XZZ 先%FHQ为敬 (fhq)treap也是可以搞区间翻转的 每次把它成(1~L-1)(L~R)(R+1~n)三块然后打标记拼回去 对于有标记 ...

  5. 大数据学习——spark运营案例

    iplocation需求 在互联网中,我们经常会见到城市热点图这样的报表数据,例如在百度统计中,会统计今年的热门旅游城市.热门报考学校等,会将这样的信息显示在热点图中. 因此,我们需要通过日志信息(运 ...

  6. python学习-- 理解'*','*args','**','**kwargs'

    刚开始学习Python的时候,对有关args,kwargs,和*的使用感到很困惑.相信对此感到疑惑的人也有很多.我打算通过这个帖子来排解这个疑惑(希望能减少疑惑). 让我们通过以下5步来理解: 1.  ...

  7. tensorflow——MNIST机器学习入门

    将这里的代码在项目中执行下载并安装数据集. 执行下面代码,训练.并评估模型: # _*_coding:utf-8_*_ import inputdata mnist = inputdata.read_ ...

  8. 2017"百度之星"程序设计大赛 - 资格赛

    度度熊与邪恶大魔王  Accepts: 3666  Submissions: 22474  Time Limit: 2000/1000 MS (Java/Others)  Memory Limit: ...

  9. jQuery 样式操作、文档操作、属性操作的方法总结

    文档操作: addClass()             向匹配的元素添加指定的类名.after()                    在匹配的元素之后插入内容.append()         ...

  10. 通过FEDERATED存储引擎同步两实例间的表数据

    需求情景:实例1中A库中的三个视图是实例2中的B库所依赖的,B需要A库中三个视图的实时数据. 方案:通过FEDERATED来完成跨势力的查询FEDERATED存储引擎表只会创建表结构,不会存储表数据, ...