乐趣区

关于netty:Java-Netty框架自建DNS代理服务器教程

前言

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 服务器能够轻松实现对于拜访域名得负载平衡配置

二、自建 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 申请代理对象
@Slf4j
public 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 输入到客户端。
@Slf4j
class 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 服务器全副代码
@Slf4j
public 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());
            }
        });
    }
}

@Slf4j
class 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 服务器成果了

退出移动版