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’ |
一般的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 <stdio.h> #include <stdlib.h> #include <error.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <time.h>
static void printmessage(unsigned char *buf); static unsigned char *printnamestring(unsigned char *p,unsignedchar *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 <host_name>\n"); 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<qdcount; 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<ancount; 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<rdlength; j++) { printf("%2.2x(%u)",*p,*p); p++; } printf("\n"); } }
static unsigned char *printnamestring(unsigned char *p,unsignedchar *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