写点什么

Java Netty 框架自建 DNS 代理服务器教程

作者:小小怪下士
  • 2023-01-09
    湖南
  • 本文字数:8900 字

    阅读完需:约 29 分钟

前言

DNS 协议作为着互联网客户端-服务器通信模式得第一关,在当下每天都有成千上亿上网记录产生得当今社会,其重要性自然不可言喻。在国内比较有名得 DNS 服务器有电信得 114.114.114.114、阿里云得 223.5.5.5,DNSPod 得 119.29.29.29,配置一个好的 DNS 服务器可以缩短请求响应时间、降低 DNS 劫持概率,提升上网体验。


上面这些都是互联网公用 DNS 服务器,本文博主教大家使用 Java Netty 自建 DNS 代理服务器,目前网上对于使用 Netty 自建 DNS 服务器得教程良莠不齐,大多没有代理步骤,达不到博主想要得代理效果,因而创建此文。

一、自建 DNS 代理服务器有哪些优势

  1. 域名控制:对于特定域名可以自由控制访问权限(屏蔽对特定网站访问)

  2. 域名记录:记录局域网内各个主机得域名访问(记录员工上网记录)

  3. 配置内网域名:通过自建 DNS 服务器可以配置内网域名,节约成本

  4. DNS 负载均衡:通过自建 DNS 服务器可以轻松实现对于访问域名得负载均衡配置

  5. ...

二、自建 DNS 代理服务器代码

  1. 添加域名黑名单文件,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);        }    }
复制代码


  1. 使用 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()); } }); }}
复制代码


  1. 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 查询。


  1. 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); }}
复制代码


  1. 自建 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); }}
复制代码

三、本地测试

  1. 修改本机 DNS 设置(win11),修改首选、备选 DNS 地址为 127.0.0.1



  1. 打开命令行工具,执行 DNS 缓存清除命令 ipconfig/flushdns



自此就可以打开浏览器访问常用网站,看是否能正常访问,来验证自建的 DNS 服务器效果了

用户头像

还未添加个人签名 2022-09-04 加入

热衷于分享java技术,一起交流学习,探讨技术。 需要Java相关资料的可以+v:xiaoyanya_1

评论

发布
暂无评论
Java Netty框架自建DNS代理服务器教程_Java_小小怪下士_InfoQ写作社区