本文将详细讲解如何用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. CentOS 7 配置OpenCL环境(安装NVIDIA cuda sdk、Cmake、Eclipse CDT)

    序 最近需要在Linux下进行一个OpenCL开发的项目,现将开发环境的配置过程记录如下,方便查阅. 完整的环境配置需要以下几个部分: 安装一个OpenCL实现,基于硬件,选择NVIDIA CUDA ...

  2. nslookup、dig命令Linux安装包

    linux下提供nslookup,dig命令的软件就是 bind-utils yum install bind-utils -y

  3. poj 2385 树上掉苹果问题 dp算法

    题意:有树1 树2 会掉苹果,奶牛去捡,只能移动w次,开始的时候在树1 问最多可以捡多少个苹果? 思路: dp[i][j]表示i分钟移动j次捡到苹果的最大值 实例分析 0,1  1,2...说明 偶数 ...

  4. cs229课程索引

    重要说明 这个系列是以cs229为参考,梳理下来的有关机器学习传统算法的一些东西.所以说cs229的有些内容我会暂时先去掉放在别的部分里面,也会加上很多重要的,但是cs229没有讲到的东西.而且本系列 ...

  5. J​a​y​r​o​c​k​.​J​s​o​n​读​取​j​s​o​n​数​据​(​n​e​t​)

    1 : 首 先 下 载 Jayrock.Json.dll 文 件 , 放 入 bin 目 录 中 : 地 址 : http://www.filediag.com/down/Jayrock.Json.d ...

  6. 如何理解redo和undo的作用

    目录 如何理解redo和undo的作用 redo undo UNDO和REDO的区别 如何理解redo和undo的作用 redo 重做日志(redo)包含所有数据产生的历史改变记录,是oracle在线 ...

  7. 03_HibernateSessionFactory源码分析

    文章导读: 讲解了一个线程为什么要使用同一个connection, 我们分析了HiberatenSessionFactory的实现机制, 然后根据Hibernate的写法重构了我们的代码. 最后测试可 ...

  8. selenium - 常用等待操作

    # 4. 等待操作 # 强制等待from time import sleepsleep(10) # 隐性等待# 设置最长等待时间,在这个时间在只要有个时间点加载完成,则执行下一步代码,比sleep智能 ...

  9. mac 命令行下连接到MySQL mysql: command not found

    mac下刚刚安装完MySQL后使用命令连接到MySQL mysql -uroot -p 提示:  -bash: mysql: command not found使用  /usr/local/mysql ...

  10. ccna 闫辉单臂路由 和 acl access control list

    ccna 闫辉单臂路由 和  acl   access control list 一单臂路由     当前园区网设计很少用到       成本低  小型的.局域网可用         二ACL acc ...