找回密码
 立即注册
首页 业界区 安全 使用Java实现一个DNS服务

使用Java实现一个DNS服务

秦晓曼 昨天 14:14
有时,我们所在单位的电脑只允许上内网,外网被断掉了,如果想要同时上内外网,我们可以通过修改路由表,然后双网卡一机两网的方式来实现分流上网,例如网线连公司内网,用WiFi连接自己的手机热点,或者额外购买一个USB网卡插入电脑,同时连接公司的AP和自己手机热点。
但是这样会衍生出一个问题,有些公司的内部系统例如OA系统等,也是通过域名而不是难以记忆的IP地址来访问的,这些内部系统的域名不是注册商注册的,更不在公共DNS上,而是公司内网上使用的内网域名,使用公司自建的内网DNS服务器才能解析,解析出通常是一个本地局域网地址,在公网无法解析和访问,当接入公司内网,企业路由器会通过DHCP下发内网DNS给网卡,现在同时上内外网时,外网网卡也会获得运营商下发的外网DNS地址,操作系统会按照跃点数只选择某个网卡上获得的的DNS用作DNS解析,如果默认了内网网卡优先,且内网DNS只解析公司内网域名,同样会导致外网无法访问,如果内网DNS能解析外部域名,同样存在利用DNS屏蔽某些网站或服务(例如影视剧,游戏,向日葵远控等)甚至后台偷偷记录DNS解析记录的可能,因此为了保险起见,我们可以自己用代码实现一个DNS代理服务器来进行代理和分流,根据特定后缀等特征判断出内网域名,交给内网DNS解析,对于外网域名则直接选择一些公共DNS来解析(例如谷歌,阿里,114的DNS服务)
这里采用Java实现一个多线程的DNS代理服务器,对于内网域名直接通过内网DNS的UDP:53进行解析,对于外网域名则以加密的DOH(DNS Over Https)方式通过阿里云DNS进行解析,并解析DNS服务器返回的报文并打印日志。需要依赖dnsjava这个类库的支持,程序启动后,只需要将网卡DNS服务器地址和备用地址修改为127.0.0.1和127.0.0.2即可实现DNS的分流。
  1. <dependencies>
  2.    
  3.     <dependency>
  4.         <groupId>dnsjava</groupId>
  5.         dnsjava</artifactId>
  6.         <version>3.6.0</version>
  7.     </dependency>
  8.    
  9.     <dependency>
  10.         <groupId>org.apache.httpcomponents.client5</groupId>
  11.         httpclient5</artifactId>
  12.         <version>5.3</version>
  13.     </dependency>
  14. </dependencies>
复制代码
[code]package com.changelzj.dns;import org.apache.hc.core5.http.ContentType;import org.xbill.DNS.*;import org.apache.hc.client5.http.classic.methods.HttpPost;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.io.entity.ByteArrayEntity;import java.io.ByteArrayInputStream;import java.io.DataInputStream;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;import java.nio.charset.StandardCharsets;import java.time.Duration;import java.time.Instant;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.concurrent.*;public class LoggedDnsServer {    /**      * 需要内网DNS才能解析的内网域名    */     private static final String[] INTERNAL_DOMAINS = {"p****c.com", "s******c.com"};    /**     * 内网NDS服务器IP地址     */    private static final String INTERNAL_DNS = "10.249.35.11";    private static final String DOH_URL = "https://223.5.5.5/dns-query";    private static final ExecutorService executor = new ThreadPoolExecutor(            Runtime.getRuntime().availableProcessors() * 2,            Runtime.getRuntime().availableProcessors() * 2,            60L,            TimeUnit.SECONDS,            new LinkedBlockingQueue(200),            new ThreadPoolExecutor.CallerRunsPolicy()    );    public static void main(String[] args) throws IOException {        DatagramSocket socket = new DatagramSocket(53);        System.out.println("Multi-threaded DNS Server with Logging started on port 53");        byte[] buffer = new byte[512];        while (true) {            DatagramPacket requestPacket = new DatagramPacket(buffer, buffer.length);            socket.receive(requestPacket);            byte[] requestData = new byte[requestPacket.getLength()];            System.arraycopy(requestPacket.getData(), 0, requestData, 0, requestPacket.getLength());            executor.submit(() -> {                Instant start = Instant.now();                String domain = "";                String method = "";                boolean success = false;                String ip = "";                try {                    Message query = new Message(requestData);                    domain = query.getQuestion().getName().toString(true).toLowerCase();                    byte[] responseData;                    if (isInternalDomain(domain)) {                        method = "Internal DNS (" + INTERNAL_DNS + ")";                        responseData = forwardToUdpDns(query, INTERNAL_DNS);                    } else {                        method = "Ali DNS DoH (" + DOH_URL + ")";                        responseData = forwardToDoh(query);                    }                    success = true;                    ip = parseDnsResponse(responseData).toString();                     DatagramPacket responsePacket = new DatagramPacket(                            responseData,                            responseData.length,                            requestPacket.getAddress(),                            requestPacket.getPort()                    );                    socket.send(responsePacket);                } catch (Exception e) {                    System.err.println("[ERROR] " + e.getMessage());                } finally {                    long ms = Duration.between(start, Instant.now()).toMillis();                    System.out.printf(                            "[%s] %s -> %s | %s | %s | %dms | %s  %n",                            requestPacket.getAddress().getHostAddress(),                            domain,                            method,                            success ? "OK" : "FAIL",                            ip,                            ms,                            Thread.currentThread().getName()                    );                }            });        }    }    private static boolean isInternalDomain(String domain) {        for (String suffix : INTERNAL_DOMAINS) {            if (domain.endsWith(suffix)) {                return true;            }        }        return false;    }    private static byte[] forwardToUdpDns(Message query, String dnsServer) throws IOException {        SimpleResolver resolver = new SimpleResolver(dnsServer);        resolver.setTCP(false);        resolver.setTimeout(3);        Message response = resolver.send(query);        return response.toWire();    }    private static byte[] forwardToDoh(Message query) throws IOException {        try (CloseableHttpClient client = HttpClients.createDefault()) {            HttpPost post = new HttpPost(DOH_URL);            post.setHeader("Content-Type", "application/dns-message");            post.setEntity(new ByteArrayEntity(query.toWire(), ContentType.create("application/dns-message")));            return client.execute(post, httpResponse -> {                try (java.io.InputStream in = httpResponse.getEntity().getContent();                     java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream()) {                    byte[] buf = new byte[1024];                    int len;                    while ((len = in.read(buf)) != -1) {                        bos.write(buf, 0, len);                    }                    return bos.toByteArray();                }            });        }    }    public static List parseDnsResponse(byte[] msg) throws Exception {        List result = new ArrayList();        int pos = 0;        // 头部 12 字节        pos += 4; // ID + Flags        int qdCount = ((msg[pos] & 0xFF)

相关推荐

您需要登录后才可以回帖 登录 | 立即注册