说明了DNS的概念和功能,详细说明了DNS的规范和实现。通过阅读RFC,我们知道明白了,应用程序对DNS的访问是通过解析器来(resolver)完成的,解析器并不像TCP/IP协议那样是OS的内核,而是通过网络访问DNS服务器来得到名字和地址的对应关系。OS的TCP/IP协议簇对DNS一点都知道。
工欲善其事必先利其器,先得进行些基础知识的复习:和 《及《,一个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
warning: implicit declaration of function ‘printf’ warning: incompatible implicit declaration of built-in function ‘printf’ |
一般的DNS是基于UDP,报文格式如下图:

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

- 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

再接着是4段16位bit:
- QuestionCount 查询问题记录数由客户端填写,服务器端按原值返回
- AnswerCount 资源记录数由服务器端填写,代表有多少适应这个问题记录的对应IP
- NameServerCount 授权资源记录数,一般为0
- AdditionalCount 额外资源记录数,一般为0

分析完报文头,现在该是报文体了。分为四大块:
- 查询问题
- 回答
- 授权
- 额外信息


接着就是查询类型,该类型就是针对查询问题的,在RFC中有详细的描述,一般使用如下表:
类型 |
值 |
描述 |
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 |
文本字符串 |
剩下的3个字段是:回答,授权和额外信息,均采用资源记录RR(Resource Record)格式,如下图:

=============================================================================
下例执行的环境在Debian4.0上,编译工具为gcc,DNS服务器地址为192.168.1.1(通常该服务的默认监听端口为53),文件名为DNSClient.c:
#include #include #include #include #include #include #include #include #include
static void printmessage(unsigned char *buf); static unsigned char *printnamestring(unsigned char *p,unsigned char *buf);
#define GETWORD(__w,__p) do{__w=*(__p++)<<8;__w|=*(p++);}while(0) #define GETLONG(__l,__p) do{__l=*(__p++)<<24;__l|=*(__p++)<<16;__l|=*(__p++)<<8;__l|=*(p++);}while(0)
int main(int argc,char* argv[]) { if(argc != 2) { printf("usage: dnsclient return -1; }
time_t ident; int fd; int rc; int serveraddrlent; char *q; unsigned char *p; unsigned char *countp; unsigned char reqBuf[512] = {0}; unsigned char rplBuf[512] = {0}; struct sockaddr_in serveraddr;
//udp fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd == -1) { perror("error create udp socket"); return -1; }
time(&ident); //copy p = reqBuf; //Transaction ID *(p++) = ident; *(p++) = ident>>8; //Header section //flag word = 0x0100 *(p++) = 0x01; *(p++) = 0x00; //Questions = 0x0001 //just one query *(p++) = 0x00; *(p++) = 0x01; //Answer RRs = 0x0000 //no answers in this message *(p++) = 0x00; *(p++) = 0x00; //Authority RRs = 0x0000 *(p++) = 0x00; *(p++) = 0x00; //Additional RRs = 0x0000 *(p++) = 0x00; *(p++) = 0x00; //Query section countp = p; *(p++) = 0; for(q=argv[1]; *q!=0; q++) { if(*q != '.') { (*countp)++; *(p++) = *q; } else if(*countp != 0) { countp = p; *(p++) = 0; } } if(*countp != 0) *(p++) = 0;
//Type=1(A):host address *(p++)=0; *(p++)=1; //Class=1(IN):internet *(p++)=0; *(p++)=1;
printf("\nRequest:\n"); printmessage(reqBuf);
//fill bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(53); serveraddr.sin_addr.s_addr = inet_addr("192.168.1.1");
//send to DNS Serv if(sendto(fd,reqBuf,p-reqBuf,0,(void *)&serveraddr,sizeof(serveraddr)) < 0) { perror("error sending request"); return -1; }
//recev the reply bzero(&serveraddr,sizeof(serveraddr)); serveraddrlent = sizeof(serveraddr); rc = recvfrom(fd,&rplBuf,sizeof(rplBuf),0,(void *)&serveraddr,&serveraddrlent); if(rc < 0) { perror("error receiving request\n"); return -1; }
//print out results printf("\nReply:\n"); printmessage(rplBuf);
//exit printf("Program Exit\n"); return 0; }
static void printmessage(unsigned char *buf) { unsigned char *p; unsigned int ident,flags,qdcount,ancount,nscount,arcount; unsigned int i,j,type,class,ttl,rdlength;
p = buf; GETWORD(ident,p); printf("ident=%#x\n",ident);
GETWORD(flags,p); printf("flags=%#x\n",flags); //printf("qr=%u\n",(flags>>15)&1); printf("qr=%u\n",flags>>15);
printf("opcode=%u\n",(flags>>11)&15); printf("aa=%u\n",(flags>>10)&1); printf("tc=%u\n",(flags>>9)&1); printf("rd=%u\n",(flags>>8)&1); printf("ra=%u\n",(flags>>7)&1); printf("z=%u\n",(flags>>4)&7); printf("rcode=%u\n",flags&15);
GETWORD(qdcount,p); printf("qdcount=%u\n",qdcount);
GETWORD(ancount,p); printf("ancount=%u\n",ancount);
GETWORD(nscount,p); printf("nscount=%u\n",nscount);
GETWORD(arcount,p); printf("arcount=%u\n",arcount);
for(i=0; i { printf("qd[%u]:\n",i); while(*p!=0) { p = printnamestring(p,buf); if(*p != 0) printf("."); } p++; printf("\n"); GETWORD(type,p); printf("type=%u\n",type); GETWORD(class,p); printf("class=%u\n",class); }
for(i=0; i { printf("an[%u]:\n",i); p = printnamestring(p,buf); printf("\n"); GETWORD(type,p); printf("type=%u\n",type); GETWORD(class,p); printf("class=%u\n",class); GETLONG(ttl,p); printf("ttl=%u\n",ttl); GETWORD(rdlength,p); printf("rdlength=%u\n",rdlength); printf("rd="); for(j=0; j { printf("%2.2x(%u)",*p,*p); p++; } printf("\n"); } }
static unsigned char *printnamestring(unsigned char *p,unsigned char *buf) { unsigned int nchars,offset;
nchars = *(p++); if((nchars & 0xc0) == 0xc0) { offset = (nchars & 0x3f) << 8; offset |= *(p++); nchars = buf[offset++]; printf("%*.*s",nchars,nchars,buf+offset); } else { printf("%*.*s",nchars,nchars,p); p += nchars; }
return (p); }
|
lsj@debian007:~$ gcc -g -Wall -o DNSClient DNSClient.c
然后执行:
lsj@debian007:~$ ./DNSClient bigdogchina.cublog.cn
就可以看见结果啦,这里使用的是UDP的数据格式,我们知道UDP的头部有一个16bit的长度,那么能表示的最大长度为2的16次方65536,再减去包头20,所以UDP包最大长度为65536-20=65516,但是在实际应用中,最好不要超过1K