试着用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就完成了。

时间: 2024-11-07 20:11:54

试着用java实现DNS(一)——DatagramSocket, DatagramPacket, Message的相关文章

【LeetCode-面试算法经典-Java实现】【107-Binary Tree Level Order Traversal II(二叉树层序遍历II)】

[107-Binary Tree Level Order Traversal II(二叉树层序遍历II)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from left to right, level by level from leaf to root). For example

【LeetCode-面试算法经典-Java实现】【064-Minimum Path Sum(最小路径和)】

[064-Minimum Path Sum(最小路径和)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path. Note: You can only move either

【LeetCode-面试算法经典-Java实现】【056-Merge Intervals(区间合并)】

[056-Merge Intervals(区间合并)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a collection of intervals, merge all overlapping intervals. For example, Given [1,3],[2,6],[8,10],[15,18], return [1,6],[8,10],[15,18]. 题目大意 给定一个区间集合,合并有重叠的区间. 解题思路 先对区间进行排序.按開始

【LeetCode-面试算法经典-Java实现】【066-Plus One(加一)】

[066-Plus One(加一)] [LeetCode-面试算法经典-Java实现][所有题目目录索引] 原题 Given a non-negative number represented as an array of digits, plus one to the number. The digits are stored such that the most significant digit is at the head of the list. 题目大意 给定一个用数组表示的一个数,

【LeetCode-面试算法经典-Java实现】【067-Add Binary(二进制加法)】

[067-Add Binary(二进制加法)] [LeetCode-面试算法经典-Java实现][所有题目目录索引] 原题 Given two binary strings, return their sum (also a binary string). For example, a = "11" b = "1" Return "100" 题目大意 给定两个二进制的字符串,返回它们的和,也是二进行制字符串. 解题思路 先将对应的两个二进制字符串

【LeetCode-面试算法经典-Java实现】【054-Spiral Matrix(螺旋矩阵)】

[054-Spiral Matrix(螺旋矩阵)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order. For example, Given the following matrix: [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ]

【LeetCode-面试算法经典-Java实现】【033-Search in Rotated Sorted Array(在旋转数组中搜索)】

[033-Search in Rotated Sorted Array(在旋转数组中搜索)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2). You are given a target value to search.

【LeetCode-面试算法经典-Java实现】【130-Surrounded Regions(环绕区域)】

[130-Surrounded Regions(环绕区域)] [LeetCode-面试算法经典-Java实现][所有题目目录索引] 原题 Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A region is captured by flipping all 'O's into 'X's in that surrounded region. For example, X X X X X

【LeetCode-面试算法经典-Java实现】【134-Gas Station(加油站问题)】

[134-Gas Station(加油站问题] [LeetCode-面试算法经典-Java实现][所有题目目录索引] 原题 There are N gas stations along a circular route, where the amount of gas at station i is gas[i]. You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from statio