DNS有两种类型的报文,分别是查询报文和响应报文
其中,首部格式如下:
各字段意义如下:
标识:16位;客户端每次查询使用不同的标志号。服务端在响应中重复这个标识号
标志:16位;意义如下
QR | 1位;0表示查询报文,1表示响应报文 |
OpenCode | 4位;定义查询或响应的类型(0:标准; 1:反向,即通过ip地址查询域名相关信息;2:服务器状态请求) |
AA | 1位;当值为1时,表示名字服务器是权限服务器 |
TC | 1位;当值为1时,表示响应已超过512字节并已截断为512字节 |
RD | 1位;当值为1时,表示客户端希望得到递归回答。响应报文中重复这个值。(递归回答:见第三部分) |
RA | 1位;当值为1时,表示可得到递归响应。只能在响应报文中置位(值为1) |
保留 | 3位;保留字段,全部为0 |
rCode | 4位;表示在响应中的差错状态。只有权限服务器才能做出这个判断。(0:无差错;1:格式差错;2:问题在域名服务器上;3:域参照问题;4:查询类型不支持;5:在管理上被禁止;6~15:保留) |
问题记录数:16位;问题部分的查询记录数
回答记录数:16位;回答记录数,在查询报文中值为0
授权记录数:16位;回答记录数,在查询报文中值为0
附加记录数:16位;回答记录数,在查询报文中值为0
问题记录格式如下:
查询名字:域名的可变长字段;其中计数字段指明每一节中的字符数。
查询类型:16位;值的意义如下表
查询类别:16位;定义使用DNS的特定协议
资源记录格式如下:
域名:16位;由于响应包含查询中完整的问题部分,为不重复记录域名;这里使用问题记录中域名的偏移量表示
域类型:16位;与问题记录的查询类型字段相同
域类别:16位;与问题记录的查询类别字段相同
生存时间:32位;在该时间内,接收方可以将次回答保存在告诉缓存中,单位为秒
资源数据长度:16位;
资源数据:可变长;值内容取决于类型字段的值,可以是数值、域名、偏移指针、字符串
例子:
该例子给出了客户端查询”cha1.fhda.edu“的ip地址的查询报文及相应报文。
查询报文:
响应报文:
结构定义:
#pragma pack(1) ///< DNS首部标志 typedef struct _DNS_HEADER_FLAG { uint8_t uQR : 1; ///< 报文类型, 0:查询报文, 1:响应报文 uint8_t uOpCode : 4; ///< 查询或响应类型, 0:标准的, 1:反响的, 2:服务器状态请求 uint8_t uAA : 1; ///< 值为1表示服务器是权限服务器 uint8_t uTC : 1; ///< 值为1时,表示响应已超过512字节并已截断为512字节 uint8_t uRD : 1; ///< 值为1时,表示客户希望得到递归回答;在查询报文中置位,在响应报文中重复置位 uint8_t uRA : 1; ///< 值为1时,表示可得到递归响应;只能在响应报文中置位 uint8_t uReserve : 3; ///< 保留位,目前全置0 uint8_t uRCode : 4; ///< 表示响应中的差错状态 } DNS_HEADER_FLAG; ///< DNS协议首部 typedef struct _DNS_HEADER { uint16_t uTicket; ///< 用户关联客户端查询和服务端响应的标识 DNS_HEADER_FLAG flag; ///< 标志 uint16_t uQuestionRecordNum; ///< 问题记录数 uint16_t uResRecordNum; ///< 回答记录数,在查询报文中为0 uint16_t uWarrantNum; ///< 授权记录数,在查询报文中为0 uint16_t uAdditionNum; ///< 附加记录数,在查询报文中为0 } DNS_HEADER; ///< 问题记录 typedef struct _DNS_QUESTION_RECORD { std::string strDomain; ///< 格式化后的域名 uint16_t uType; ///< 查询类型 uint16_t uCategory; ///< 查询类别 } DNS_QUESTION_RECORD; typedef struct _DNS_QUESTION_RECORD_WITH_TICKET { uint16_t uTicket; DNS_QUESTION_RECORD dnsQuestionRecord; } DNS_QUESTION_RECORD_WITH_TICKET; ///< DNS资源记录 typedef struct _DNS_RESOURCE_RECORD { uint16_t uDomainNameOffset; ///< 域名偏移量 uint16_t uDomainNameType; ///< 域类型 uint16_t uDomainNameClass; ///< 域类别 uint32_t uTimeToLive; ///< 生存时间 uint16_t uResourceDataSize; ///< 资源数据长度 uint8_t uData[1]; ///< 资源数据 } DNS_RESOURCE_RECORD; #pragma pack() ///< DNS查询类型 enum enumDNS_QUERY_TYPE { enumDNS_QTYPE_A = 1, ///< 32位的IPv6地址 enumDNS_QTYPE_NS = 2, ///< 名字服务器 enumDNS_QTYPE_CNAME = 5, ///< 规范名称 enumDNS_QTYPE_SOA = 6, ///< 授权开始 enumDNS_QTYPE_WKS = 11, ///< 熟知服务 enumDNS_QTYPE_PTR = 12, ///< 指针 enumDNS_QTYPE_HINFO = 13, ///< 主机信息 enumDNS_QTYPE_MX = 15, ///< 邮件交换 enumDNS_QTYPE_AAAA = 28, ///< IPv6地址 enumDNS_QTYPE_AXFR = 252, ///< 请求传送完整区文件 enumDNS_QTYPE_ANY = 255 ///< 请求所有记录 }; ///< DNS查询类别 enum enumDNS_QUERY_CATEGORY { enumDNS_QCATEGORY_AN = 1, ///< 因为特 enumDNS_QCATEGORY_CSNET = 2, ///< CSNET网络 enumDNS_QCATEGORY_CS = 3, ///< COAS网络 enumDNS_QCATEGORY_HS = 4 ///< 由MIT开发的Hesoid服务器 };
格式化域名函数:将域名(如www.z.cn格式化为dns协议要求的格式(3'w''w''w'1'z'2'c''n'0)
HRESULT _FormatDomainName(const std::string& strDomainName, std::string& strFormatedDomainName) { HRESULT hRetCode = E_FAIL; do { strFormatedDomainName.clear(); CHECK_ASSERT_BREAK( strDomainName.empty() ); uint8_t uSectionNum = 0; std::string strTmp; for ( size_t i = 0; i <= strDomainName.size(); ++i ) { if ( '.' == strDomainName[i] || i == strDomainName.size() ) { strFormatedDomainName.append((char*)&uSectionNum, sizeof(uint8_t)); strFormatedDomainName += strTmp; strTmp.clear(); uSectionNum = 0; if ( i == strDomainName.size() ) { strFormatedDomainName.append((char*)&uSectionNum, sizeof(uSectionNum)); } } else { char chTmp = strDomainName[i]; strTmp.append(&chTmp, 1); ++uSectionNum; } } hRetCode = S_OK; } while ( FALSE ); return hRetCode; }
其他一些操作的代码就不贴出来了,相信有一定编程经验的同学应该能写出来
DNS协议基于什么协议?
DNS可以使用UDP和TCP;DNS协议要求客户端先使用UDP进行查询,若响应数据超过512字节,则可再次使用TCP进行查询得到完整响应(我曾尝试首次就用TCP查询,结果是部分服务器不响应TCP三次握手,部分建立TCP连接成功但不响应DNS查询)
DNS服务器使用的端口号为53
虽然DNS协议支持一次查询多个域名,但大部分的DNS服务器的实现都不支持querycout>1的情况,会返回Format error错误;想要一次查询多个,需要使用DNS扩展协议EDNS