DNS协议及客户端实现

一:DNS报文

DNS有两种类型的报文,分别是查询报文响应报文

02081024_ybF1.jpg


其中,首部格式如下:

02081026_QJGP.jpg

各字段意义如下:

标识:16位;客户端每次查询使用不同的标志号。服务端在响应中重复这个标识号

标志:16位;意义如下

02081026_YeYX.jpg

QR1位;0表示查询报文,1表示响应报文
OpenCode4位;定义查询或响应的类型(0:标准; 1:反向,即通过ip地址查询域名相关信息;2:服务器状态请求)
AA1位;当值为1时,表示名字服务器是权限服务器
TC1位;当值为1时,表示响应已超过512字节并已截断为512字节
RD1位;当值为1时,表示客户端希望得到递归回答。响应报文中重复这个值。(递归回答:见第三部分)
RA1位;当值为1时,表示可得到递归响应。只能在响应报文中置位(值为1)
保留3位;保留字段,全部为0
rCode4位;表示在响应中的差错状态。只有权限服务器才能做出这个判断。(0:无差错;1:格式差错;2:问题在域名服务器上;3:域参照问题;4:查询类型不支持;5:在管理上被禁止;6~15:保留)

问题记录数:16位;问题部分的查询记录数

回答记录数:16位;回答记录数,在查询报文中值为0

授权记录数:16位;回答记录数,在查询报文中值为0

附加记录数:16位;回答记录数,在查询报文中值为0


问题记录格式如下:

02081026_aiEP.jpg

查询名字:域名的可变长字段;其中计数字段指明每一节中的字符数。

查询类型:16位;值的意义如下表

查询类别:16位;定义使用DNS的特定协议


资源记录格式如下:

02081026_Ip33.jpg

域名:16位;由于响应包含查询中完整的问题部分,为不重复记录域名;这里使用问题记录中域名的偏移量表示

域类型:16位;与问题记录的查询类型字段相同

域类别:16位;与问题记录的查询类别字段相同

生存时间:32位;在该时间内,接收方可以将次回答保存在告诉缓存中,单位为秒

资源数据长度:16位;

资源数据:可变长;值内容取决于类型字段的值,可以是数值、域名、偏移指针、字符串


例子:

该例子给出了客户端查询”cha1.fhda.edu“的ip地址的查询报文及相应报文。

查询报文:

02081027_FXV6.jpg

响应报文:

02081027_APqB.jpg

二:相关结构定义及关键代码

结构定义:

#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

转载于:https://my.oschina.net/simplefocus/blog/215310

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值