NiceLeeのBlog 用爱发电 bilibili~

Java 本地DNS服务器Demo

2019-02-14
nIceLee

阅读:

 

本以为Android手机就和Linux一样改Host会比较方便,然而发现权限是个问题,很大的问题。有了这个契机,正好捣鼓一下Android提供的VPNService这个api,直接本地拦截DNS报文,然后根据host污染掉再直接回复。
当然,这篇文章作为前置,跟Android没半毛钱关系,只单纯的涉及DNS协议

写在前面

  • DNS协议详解及报文格式分析
  • 目的是加深对DNS协议的理解;最终目标是实现Android手机无Root下Host解析;现有目标是实现本地DNS A查询的答复(一般更多的使用的是域名解析功能,同时现在基本上使用的是IPv4)。

思考

  • 现在的思路如下:
    • 简单起见,先不做任何处理 - 客户端来了DNS查询请求,转给服务器; 服务器来了回复,直接转给客户端。
    • 考虑到只为本地服务,可直接采用DNS报文的ID作为Key,通过HashMap来保存DNS查询记录。服务器回复后查询Map匹配客户端端口。
    • 再拓展一下,可以在客户端来了DNS查询请求后直接构造回复污染DNS。

实现

接收到UDP报文的处理:

udpSocket.receive(packet);

DnsHeader header = new DnsHeader(receMsgs, 0);
Short dnsId = header.getID();
// 如果是来自客户端的, 直接回复或转发给DNS服务器
if (!dnsHost.equals(packet.getAddress())) {
    
    ByteBuffer buffer = ByteBuffer.wrap(receMsgs);
    DnsPacket dnsPacket = DnsPacket.FromBytes(buffer);
    Question q = dnsPacket.Questions[0];
    
    System.out.printf("查询的地址是: %s \r\n", q.Domain);
    String ipAddr = null;
    // 如果已有设定, 直接构造回复
    if ((ipAddr = domainIpMap.get(q.Domain)) != null) {
        createDNSResponseToAQuery(dnsPacket, packet, ipAddr);
        DatagramPacket sendPacket = new DatagramPacket(receMsgs, dnsPacket.Size,
                packet.getSocketAddress());
        udpSocket.send(sendPacket);
    } else {
    // 如果没有设定, 转发服务器
        QueryInfo info = new QueryInfo();
        info.socketAddr = packet.getSocketAddress();
        info.lastnanos = System.nanoTime();
        queryMap.put(dnsId, info);
        DatagramPacket sendPacket = new DatagramPacket(receMsgs, packet.getLength(), dnsHost, 53);
        udpSocket.send(sendPacket);
    }

} else {
    // 收到来自服务器的包,直接转发给客户端
    // System.out.println("收到来自服务器的消息, 会话标识为: " + dnsId);
    if (queryMap.containsKey(dnsId)) {
        // System.out.println("存在该会话标识, 返回结果");
        DatagramPacket sendPacket = new DatagramPacket(receMsgs, packet.getLength(),
                queryMap.get(dnsId).socketAddr);
        udpSocket.send(sendPacket);
        queryMap.remove(dnsId);
    }
}

构造DNS答复的方法:

public void createDNSResponseToAQuery(DnsPacket dnsPacket, DatagramPacket packet, String ipAddr) {
    Question question = dnsPacket.Questions[0];

    dnsPacket.Header.setResourceCount((short) 1);
    dnsPacket.Header.setAResourceCount((short) 0);
    dnsPacket.Header.setEResourceCount((short) 0);

    ResourcePointer rPointer = new ResourcePointer(packet.getData(), question.Offset() + question.Length());
    rPointer.setDomain((short) 0xC00C);
    rPointer.setType(question.Type);
    rPointer.setClass(question.Class);
    rPointer.setTTL(300);
    rPointer.setDataLength((short) 4);
    rPointer.setIP(CommonMethods.ipStringToInt(ipAddr));

    dnsPacket.Size = 12 + question.Length() + 16;
    
}

效果预览

源代码


内容
隐藏