zoukankan      html  css  js  c++  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。。。。。。
    ");
            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("
    sourceIpAddr = " + sourceIpAddr.toString() + "
    sourcePort = " + 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。。。。。。
    ");
    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("
    sourceIpAddr = " + sourceIpAddr.toString() + "
    sourcePort = " + 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。。。。。。
    ");
    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("
    sourceIpAddr = " + sourceIpAddr.toString() + "
    sourcePort = " + sourcePort);
    34                 //分析dns数据包格式
    35                 Message indata = new Message(request.getData());
    36                 System.out.println("
    indata = " + 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就完成了。

  • 相关阅读:
    SP3946 MKTHNUM
    P1948 [USACO08JAN]电话线Telephone Lines(二分答案+最短路)
    CF375D Tree and Queries(dsu on tree)
    P2051 [AHOI2009]中国象棋(动态规划)
    P3810 【模板】三维偏序(陌上花开)(cdq分治)
    P4390 [BOI2007]Mokia 摩基亚(cdq分治)
    P2163 [SHOI2007]园丁的烦恼(cdq分治)
    UVA11270 Tiling Dominoes(轮廓线动态规划)
    P2475 [SCOI2008]斜堆(递归模拟)
    P2617 Dynamic Rankings(带修主席树)
  • 原文地址:https://www.cnblogs.com/everSeeker/p/5297871.html
Copyright © 2011-2022 走看看