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: 要求查詢的域名地址。比如有這樣一個郵件地址[email protected],
我們將@後面的地址提取出來,即163.net。然後將其變爲這樣一個序列,0316303net0,也就是以 . 分界,並以各自的字符個數作爲前綴,最後以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。