有时,我们所在单位的电脑只允许上内网,外网被断掉了,如果想要同时上内外网,我们可以通过修改路由表,然后双网卡一机两网的方式来实现分流上网,例如网线连公司内网,用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的分流。- <dependencies>
-
- <dependency>
- <groupId>dnsjava</groupId>
- dnsjava</artifactId>
- <version>3.6.0</version>
- </dependency>
-
- <dependency>
- <groupId>org.apache.httpcomponents.client5</groupId>
- httpclient5</artifactId>
- <version>5.3</version>
- </dependency>
- </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) |