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: 要求查询的域名地址。比如有这样一个邮件地址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的返回包。
文章不免有错,请各位多指点craks@263.net