织梦CMS - 轻松建站从此开始!

罗索

C实现DNS

jackyhwei 发布于 2014-10-21 14:42 点击:次 
DNS(Domain Name System)域名系统提供了主机名和IP地址之间的转换。通常我们在应用程序中使用库函数gethostbyname()和gethostbyaddr()来完成两者之间的转换。但是为了更深入的学习网络底层知识,有必要从源代码级别来分析和实现
TAG: 实现DNS  

DNS(Domain Name System)域名系统提供了主机名和IP地址之间的转换。通常我们在应用程序中使用库函数gethostbyname()和gethostbyaddr()来完成两者之间的转换。但是为了更深入的学习网络底层知识,有必要从源代码级别来分析和实现

RFC 1034说明了DNS的概念和功能,RFC 1035详细说明了DNS的规范和实现。通过阅读RFC,我们知道明白了,应用程序对DNS的访问是通过解析器来(resolver)完成的,解析器并不像TCP/IP协议那样是OS的内核,而是通过网络访问DNS服务器来得到名字和地址的对应关系。OS的TCP/IP协议簇对DNS一点都知道。

工欲善其事必先利其器,先得进行些基础知识的复习:《bit与byte的区别》和 《bit与byte的联系》及《位运算》,一个int是4个byte(十六进制中01 02 03 04转化为十进制为16909060),一个char是1个byte(十六进制中97转化为字符为a)。例如在十六进制中0x80,用bit来表示就是1000 0000,此时如果我们对它实施位(>>5)运算,得到的结果就是0000 0100,十六进制值为0x04

在Linux的内核代码中,经常可以看见形如#define do{ }while(0)的宏定义,是否感到疑惑呢?宏定义只是帮助我们进行替换而已,当定义多条语句时,会在if...else...语句中产生歧义,详细解释参考链接。(小插曲,我在测试中只#include <stdlib.h>,忘记了#include <stdio.h>,然后在后面使用了printf等,结果编译的时候产生警告:
warning: implicit declaration of function ‘printf’
warning: incompatible implicit declaration of built-in function ‘printf’
经过查找才知道警告的原因是没有包含printf函数的明确定义,那么就隐式定义了。而编译时库里恰好有这个函数,虽然不会出错,但会给出警告。从代码习惯上讲,所有函数都应该被明确定义,切记)

一般的DNS是基于UDP,报文格式如下图:

C实现DNS - 满天星 - 满天星
前面是固定的12byte首部,后面是4个长度可变的字段。

从首部开始,0~15位bit刚好是2个byte,由客户程序设置并由服务器返回,客户程序通过它来确定响应是否与查询匹配(例如,客户程序在这里输入的是十六进制的0xD8B4,那么服务器的该字段也会填入相同的值。这个标识又称为Transaction ID,在《DNS欺骗技术原理与安全防范技术》中有更详细的讨论)。

接下来的16~31位bit刚好也是2个byte,用作协议的标志位
C实现DNS - 满天星 - 满天星500)this.width=500;" border=0>
  • QR是1个bit位:0代表查询报文,1代表相应报文
  • opcode是4个bit位字段:0代表标准查询,1代表反向查询,2代表服务器状态请求
  • AA是1个bit位,是Authoritative Answer的缩写,指明名字服务器是授权于该域的
  • TC是1个bit位,是Truncated的缩写,意为可截断的,指明在UDP中应答报文超过512字节时,只返回512字节
  • RD是1个bit位,是Recursion Desired的缩写,意为期望递归,期望名字服务器必须处理这个查询,而不是给出一个迭代查询服务器的列表
  • RA是1个bit位,是Recursion Available的缩写,意为可用递归,如果名字服务器支持递归查询,这会将此位设置为1
  • zero是3个bit位,设置为0
  • rcode是4个bit位,表示名字差错,0为无差错,3为有差错。当查询中指定的域不存在的时候,就返回3
现在通过抓包来加深对上面的理解:
C实现DNS - 满天星 - 满天星
通过上图我们可以,二进制格式就为0000 0001 0000 0000,16位bit两个byte,其十六进制值为0x0100,这是一个标准的DNS查询请求的标志位

再接着是4段16位bit:
  • QuestionCount 查询问题记录数由客户端填写,服务器端按原值返回
  • AnswerCount 资源记录数由服务器端填写,代表有多少适应这个问题记录的对应IP
  • NameServerCount 授权资源记录数,一般为0
  • AdditionalCount 额外资源记录数,一般为0
现在通过抓包来加深对上面的理解:
C实现DNS - 满天星 - 满天星500)this.width=500;" border=0>

分析完报文头,现在该是报文体了。分为四大块:
  • 查询问题
  • 回答
  • 授权
  • 额外信息
先看查询问题,它通常只有一个问题,当然也可以有多个问题,问题数由QuestionCount确定:
C实现DNS - 满天星 - 满天星
查询名是要查找的名字,它是一个或多个标识符的序列。每个标识符以首字节的计数值,来说明随后标识符的字节长度,每个名字以最后字节为0结束,长度为0的标识符是根标识符。计数字节的值必须是0~63的数,因为标识符的最大长度仅为63。该字段无需以整32bit边界结束,即无需填充字节。这种编码格式非常象BT协议中bencode编码,例如查询twistedmatrix.com:
C实现DNS - 满天星 - 满天星500)this.width=500;" border=0>
第一位为计数位,从首字母到第一个.号,一共是13位(twistedmatrix),然后是第二个计数位,值为3,由com计算得到,最后是结束符号0。
接着就是查询类型,该类型就是针对查询问题的,在RFC中有详细的描述,一般使用如下表: 
< --if gte mso > Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE MicrosoftInternetExplorer4 < endif-->< --if gte mso > < endif-->

类型

描述

A

1

IP地址

NS

2

名字服务器

MD

3

邮件目的的(已过时,请用MX)

MF

4

邮件中转站(已过时,请用MX)

CNAME

5

规范名词

SOA

6

 xxx

MB

7

邮箱记录名(实验性质)

MG

8

邮件组成员(实验性质)

MR

9

邮件更改后记录名(实验性质)

NULL

10

RR(实验性质)

WKS

11

众所皆知的服务描述

PTR

12

指针记录

HINFO

13

主机信息

MINFO

14

邮箱或者邮件列表信息

MX

15

邮件交换记录

TXT

16

文本字符串

最常用的查询类型为A,表示期望获得查询名对应的IP地址。最后的查询类,通常是1,指互联网地址

剩下的3个字段是:回答授权额外信息,均采用资源记录RR(Resource Record)格式,如下图:
C实现DNS - 满天星 - 满天星
域名是记录中资源数据对应的名字。它的格式和前面介绍的查询名字段格式相同。类型说明RR的类型码。它的值和前面介绍的查询类型值是一样的。类通常为1,指Internet数据。生存时间字段是客户程序保留该资源记录的秒数。资源记录通常的生存时间值为2天。资源数据长度说明资源数据的数量。该数据的格式依赖于类型字段的值。对于类型1(A记录)资源数据是4字节的IP地址。

=============================================================================
上文已提过,通常进行域名和IP地址的转换时,使用gethostbyname()和gethostbyaddr()(在W.Richard Stevens的《Unix Network Programming》第11章有详细的说明)。当仔细分析了DNS的协议后,我们自己动手来写写看。
下例执行的环境在Debian4.0上,编译工具为gcc,DNS服务器地址为192.168.1.1(通常该服务的默认监听端口为53),文件名为DNSClient.c:
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <error.h> 
  4. #include <string.h> 
  5. #include <sys/socket.h> 
  6. #include <sys/types.h> 
  7. #include <netinet/in.h> 
  8. #include <arpa/inet.h> 
  9. #include <time.h> 
  10.  
  11. static void printmessage(unsigned char *buf); 
  12. static unsigned char *printnamestring(unsigned char *p,unsigned char *buf); 
  13.  
  14. #define GETWORD(__w,__p) do{__w=*(__p++)<<8;__w|=*(p++);}while(0) 
  15. #define GETLONG(__l,__p) do{__l=*(__p++)<<24;__l|=*(__p++)<<16;__l|=*(__p++)<<8;__l|=*(p++);}while(0) 
  16.  
  17. int main(int argc,char* argv[]) 
  18.     if(argc != 2) 
  19.     { 
  20.        printf("usage: dnsclient <host_name>\n"); 
  21.        return -1; 
  22.     } 
  23.     time_t ident; 
  24.     int fd; 
  25.     int rc; 
  26.     int serveraddrlent; 
  27.     char *q; 
  28.     unsigned char *p; 
  29.     unsigned char *countp; 
  30.     unsigned char reqBuf[512] = {0}; 
  31.     unsigned char rplBuf[512] = {0}; 
  32.     struct sockaddr_in serveraddr; 
  33.   
  34.     //udp 
  35.     fd = socket(AF_INET, SOCK_DGRAM, 0); 
  36.     if(fd == -1) 
  37.     { 
  38.        perror("error create udp socket"); 
  39.        return -1; 
  40.     } 
  41.     
  42.     time(&ident); 
  43.     //copy 
  44.     p = reqBuf; 
  45.     //Transaction ID 
  46.     *(p++) = ident; 
  47.     *(p++) = ident>>8; 
  48.     //Header section 
  49.     //flag word = 0x1000 
  50.     *(p++) = 0x01; 
  51.     *(p++) = 0x00; 
  52.     //Questions = 0x0001 
  53.     //just one query 
  54.     *(p++) = 0x00; 
  55.     *(p++) = 0x01; 
  56.     //Answer RRs = 0x0000 
  57.     //no answers in this message 
  58.     *(p++) = 0x00; 
  59.     *(p++) = 0x00; 
  60.     //Authority RRs = 0x0000 
  61.     *(p++) = 0x00; 
  62.     *(p++) = 0x00; 
  63.     //Additional RRs = 0x0000 
  64.     *(p++) = 0x00; 
  65.     *(p++) = 0x00; 
  66.     //Query section 
  67.     countp = p;   
  68.     *(p++) = 0; 
  69.     for(q=argv[1]; *q!=0; q++) 
  70.     { 
  71.        if(*q != '.'
  72.        { 
  73.            (*countp)++; 
  74.            *(p++) = *q; 
  75.        } 
  76.        else if(*countp != 0) 
  77.        { 
  78.            countp = p; 
  79.            *(p++) = 0; 
  80.        } 
  81.     } 
  82.     if(*countp != 0) 
  83.        *(p++) = 0; 
  84.  
  85.     //Type=1(A):host address 
  86.     *(p++)=0; 
  87.     *(p++)=1; 
  88.     //Class=1(IN):internet 
  89.     *(p++)=0; 
  90.     *(p++)=1; 
  91.   
  92.     printf("\nRequest:\n"); 
  93.     printmessage(reqBuf); 
  94.  
  95.    //fill 
  96.     bzero(&serveraddr, sizeof(serveraddr)); 
  97.     serveraddr.sin_family = AF_INET; 
  98.     serveraddr.sin_port = htons(53); 
  99.     serveraddr.sin_addr.s_addr = inet_addr("192.168.1.1"); 
  100.   
  101.     //send to DNS Serv 
  102.     if(sendto(fd,reqBuf,p-reqBuf,0,(void *)&serveraddr,sizeof(serveraddr)) < 0) 
  103.     { 
  104.        perror("error sending request"); 
  105.        return -1; 
  106.     } 
  107.   
  108.     //recev the reply 
  109.     bzero(&serveraddr,sizeof(serveraddr)); 
  110.     serveraddrlent = sizeof(serveraddr); 
  111.     rc = recvfrom(fd,&rplBuf,sizeof(rplBuf),0,(void *)&serveraddr,&serveraddrlent); 
  112.     if(rc < 0) 
  113.     { 
  114.        perror("error receiving request\n"); 
  115.        return -1; 
  116.     }   
  117.  
  118.     //print out results 
  119.     printf("\nReply:\n"); 
  120.     printmessage(rplBuf); 
  121.   
  122.     //exit 
  123.     printf("Program Exit\n"); 
  124.     return 0;  
  125. static void printmessage(unsigned char *buf) 
  126.     unsigned char *p; 
  127.     unsigned int ident,flags,qdcount,ancount,nscount,arcount; 
  128.     unsigned int i,j,type,class,ttl,rdlength; 
  129.   
  130.     p = buf; 
  131.     GETWORD(ident,p); 
  132.     printf("ident=%#x\n",ident); 
  133.   
  134.     GETWORD(flags,p); 
  135.     printf("flags=%#x\n",flags); 
  136.     //printf("qr=%u\n",(flags>>15)&1); 
  137.     printf("qr=%u\n",flags>>15); 
  138.   
  139.     printf("opcode=%u\n",(flags>>11)&15); 
  140.     printf("aa=%u\n",(flags>>10)&1); 
  141.     printf("tc=%u\n",(flags>>9)&1); 
  142.     printf("rd=%u\n",(flags>>8)&1); 
  143.     printf("ra=%u\n",(flags>>7)&1); 
  144.     printf("z=%u\n",(flags>>4)&7); 
  145.     printf("rcode=%u\n",flags&15);  
  146.   
  147.     GETWORD(qdcount,p); 
  148.     printf("qdcount=%u\n",qdcount); 
  149.  
  150.     GETWORD(ancount,p); 
  151.     printf("ancount=%u\n",ancount); 
  152.  
  153.     GETWORD(nscount,p); 
  154.     printf("nscount=%u\n",nscount); 
  155.   
  156.     GETWORD(arcount,p); 
  157.     printf("arcount=%u\n",arcount); 
  158.   
  159.     for(i=0; i<qdcount; i++) 
  160.     { 
  161.        printf("qd[%u]:\n",i); 
  162.        while(*p!=0) 
  163.        { 
  164.            p = printnamestring(p,buf); 
  165.            if(*p != 0) 
  166.               printf("."); 
  167.        } 
  168.        p++; 
  169.        printf("\n"); 
  170.        GETWORD(type,p); 
  171.        printf("type=%u\n",type); 
  172.        GETWORD(class,p); 
  173.        printf("class=%u\n",class); 
  174.     } 
  175.   
  176.     for(i=0; i<ancount; i++) 
  177.     { 
  178.        printf("an[%u]:\n",i); 
  179.        p = printnamestring(p,buf); 
  180.        printf("\n"); 
  181.        GETWORD(type,p); 
  182.        printf("type=%u\n",type); 
  183.        GETWORD(class,p); 
  184.        printf("class=%u\n",class); 
  185.        GETLONG(ttl,p); 
  186.        printf("ttl=%u\n",ttl); 
  187.        GETWORD(rdlength,p); 
  188.        printf("rdlength=%u\n",rdlength); 
  189.        printf("rd="); 
  190.        for(j=0; j<rdlength; j++) 
  191.        { 
  192.            printf("%2.2x(%u)",*p,*p); 
  193.            p++; 
  194.        } 
  195.        printf("\n"); 
  196.     } 
  197.   
  198. static unsigned char *printnamestring(unsigned char *p,unsigned char *buf) 
  199.     unsigned int nchars,offset; 
  200.  
  201.     nchars = *(p++); 
  202.     if((nchars & 0xc0) == 0xc0) 
  203.     { 
  204.        offset = (nchars & 0x3f) << 8; 
  205.        offset |= *(p++); 
  206.        nchars = buf[offset++]; 
  207.        printf("%*.*s",nchars,nchars,buf+offset); 
  208.     } 
  209.     else 
  210.     { 
  211.        printf("%*.*s",nchars,nchars,p); 
  212.        p += nchars; 
  213.     } 
  214.   
  215.     return (p); 

ddd

编译命令为
lsj@debian007:~$ gcc -g -Wall -o DNSClient DNSClient.c
然后执行:
lsj@debian007:~$ ./DNSClient rg4.net
就可以看见结果啦,这里使用的是UDP的数据格式,我们知道UDP的头部有一个16bit的长度,那么能表示的最大长度为2的16次方65536,再减去包头20,所以UDP包最大长度为65536-20=65516,但是在实际应用中,最好不要超过1K

(秩名)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201410/17101.html]
本文出处:ChinaUnix 作者:秩名 原文
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
相关文章
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容