你的位置:首页 > Java教程

[Java教程]试着用java实现DNS(一)——DatagramSocket, DatagramPacket, Message


    一般来说,自己编写DNS是没有必要的,目前开源的dns服务软件很多,功能也很强大。但是,有时候又是很有必要的,有着诸多好处。比如说,用于企业内网,简化DNS配置,可以根据企业需求添加新的功能,非常灵活。本文试着用java实现一个最简单的DNS服务。

    

    DNS是基于udp协议的,默认端口为53。

    在自己电脑上实现dns服务(作为dns服务器),首先需要程序监听udp 53端口。在java中,和udp相关的类为DatagramSocket以及DatagramPacket。具体信息可以查看API,或者参考http://www.cnblogs.com/hq-antoine/archive/2012/02/11/2346908.html。

    之后,需要另一台电脑作为客户端,设置dns地址为服务器的ip地址。

public class UDPServer {  private static DatagramSocket socket;  public UDPServer() {    //设置socket,监听端口53    try {      this.socket = new DatagramSocket(53);    } catch (SocketException e) {      e.printStackTrace();    }  }  public void start() {    System.out.println("Starting。。。。。。\n");    while (true) {      try {        byte[] buffer = new byte[1024];        DatagramPacket request = new DatagramPacket(buffer, buffer.length);        socket.receive(request);        //输出客户端的dns请求数据        InetAddress sourceIpAddr = request.getAddress();        int sourcePort = request.getPort();        System.out.println("\nsourceIpAddr = " + sourceIpAddr.toString() + "\nsourcePort = " + sourcePort);        System.out.println("data = " + new String(request.getData(), 0 , request.getLength()));      } catch (SocketException e) {        System.out.println("SocketException:");        e.printStackTrace();      } catch (IOException e) {        System.out.println("IOException:");        e.printStackTrace();      }    }  }}

运行程序报错,错误提示如下:

    根据异常提示,打开53端口异常,需要确认操作权限。1024以下端口默认系统保留,只有root用户才能使用。使用root账号运行程序,之后在客户端上使用nslookup www.baidu.com命令测试,在服务器上输出信息如下:

 

    得到客户端的ip地址为10.211.55.253,端口为43065,但是下面这个data是什么玩意?

    分析:出现乱码,原因在于request.getData()获取到的数据并非为String类型,因此不能简单粗暴的通过new String(request.getData(), 0, request.getLength())强制为String类型输出。猜测应该是符合dns数据格式的字节流,下面通过抓包软件wireshark进行分析。

 

    发现dns数据包中,包括ID, Flags, Questions, Answer RRs, Authority RRs, Additional RRs以及Queries等字段。如果自己编写程序,通过分析字节流来提取出所需要的信息,是一个比较麻烦的事情。幸好有dnsjava这个开源项目,里面的Message类已经帮我们把这些事情都处理好了。

    更改代码如下,

 1 package com.everSeeker; 2  3 import org.xbill.DNS.Message; 4  5 import java.io.IOException; 6 import java.net.DatagramPacket; 7 import java.net.DatagramSocket; 8 import java.net.InetAddress; 9 import java.net.SocketException;10 11 public class UDPServer {12   private static DatagramSocket socket;13 14   public UDPServer() {15     //设置socket,监听端口5316     try {17       this.socket = new DatagramSocket(53);18     } catch (SocketException e) {19       e.printStackTrace();20     }21   }22 23   public void start() {24     System.out.println("Starting。。。。。。\n");25     while (true) {26       try {27         byte[] buffer = new byte[1024];28         DatagramPacket request = new DatagramPacket(buffer, buffer.length);29         socket.receive(request);30         //输出客户端的dns请求数据31         InetAddress sourceIpAddr = request.getAddress();32         int sourcePort = request.getPort();33         System.out.println("\nsourceIpAddr = " + sourceIpAddr.toString() + "\nsourcePort = " + sourcePort);34 //        System.out.println("data = " + new String(request.getData(), 0 , request.getLength()));35 36         Message indata = new Message(request.getData());37         System.out.println("indata = " + indata.toString());38       } catch (SocketException e) {39         System.out.println("SocketException:");40         e.printStackTrace();41       } catch (IOException e) {42         System.out.println("IOException:");43         e.printStackTrace();44       }45     }46   }47 }

    重新测试,输出信息为:

    发现,输出信息与我们通过抓包得到的信息一致。其中,Questions记录了需要解析的域名www.baidu.com,type为A。而Answers为空,是因为这是一个域名解析请求信息,下面我们只需要把解析的结果放入Answers这个字段,并返回给客户端,即完成了最简单的dns功能。

    分析dnsjava的源码,发现Message类中有一个变量private List [] sections, 长度为4,记录了Questions, Answers, Authority RRs, Additional RRs这4个字段。更改代码如下,

 1 package com.everSeeker; 2  3 import org.xbill.DNS.*; 4  5 import java.io.IOException; 6 import java.net.DatagramPacket; 7 import java.net.DatagramSocket; 8 import java.net.InetAddress; 9 import java.net.SocketException;10 11 public class UDPServer {12   private static DatagramSocket socket;13 14   public UDPServer() {15     //设置socket,监听端口5316     try {17       this.socket = new DatagramSocket(53);18     } catch (SocketException e) {19       e.printStackTrace();20     }21   }22 23   public void start() {24     System.out.println("Starting。。。。。。\n");25     while (true) {26       try {27         byte[] buffer = new byte[1024];28         DatagramPacket request = new DatagramPacket(buffer, buffer.length);29         socket.receive(request);30         //输出客户端的dns请求数据31         InetAddress sourceIpAddr = request.getAddress();32         int sourcePort = request.getPort();33         System.out.println("\nsourceIpAddr = " + sourceIpAddr.toString() + "\nsourcePort = " + sourcePort);34         //分析dns数据包格式35         Message indata = new Message(request.getData());36         System.out.println("\nindata = " + indata.toString());37         Record question = indata.getQuestion();38         System.out.println("question = " + question);39         String domain = indata.getQuestion().getName().toString();40         System.out.println("domain = " + domain);41         //解析域名42         InetAddress answerIpAddr = Address.getByName(domain);43         Message outdata = (Message)indata.clone();44         //由于接收到的请求为A类型,因此应答也为ARecord。查看Record类的继承,发现还有AAAARecord(ipv6),CNAMERecord等45         Record answer = new ARecord(question.getName(), question.getDClass(), 64, answerIpAddr);46         outdata.addRecord(answer, Section.ANSWER);47         //发送消息给客户端48         byte[] buf = outdata.toWire();49         DatagramPacket response = new DatagramPacket(buf, buf.length, sourceIpAddr, sourcePort);50         socket.send(response);51       } catch (SocketException e) {52         System.out.println("SocketException:");53         e.printStackTrace();54       } catch (IOException e) {55         System.out.println("IOException:");56         e.printStackTrace();57       }58     }59   }60 }

    继续测试,客户端nslookup www.baidu.com,输出结果为:

    测试成功,这样一个最简单的dns就完成了。