前言
DNS协定作为着互联网客户端-服务器通信模式得第一关,在当下每天都有成千上亿上网记录产生得当今社会,其重要性天然不可言喻。在国内比拟有名得DNS服务器有电信得114.114.114.114、阿里云得223.5.5.5,DNSPod得119.29.29.29,配置一个好的DNS服务器能够缩短申请响应工夫、升高DNS劫持概率,晋升上网体验。
下面这些都是互联网专用DNS服务器,本文博主教大家应用 Java Netty
自建DNS代理服务器,目前网上对于应用Netty自建DNS服务器得教程参差不齐,大多没有代理步骤,达不到博主想要得代理成果,因此创立此文。
一、自建DNS代理服务器有哪些劣势
- 域名管制:对于特定域名能够自在管制拜访权限(屏蔽对特定网站拜访)
- 域名记录:记录局域网内各个主机得域名拜访(记录员工上网记录)
- 配置内网域名:通过自建DNS服务器能够配置内网域名,节约老本
- DNS负载平衡:通过自建DNS服务器能够轻松实现对于拜访域名得负载平衡配置
- ...
二、自建DNS代理服务器代码
- 增加域名黑名单文件,
resources
文件夹下增加black_list.txt
文件
google.com.facebook.com.
初始化 BLACK_LIST_DOMAIN
private static final List<String> BLACK_LIST_DOMAIN = new ArrayList<>(); static { String s; try (InputStream is = DnsServer.class.getClassLoader().getResourceAsStream("black_list.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { while (StrUtil.isNotBlank(s = br.readLine())) { BLACK_LIST_DOMAIN.add(s); } } catch (Exception e) { log.error(e.getMessage(), e); } }
- 应用
UDP
协定绑定本机53端口,并初始化ProxyUdp
DNS申请代理对象
@Slf4jpublic final class DnsServer { private static final List<String> BLACK_LIST_DOMAIN = new ArrayList<>(); static { ... } public static void main(String[] args) throws Exception { ProxyUdp proxyUdp = new ProxyUdp(); proxyUdp.init(); final int[] num = {0}; final NioEventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioDatagramChannel.class) .handler(new ChannelInitializer<NioDatagramChannel>() { @Override protected void initChannel(NioDatagramChannel nioDatagramChannel) { nioDatagramChannel.pipeline().addLast(...); } }).option(ChannelOption.SO_BROADCAST, true); int port = 53; ChannelFuture future = bootstrap.bind(port).addListener(future1 -> { log.info("server listening port:{}", port); }); future.channel().closeFuture().addListener(future1 -> { if (future.isSuccess()) { log.info(future.channel().toString()); } }); }}
- 给
nioDatagramChannel.pipeline()
增加ChannelHandler
nioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder()); nioDatagramChannel.pipeline().addLast(new SimpleChannelInboundHandler<DatagramDnsQuery>() { @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsQuery msg) { try { DefaultDnsQuestion dnsQuestion = msg.recordAt(DnsSection.QUESTION); String name = dnsQuestion.name(); log.info(name + ++num[0]); Channel channel = ctx.channel(); int id = msg.id(); channel.attr(AttributeKey.<DatagramDnsQuery>valueOf(String.valueOf(id))).set(msg); if (BLACK_LIST_DOMAIN.contains(name)) { DnsQuestion question = msg.recordAt(DnsSection.QUESTION); DatagramDnsResponse dnsResponse = getDatagramDnsResponse(msg, id, question); channel.writeAndFlush(dnsResponse); return; } proxyUdp.send(name, msg.id(), channel); } catch (Exception e) { log.error(e.getMessage(), e); } } private DatagramDnsResponse getDatagramDnsResponse(DatagramDnsQuery msg, int id, DnsQuestion question) { DatagramDnsResponse dnsResponse = new DatagramDnsResponse(msg.recipient(), msg.sender(), id); dnsResponse.addRecord(DnsSection.QUESTION, question); DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord( question.name(), DnsRecordType.A, 600, Unpooled.wrappedBuffer(new byte[]{(byte) 192, (byte) 168, 1, 1})); dnsResponse.addRecord(DnsSection.ANSWER, queryAnswer); return dnsResponse; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { log.error(e.getMessage(), e); } }); nioDatagramChannel.pipeline().addLast(new DatagramDnsResponseEncoder());
在 new SimpleChannelInboundHandler<DatagramDnsQuery>()
中 解析客户端DNS查问报文, 获取拜访域名信息,如果拜访域名在黑名单中,则通过 getDatagramDnsResponse()
间接返回 192.168.1.1
的DNS响应报文,反之则通过 proxyUdp
对象转发DNS查问。
ProxyUdp
作为DNS查问代理类会通过send(String domain, int id, Channel serverChannel)
办法传入DnsServer类收到的拜访域名、DNS事务ID、serverChannel。随后包装拜访域名申请DNS服务器114.114.114.114
,最初通过new SimpleChannelInboundHandler<DatagramDnsResponse>()
将收到的DNS响应报文通过上一步传入得serverChannel
输入到客户端。
@Slf4jclass ProxyUdp { private Channel serverChannel; private Channel proxyChannel; public void init() throws InterruptedException { EventLoopGroup proxyGroup = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(proxyGroup) .channel(NioDatagramChannel.class) .handler(new ChannelInitializer<DatagramChannel>() { @Override protected void initChannel(DatagramChannel ch) { ChannelPipeline p = ch.pipeline(); p.addLast(new DatagramDnsQueryEncoder()) .addLast(new DatagramDnsResponseDecoder()) .addLast(new SimpleChannelInboundHandler<DatagramDnsResponse>() { @Override public void channelActive(ChannelHandlerContext ctx) { log.info(ctx.channel().toString()); } @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsResponse msg) { DatagramDnsQuery dnsQuery = localChannel.attr(AttributeKey.<DatagramDnsQuery>valueOf(String.valueOf(msg.id()))).get(); DnsQuestion question = msg.recordAt(DnsSection.QUESTION); DatagramDnsResponse dnsResponse = new DatagramDnsResponse(dnsQuery.recipient(), dnsQuery.sender(), msg.id()); dnsResponse.addRecord(DnsSection.QUESTION, question); for (int i = 0, count = msg.count(DnsSection.ANSWER); i < count; i++) { DnsRecord record = msg.recordAt(DnsSection.ANSWER, i); if (record.type() == DnsRecordType.A) { // just print the IP after query DnsRawRecord raw = (DnsRawRecord) record; DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord( question.name(), DnsRecordType.A, 600, Unpooled.wrappedBuffer(ByteBufUtil.getBytes(raw.content()))); dnsResponse.addRecord(DnsSection.ANSWER, queryAnswer); } } serverChannel.writeAndFlush(dnsResponse); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { log.error(e.getMessage(), e); } }); } }); proxyChannel = b.bind(0).sync().addListener(future1 -> { log.info("绑定胜利"); }).channel(); } public void send(String domain, int id, Channel serverChannel) { this.serverChannel = serverChannel; DnsQuery query = new DatagramDnsQuery(null, new InetSocketAddress("114.114.114.114", 53), id).setRecord( DnsSection.QUESTION, new DefaultDnsQuestion(domain, DnsRecordType.A)); this.proxyChannel.writeAndFlush(query); }}
- 自建DNS服务器全副代码
@Slf4jpublic final class DnsServer { private static final List<String> BLACK_LIST_DOMAIN = new ArrayList<>(); static { String s; try (InputStream is = DnsServer.class.getClassLoader().getResourceAsStream("black_list.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { while (StrUtil.isNotBlank(s = br.readLine())) { BLACK_LIST_DOMAIN.add(s); } } catch (Exception e) { log.error(e.getMessage(), e); } } public static void main(String[] args) throws Exception { ProxyUdp proxyUdp = new ProxyUdp(); proxyUdp.init(); final int[] num = {0}; final NioEventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioDatagramChannel.class) .handler(new ChannelInitializer<NioDatagramChannel>() { @Override protected void initChannel(NioDatagramChannel nioDatagramChannel) { nioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder()); nioDatagramChannel.pipeline().addLast(new SimpleChannelInboundHandler<DatagramDnsQuery>() { @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsQuery msg) { try { DefaultDnsQuestion dnsQuestion = msg.recordAt(DnsSection.QUESTION); String name = dnsQuestion.name(); log.info(name + ++num[0]); Channel channel = ctx.channel(); int id = msg.id(); channel.attr(AttributeKey.<DatagramDnsQuery>valueOf(String.valueOf(id))).set(msg); if (BLACK_LIST_DOMAIN.contains(name)) { DnsQuestion question = msg.recordAt(DnsSection.QUESTION); DatagramDnsResponse dnsResponse = getDatagramDnsResponse(msg, id, question); channel.writeAndFlush(dnsResponse); return; } proxyUdp.send(name, msg.id(), channel); } catch (Exception e) { log.error(e.getMessage(), e); } } private DatagramDnsResponse getDatagramDnsResponse(DatagramDnsQuery msg, int id, DnsQuestion question) { DatagramDnsResponse dnsResponse = new DatagramDnsResponse(msg.recipient(), msg.sender(), id); dnsResponse.addRecord(DnsSection.QUESTION, question); // just print the IP after query DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord( question.name(), DnsRecordType.A, 600, Unpooled.wrappedBuffer(new byte[]{(byte) 192, (byte) 168, 1, 1})); dnsResponse.addRecord(DnsSection.ANSWER, queryAnswer); return dnsResponse; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { log.error(e.getMessage(), e); } }); nioDatagramChannel.pipeline().addLast(new DatagramDnsResponseEncoder()); } }).option(ChannelOption.SO_BROADCAST, true); int port = 553; ChannelFuture future = bootstrap.bind(port).addListener(future1 -> { log.info("server listening port:{}", port); }); future.channel().closeFuture().addListener(future1 -> { if (future.isSuccess()) { log.info(future.channel().toString()); } }); }}@Slf4jclass ProxyUdp { private Channel localChannel; private Channel proxyChannel; public void init() throws InterruptedException { EventLoopGroup proxyGroup = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(proxyGroup) .channel(NioDatagramChannel.class) .handler(new ChannelInitializer<DatagramChannel>() { @Override protected void initChannel(DatagramChannel ch) { ChannelPipeline p = ch.pipeline(); p.addLast(new DatagramDnsQueryEncoder()) .addLast(new DatagramDnsResponseDecoder()) .addLast(new SimpleChannelInboundHandler<DatagramDnsResponse>() { @Override public void channelActive(ChannelHandlerContext ctx) { log.info(ctx.channel().toString()); } @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsResponse msg) { DatagramDnsQuery dnsQuery = localChannel.attr(AttributeKey.<DatagramDnsQuery>valueOf(String.valueOf(msg.id()))).get(); DnsQuestion question = msg.recordAt(DnsSection.QUESTION); DatagramDnsResponse dnsResponse = new DatagramDnsResponse(dnsQuery.recipient(), dnsQuery.sender(), msg.id()); dnsResponse.addRecord(DnsSection.QUESTION, question); for (int i = 0, count = msg.count(DnsSection.ANSWER); i < count; i++) { DnsRecord record = msg.recordAt(DnsSection.ANSWER, i); if (record.type() == DnsRecordType.A) { // just print the IP after query DnsRawRecord raw = (DnsRawRecord) record; DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord( question.name(), DnsRecordType.A, 600, Unpooled.wrappedBuffer(ByteBufUtil.getBytes(raw.content()))); dnsResponse.addRecord(DnsSection.ANSWER, queryAnswer); } } localChannel.writeAndFlush(dnsResponse); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { log.error(e.getMessage(), e); } }); } }); proxyChannel = b.bind(0).sync().addListener(future1 -> { log.info("绑定胜利"); }).channel(); } public void send(String domain, int id, Channel localChannel) { this.localChannel = localChannel; DnsQuery query = new DatagramDnsQuery(null, new InetSocketAddress("114.114.114.114", 53), id).setRecord( DnsSection.QUESTION, new DefaultDnsQuestion(domain, DnsRecordType.A)); this.proxyChannel.writeAndFlush(query); }}
三、本地测试
- 批改本机DNS设置(win11),批改首选、备选DNS地址为127.0.0.1
- 关上命令行工具,执行DNS缓存革除命令
ipconfig/flushdns
自此就能够关上浏览器拜访罕用网站,看是否能失常拜访,来验证自建的DNS服务器成果了