一般来说,自己编写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类已经帮我们把这些事情都处理好了。

更改代码如下,

 package com.everSeeker;

 import org.xbill.DNS.Message;

 import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException; 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())); Message indata = new Message(request.getData());
System.out.println("indata = " + indata.toString());
} catch (SocketException e) {
System.out.println("SocketException:");
e.printStackTrace();
} catch (IOException e) {
System.out.println("IOException:");
e.printStackTrace();
}
}
}
}

重新测试,输出信息为:

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

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

 package com.everSeeker;

 import org.xbill.DNS.*;

 import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException; 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);
//分析dns数据包格式
Message indata = new Message(request.getData());
System.out.println("\nindata = " + indata.toString());
Record question = indata.getQuestion();
System.out.println("question = " + question);
String domain = indata.getQuestion().getName().toString();
System.out.println("domain = " + domain);
//解析域名
InetAddress answerIpAddr = Address.getByName(domain);
Message outdata = (Message)indata.clone();
//由于接收到的请求为A类型,因此应答也为ARecord。查看Record类的继承,发现还有AAAARecord(ipv6),CNAMERecord等
Record answer = new ARecord(question.getName(), question.getDClass(), 64, answerIpAddr);
outdata.addRecord(answer, Section.ANSWER);
//发送消息给客户端
byte[] buf = outdata.toWire();
DatagramPacket response = new DatagramPacket(buf, buf.length, sourceIpAddr, sourcePort);
socket.send(response);
} catch (SocketException e) {
System.out.println("SocketException:");
e.printStackTrace();
} catch (IOException e) {
System.out.println("IOException:");
e.printStackTrace();
}
}
}
}

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

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

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

  1. Java内部DNS查询实现和参数设置

    一.Java内部DNS查询 Java使用域名查询时,用的自己内部的域名实现机制,最后都是交给InetAddress去做DNS解析. 源码分析参考:http://blog.arganzheng.me/p ...

  2. 【LeetCode-面试算法经典-Java实现】【053-Maximum Subarray(最大子数组和)】

    [053-Maximum Subarray(最大子数组和)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Find the contiguous subarray w ...

  3. 【LeetCode-面试算法经典-Java实现】【062-Unique Paths(唯一路径)】

    [062-Unique Paths(唯一路径)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 A robot is located at the top-left c ...

  4. JavaMail SMTP服务器发送邮件程序示例 java通过dns服务器解析ip地址

    /** * JavaMail SMTP服务器发送邮件程序示例 * 扮演SMTP服务器角色与邮件客户端软件最大的区别就是: * SMTP服务器需要解析不同接收人邮件地址主机名对应的SMTP服务器主机名 ...

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

    [059-Spiral Matrix II(螺旋矩阵II)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given an integer n, generate a ...

  6. 【LeetCode-面试算法经典-Java实现】【136-Single Number(仅仅出现一次的数字)】

    [136-Single Number(仅仅出现一次的数字)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given an array of integers, ev ...

  7. 【LeetCode-面试算法经典-Java实现】【075-Sort Colors (颜色排序)】

    [075-Sort Colors (颜色排序)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given an array with n objects colore ...

  8. 【LeetCode-面试算法经典-Java实现】【101-Symmetric Tree(对称树)】

    [101-Symmetric Tree(对称树)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a binary tree, check whether ...

  9. 【LeetCode-面试算法经典-Java实现】【109-Convert Sorted List to Binary Search Tree(排序链表转换成二叉排序树)】

    [109-Convert Sorted List to Binary Search Tree(排序链表转换成二叉排序树)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 ...

随机推荐

  1. 【Linux】Jenkins+Git源码管理(三)

    摘要 本章介绍Jenkins配合Git源码管理,关于Jenkins的基本操作,参照[Linux]Jenkins配置和使用(二) 事例说明:在linux环境下,安装的jenkins,已安装git. 代码 ...

  2. Python sys.argv[] 的用法

    sys.argv变量是一个list, 执行 python abc.py a b c 时, sys.argv[0]为 abc.py sys.argv[1]为 a sys.argv[2]为 b sys.a ...

  3. TP5在lnmp环境中不能重写的问题

    说到坑,这个问题困扰了我一两天时间,本地可以,线上环境检查了好久. 基本检查的地方有几个了,首先就是nginx下面的重写配置,这个大家在网上都能搜到,至于定义的变量和配置路径,修改一下即可. 还有就是 ...

  4. flex布局中的主轴和侧轴的确定

    1.主轴和侧轴是通过flex-direction确定的 如果flex-direction是row或者row-reverse,那么主轴就是justify-contain 如果flex-direction ...

  5. div配景图片全div显示

    <div class="face-boy" style="width:86px;height:92px;background: url('/${userProfil ...

  6. BZOJ 4407 于神之怒加强版 (莫比乌斯反演 + 分块)

    4407: 于神之怒加强版 Time Limit: 80 Sec  Memory Limit: 512 MBSubmit: 1067  Solved: 494[Submit][Status][Disc ...

  7. navigtor对象和插件检测

    每一个浏览器都内置了属于自己的一套属性和方法 浏览器中navigator对象有plugins属性对象存着插件的数组 每一项包含: name 插件名称 description 插件的描述 filenam ...

  8. L1范式和L2范式

    正则化(Regularization) 机器学习中几乎都可以看到损失函数后面会添加一个额外项,常用的额外项一般有两种,一般英文称作ℓ1ℓ1-norm和ℓ2ℓ2-norm,中文称作L1正则化和L2正则化 ...

  9. GIS矢量数据化简:一种改进的道格拉斯-普克算法以及C++实现

    GIS领域的同志都知道,传统的道格拉斯-普克算法都是递归实现.然而有时候递归的层次太深的话会出现栈溢出的情况.在此,介绍一种非递归的算法. 要将递归算法改为非递归算法,一般情况下分为两种场景.第一种是 ...

  10. Logging from multiple processes using log4net

    When logging with log4net to a file (using the FileAppender), the FileAppender is holding an exclusi ...