試着用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,監聽端口53
16         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,監聽端口53
16         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就完成了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM