Dns解析

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。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章