最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

最资讯丨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服务器得教程良莠不齐,大多没有代理步骤,达不到博主想要得代理效果,因而创建此文。觉得本文有帮助得可以关注博主github

  • https://github.com/wayn111

一、自建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 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端口,并初始化 ProxyUdpDNS请求代理对象
@Slf4jpublic final class DnsServer {    private static final List 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() {                    @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() {                            @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.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()中 解析客户端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()将收到的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() {                    @Override                    protected void initChannel(DatagramChannel ch) {                        ChannelPipeline p = ch.pipeline();                        p.addLast(new DatagramDnsQueryEncoder())                                .addLast(new DatagramDnsResponseDecoder())                                .addLast(new SimpleChannelInboundHandler() {                                    @Override                                    public void channelActive(ChannelHandlerContext ctx) {                                        log.info(ctx.channel().toString());                                    }                                    @Override                                    protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsResponse msg) {                                        DatagramDnsQuery dnsQuery = localChannel.attr(AttributeKey.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 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() {                    @Override                    protected void initChannel(NioDatagramChannel nioDatagramChannel) {                        nioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder());                        nioDatagramChannel.pipeline().addLast(new SimpleChannelInboundHandler() {                            @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.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() {                    @Override                    protected void initChannel(DatagramChannel ch) {                        ChannelPipeline p = ch.pipeline();                        p.addLast(new DatagramDnsQueryEncoder())                                .addLast(new DatagramDnsResponseDecoder())                                .addLast(new SimpleChannelInboundHandler() {                                    @Override                                    public void channelActive(ChannelHandlerContext ctx) {                                        log.info(ctx.channel().toString());                                    }                                    @Override                                    protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsResponse msg) {                                        DatagramDnsQuery dnsQuery = localChannel.attr(AttributeKey.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
  2. 打开命令行工具,执行DNS缓存清除命令 ipconfig/flushdns

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

参考资料

  • 用 Node.js 手写一个 DNS 服务器
  • DNS中有哪些值得学习的优秀设计
  • netty dns example

关键词: 代理服务器 有哪些优势 负载均衡