Dns(Domain Name Server)即域名服务器,在网络中承担着将域名转换为ip地址的工作。在很多编程中都要用到这种技术,就是使用域名解析。这篇文章将说明这项技术。 通过Dns服务器,可以查询很多地址,比如mail服务器地址,ftp服务器等等,我在这里就以mail服务器为例,并以java实现。 +---------------------+ | Header | +---------------------+ | Question | +---------------------+ | Answer | +---------------------+ | Authority | +---------------------+ | Additional | +---------------------+ 这个表是从rfc1035文档中拷出来的,大致说明了dns包的格式。 Header 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 这个也是从rfc文档中拷出来的,只是我将其头部数字改成16进制了。 ID: 16位的一个标志,用以验证请求和回复消息的匹配。就实用程序产生一个16位的随机数。 QR: 1位数据表明这是一个请求,还是一个回复(0为请求,1为恢复)。 Opcode: 4位的数据表示查询的类型。 0 基本查找 1 反向查找 2 查询服务器情况 3-15 保留 RD:(recursion desired)即是否以递归方式的查询,RD=1为递归。 RA:(Recursion Available)表示服务器是否支持递归方式查询,只在回复中有效。 QDCOUNT:16位数据表示要查询的问题个数。 ANCOUNT:16位数据表示回复结果的个数,只在回复中有效。 其他几个请参考rfc文档,在这里我们只用这些,并将其他参数设为0。 Question 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ QNAME: 要求查询的域名地址。比如有这样一个邮件地址XXX@163.net, 我们将@后面的地址提取出来,即163.net。然后将其变为这样一个序列,31633net0,也就是以 . 分界,并以各自的字符个数作为前缀,最后以0结束 QTYPE: 2位数据表示查询类型。 A 1 a host address NS 2 an authoritative name server MD 3 a mail destination (Obsolete - use MX) MF 4 a mail forwarder (Obsolete - use MX) CNAME 5 the canonical name for an alias SOA 6 marks the start of a zone of authority MB 7 a mailbox domain name (EXPERIMENTAL MG 8 a mail group member (EXPERIMENTAL) MR 9 a mail rename domain name (EXPERIMENTAL) NULL 10 a null RR (EXPERIMENTAL) WKS 11 a well known service description PTR 12 a domain name pointer HINFO 13 host information MINFO 14 mailbox or mail list information MX 15 mail exchange TXT 16 text strings 这是在rfc文档中列出的各类type,我们在这里用MX,即QTYPE=15。 QCLASS: 2位数据表示查询方式。 IN 1 the Internet CS 2 the CSNET class (Obsolete - used only for examples in some obsolete RFCs) CH 3 the CHAOS class HS 4 Hesiod [Dyer 87] 这是在rfc文档中列出的各类class,我们在这里用IN,即QCLASS=15。 下面使用JAVA实现的原码: 说明:DnsTool.IntToBytes(int,int)是将一个整数转换为几个8位数的组合。 DnsTool.StringToBytes(String)是将一个字符串转换为QNAME需要的格式,并以BYTE[]的格式返回。 class DnsHeader { private int ID; private int Flags=0; private byte[] head=new byte[]{0,0,0,0,0,0,0,0,0,0,0,0}; /** Creates new DnsHeader */ public DnsHeader() { setID(); setFlags(Flags); setAnswer(false);//does not an answer setRecursionDesired(true); } private void setID() { byte[] tmp=new byte[2]; ID=new Random().nextInt(10); tmp=DnsTool.IntToBytes(ID,2); head[0]=tmp[0]; head[1]=tmp[1]; } public int getID() { return this.ID; } private void setFlags(int Flags) { byte[] tmp=new byte[2]; tmp=DnsTool.IntToBytes(ID,2); head[2]=tmp[0]; head[3]=tmp[1]; } public void setAnswer(boolean isAnswer) { head[2]=isAnswer?(byte)(head[2]|0x80):(byte) (head[2]&0x7f); } public void setRecursionDesired(boolean isRecursionDesired) { head[2]=isRecursionDesired?((byte)(head[2]|0x1)) :((byte)(head[2] & 0xFE)); } public void setQDcount(int num)//set the number of question { byte[] tmp=new byte[2]; tmp=DnsTool.IntToBytes(num,2); head[4]=tmp[0]; head[5]=tmp[1]; } public byte[] getBytes() { return head; } } class Question { private byte[] question; private int QuestionLength; /** Creates new Question */ public Question(String questionLabel,int questionType, int questionClass) { byte[] transName=DnsTool.StringToBytes(questionLabel); byte[] ty=DnsTool.IntToBytes(questionType,2); byte[] cl=DnsTool.IntToBytes(questionClass,2); QuestionLength=0; //transfer the QuestionLabel to the bytes question=new byte[transName.length+4]; System.arraycopy(transName,0,question,QuestionLength, transName.length); QuestionLength+=transName.length; //transfer the type to the bytes System.arraycopy(ty,0,question,QuestionLength, ty.length); QuestionLength+=ty.length; //transfer the class to the bytes System.arraycopy(cl,0,question,QuestionLength, cl.length); QuestionLength+=cl.length; } public byte[] getBytes() { return question; } } 这里实现了dns 的包头和要查询的question的数据,然后只要将它们组合在一起就成了dns包了,接下来就只要将它发出去就可以了,下面这段程序就实现了这一功能。 说明: DNSSERVER:就是dns服务器的地址。 DNSPORT:dns服务器的端口,即53。 DnsQuery:这个是header 和 question 组合的数据。 DatagramPacket ID_Packet; DatagramSocket ID_Socket; byte[] query=DnsQuery.getBytes(); int i; try { ID_Packet=new DatagramPacket(query,query.length,InetAddress.getByName(DNSSERVER),Constant.DNSPORT); ID_Socket=new DatagramSocket(); //send query ID_Socket.send(ID_Packet); //close socket ID_Socket.close(); } catch(IOException e) { System.out.println(e); return null; } } 下面这段程序是从Dns服务器上得到dns的返回包: ID_Packet=new DatagramPacket(new byte[Constant.DNSUDPLEN], Constant.DNSUDPLEN); ID_Socket.receive(ID_Packet); 这里的变量已在上篇中定义了,Constant.DNSUDPLEN为512。 接下来就只要将这数据解压缩就可以了。这里就涉及了RR的格式了(Resource Record Format)。 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| / RDATA / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 这是在rfc文档中定义的RR格式。 NAME:就是在question中的QNAME; TYPE:question中的QTYPE; CLASS:question中的QCLASS; RDLENGTH:RDATA的长度; RDATA:返回的数据,这才是真正有用的数据,也是我们要解析的东西。 因为其数据是被压缩的,所以得想知道他的压缩格式: 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 1 1| OFFSET | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 他的压缩方式是将在数据中重复出现的字符放在一起,然后再字符出现的地方加上一个偏移位置,即如上图所示,16位的数据以11开头,后跟偏移量。偏移量是从信息的头部开始算得。下面是一个rfc文档中的例子: 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 20 | 1 | F | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 22 | 3 | I | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 24 | S | I | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 26 | 4 | A | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 28 | R | P | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 30 | A | 0 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 40 | 3 | F | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 42 | O | O | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 44 | 1 1| 20 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 这个结果是:在40位置的域名是FOO.F.ISI.ARPA。 了解了他的压缩方式,解析就简单了。 上篇中在Header中我们已提到ANCOUNT这个字段,他表示的是回复中结果的数目,我们相见他解析出来: public int getAnswerCount() { int INDEX=6; byte[] AnCountArray=new byte[2]; System.arraycopy(message,INDEX,AnCountArray,0,2); return DnsTool.BytesToInt(AnCountArray);//将byte[]变为int } 得到了ANCOUNT,就可以解释结果了: public Vector parseAnswer() { int theOffset=8; int pos=thePosOfAnswer;(thePosOfAnswer是你发得dns包的长度) int i=0,p; int RDlength; byte[] tmp; String Name=""; Vector IV_ Answer=new Vector(); //get return name from message while(i<getAnswerCount()) { Name=""; //get type pos+=2; tmp=new byte[2]; System.arraycopy(message,pos,tmp,0,2); if(DnsTool.BytesToInt(tmp)==Constant.TYPE_MX)//check the type { pos+=theOffset; //get RDlength tmp=new byte[2]; System.arraycopy(message,pos,tmp,0,2); RDlength=DnsTool.BytesToInt(tmp); pos+=4; p=pos; while((pos-p)<RDlength-2) { if((message[pos]&0xC0)==0xC0) { //this is a offset Name+=getPrior((message[pos]&0x3F) |(message[pos+1]&0xFF)); pos+=2; } else { //not offset tmp=new byte[message[pos]]; System.arraycopy(message,pos+1,tmp,0,tmp.length); pos+=message[pos]+1; if(message[pos]!=0) Name+=new String(tmp)+"."; else Name+=new String(tmp); } } } IV_Answer.addElement(Name); i++; } } 函数Stirng getPrior(int)是根据其偏移量等到所要的字符串,这是一个递归函数: private String getPrior(int j) { byte[] tmp; String Name=""; while(message[j]!=0) { if((message[j]&0xC0)==0xC0) { String mid=getPrior((message[j]&0x3F)|(message[j+1]&0xFF)); Name+=mid; j+=mid.length()+1; } else { tmp=new byte[message[j]]; System.arraycopy(message,j+1,tmp,0,tmp.length); j+=message[j]+1; if(message[j]!=0) Name+=new String(tmp)+"."; else Name+=new String(tmp); } } return Name; } 我们只介绍了mail地址的dns解析,其他几类都大同小异,如需要可参考rfc1035。 |