日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

目錄
  • 簡介
  • DNS傳輸協議簡介
  • DNS的IP地址
  • Do53/TCP在netty中的使用
    • 搭建DNS netty client
    • 發送DNS查詢消息
    • DNS查詢的消息處理
  • 總結

    簡介

    DNS的全稱domain name system,既然是一個系統就有客戶端和服務器之分。一般情況來說我們并不需要感知這個DNS客戶端的存在,因為我們在瀏覽器訪問某個域名的時候,瀏覽器作為客戶端已經實現了這個工作。

    但是有時候我們沒有使用瀏覽器,比如在netty環境中,如何構建一個DNS請求呢?

    DNS傳輸協議簡介

    在RFC的規范中,DNS傳輸協議有很多種,如下所示:

    • DNS-over-UDP/53簡稱"Do53",是使用UDP進行DNS查詢傳輸的協議。
    • DNS-over-TCP/53簡稱"Do53/TCP",是使用TCP進行DNS查詢傳輸的協議。
    • DNSCrypt,對DNS傳輸協議進行加密的方法。
    • DNS-over-TLS簡稱"DoT",使用TLS進行DNS協議傳輸。
    • DNS-over-HTTPS簡稱"DoH",使用HTTPS進行DNS協議傳輸。
    • DNS-over-TOR,使用VPN或者tunnels連接DNS。

    這些協議都有對應的實現方式,我們先來看下Do53/TCP,也就是使用TCP進行DNS協議傳輸。

    DNS的IP地址

    先來考慮一下如何在netty中使用Do53/TCP協議,進行DNS查詢。

    因為DNS是客戶端和服務器的模式,我們需要做的是構建一個DNS客戶端,向已知的DNS服務器端進行查詢。

    已知的DNS服務器地址有哪些呢?

    除了13個root DNS IP地址以外,還出現了很多免費的公共DNS服務器地址,比如我們常用的阿里DNS,同時提供了IPv4/IPv6 DNS和DoT/DoH服務。

    IPv4: 
    223.5.5.5
    
    223.6.6.6
    
    IPv6: 
    2400:3200::1
    
    2400:3200:baba::1
    
    DoH 地址: 
    https://dns.alidns.com/dns-query
    
    DoT 地址: 
    dns.alidns.com

    再比如百度DNS,提供了一組IPv4和IPv6的地址:

    IPv4: 
    180.76.76.76
    
    IPv6: 
    2400:da00::6666

    還有114DNS:

    114.114.114.114
    114.114.115.115

    當然還有很多其他的公共免費DNS,這里我選擇使用阿里的IPv4:223.5.5.5為例。

    有了IP地址,我們還需要指定netty的連接端口號,這里默認的是53。

    然后就是我們要查詢的域名了,這里以www.flydean.com為例。

    你也可以使用你系統中配置的DNS解析地址,以mac為例,可以通過nslookup進行查看本地的DNS地址:

    nslookup  www.flydean.com
    Server:		8.8.8.8
    Address:	8.8.8.8#53
    
    Non-authoritative answer:
    www.flydean.com	canonical name = flydean.com.
    Name:	flydean.com
    Address: 47.107.98.187

    Do53/TCP在netty中的使用

    有了DNS Server的IP地址,接下來我們需要做的就是搭建netty client,然后向DNS server端發送DNS查詢消息。

    搭建DNS netty client

    因為我們進行的是TCP連接,所以可以借助于netty中的NIO操作來實現,也就是說我們需要使用NioEventLoopGroup和NioSocketChannel來搭建netty客戶端:

    final String dnsServer = "223.5.5.5";
            final int dnsPort = 53;
    EventLoopGroup group = new NioEventLoopGroup();
                Bootstrap b = new Bootstrap();
                b.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new Do53ChannelInitializer());
                final Channel ch = b.connect(dnsServer, dnsPort).sync().channel();

    netty中的NIO Socket底層使用的就是TCP協議,所以我們只需要像常用的netty客戶端服務一樣構建客戶端即可。

    然后調用Bootstrap的connect方法連接到DNS服務器,就建立好了channel連接。

    這里我們在handler中傳入了自定義的Do53ChannelInitializer,我們知道handler的作用是對消息進行編碼、解碼和對消息進行讀取。因為目前我們并不知道客戶端查詢的消息格式,所以Do53ChannelInitializer的實現我們在后面再進行詳細講解。

    發送DNS查詢消息

    netty提供了DNS消息的封裝,所有的DNS消息,包括查詢和響應都是DnsMessage的子類。

    每個DnsMessage都有一個唯一標記的ID,還有代表這個message類型的DnsOpCode。

    對于DNS來說,opCode有下面這幾種:

     public static final DnsOpCode QUERY = new DnsOpCode(0, "QUERY");
        public static final DnsOpCode IQUERY = new DnsOpCode(1, "IQUERY");
        public static final DnsOpCode STATUS = new DnsOpCode(2, "STATUS");
        public static final DnsOpCode NOTIFY = new DnsOpCode(4, "NOTIFY");
        public static final DnsOpCode UPDATE = new DnsOpCode(5, "UPDATE");

    因為每個DnsMessage都可能包含4個sections,每個section都以DnsSection來表示。因為有4個section,所以在DnsSection定義了4個section類型:

     QUESTION,
        ANSWER,
        AUTHORITY,
        ADDITIONAL;

    每個section里面又包含了多個DnsRecord, DnsRecord代表的就是Resource record,簡稱為RR,RR中有一個CLASS字段,下面是DnsRecord中CLASS字段的定義:

    int CLASS_IN = 1;
        int CLASS_CSNET = 2;
        int CLASS_CHAOS = 3;
        int CLASS_HESIOD = 4;
        int CLASS_NONE = 254;
        int CLASS_ANY = 255;

    DnsMessage是DNS消息的統一表示,對于查詢來說,netty中提供了一個專門的查詢類叫做DefaultDnsQuery。

    先來看下DefaultDnsQuery的定義和構造函數:

    public class DefaultDnsQuery extends AbstractDnsMessage implements DnsQuery {
    
            public DefaultDnsQuery(int id) {
            super(id);
        }
    
        public DefaultDnsQuery(int id, DnsOpCode opCode) {
            super(id, opCode);
        }

    DefaultDnsQuery的構造函數需要傳入id和opCode。

    我們可以這樣定義一個DNS查詢:

    int randomID = (int) (System.currentTimeMillis() / 1000);
                DnsQuery query = new DefaultDnsQuery(randomID, DnsOpCode.QUERY)

    既然是QEURY,那么還需要設置4個sections中的查詢section:

    query.setRecord(DnsSection.QUESTION, new DefaultDnsQuestion(queryDomain, DnsRecordType.A));

    這里調用的是setRecord方法向section中插入RR數據。

    這里的RR數據使用的是DefaultDnsQuestion。DefaultDnsQuestion的構造函數有兩個,一個是要查詢的domain name,這里就是"www.flydean.com",另外一個參數是dns記錄的類型。

    dns記錄的類型有很多種,在netty中有一個專門的類DnsRecordType表示,DnsRecordType中定義了很多個類型,如下所示:

    public class DnsRecordType implements Comparable<DnsRecordType> {
        public static final DnsRecordType A = new DnsRecordType(1, "A");
        public static final DnsRecordType NS = new DnsRecordType(2, "NS");
        public static final DnsRecordType CNAME = new DnsRecordType(5, "CNAME");
        public static final DnsRecordType SOA = new DnsRecordType(6, "SOA");
        public static final DnsRecordType PTR = new DnsRecordType(12, "PTR");
        public static final DnsRecordType MX = new DnsRecordType(15, "MX");
        public static final DnsRecordType TXT = new DnsRecordType(16, "TXT");
        ...

    因為類型比較多,我們挑選幾個常用的進行講解。

    • A類型,是address的縮寫,用來指定主機名或者域名對應的ip地址.
    • NS類型,是name server的縮寫,是域名服務器記錄,用來指定域名由哪個DNS服務器來進行解析。
    • MX類型,是mail exchanger的縮寫,是一個郵件交換記錄,用來根據郵箱的后綴來定位郵件服務器。
    • CNAME類型,是canonical name的縮寫,可以將多個名字映射到同一個主機.
    • TXT類型,用來表示主機或者域名的說明信息。

    以上幾個是我們經常會用到的dns record類型。

    這里我們選擇使用A,用來查詢域名對應的主機IP地址。

    構建好query之后,我們就可以使用netty client發送query指令到dns服務器了,具體的代碼如下:

    DnsQuery query = new DefaultDnsQuery(randomID, DnsOpCode.QUERY)
                        .setRecord(DnsSection.QUESTION, new DefaultDnsQuestion(queryDomain, DnsRecordType.A));
                ch.writeAndFlush(query).sync();

    DNS查詢的消息處理

    DNS的查詢消息我們已經發送出去了,接下來就是對消息的處理和解析了。

    還記得我們自定義的Do53ChannelInitializer嗎?看一下它的實現:

    class Do53ChannelInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel ch) {
            ChannelPipeline p = ch.pipeline();
            p.addLast(new TcpDnsQueryEncoder())
                    .addLast(new TcpDnsResponseDecoder())
                    .addLast(new Do53ChannelInboundHandler());
        }
    }

    我們向pipline中添加了兩個netty自帶的編碼解碼器TcpDnsQueryEncoder和TcpDnsResponseDecoder,還有一個自定義用來做消息解析的Do53ChannelInboundHandler。

    因為我們向channel中寫入的是DnsQuery,所以需要一個encoder將DnsQuery編碼為ByteBuf,這里使用的是netty提供的TcpDnsQueryEncoder:

    public final class TcpDnsQueryEncoder extends MessageToByteEncoder<DnsQuery> 

    TcpDnsQueryEncoder繼承自MessageToByteEncoder,表示將DnsQuery編碼為ByteBuf。

    看下他的encode方法:

     protected void encode(ChannelHandlerContext ctx, DnsQuery msg, ByteBuf out) throws Exception {
            out.writerIndex(out.writerIndex() + 2);
            this.encoder.encode(msg, out);
            out.setShort(0, out.readableBytes() - 2);
        }

    可以看到TcpDnsQueryEncoder在msg編碼之前存儲了msg的長度信息,所以是一個基于長度的對象編碼器。

    這里的encoder是一個DnsQueryEncoder對象。

    看一下它的encoder方法:

     void encode(DnsQuery query, ByteBuf out) throws Exception {
            encodeHeader(query, out);
            this.encodeQuestions(query, out);
            this.encodeRecords(query, DnsSection.ADDITIONAL, out);
        }

    DnsQueryEncoder會依次編碼header、questions和records。

    完成編碼之后,我們還需要從DNS server的返回中decode出DnsResponse,這里使用的是netty自帶的TcpDnsResponseDecoder:

    public final class TcpDnsResponseDecoder extends LengthFieldBasedFrameDecoder

    TcpDnsResponseDecoder繼承自LengthFieldBasedFrameDecoder,表示數據是以字段長度來進行分割的,這和我們剛剛將的encoder的格式類似。

    來看下他的decode方法:

     protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
            ByteBuf frame = (ByteBuf)super.decode(ctx, in);
            if (frame == null) {
                return null;
            } else {
                DnsResponse var4;
                try {
                    var4 = this.responseDecoder.decode(ctx.channel().remoteAddress(), ctx.channel().localAddress(), frame.slice());
                } finally {
                    frame.release();
                }
                return var4;
            }
        }

    decode方法先調用LengthFieldBasedFrameDecoder的decode方法將要解碼的內容提取出來,然后調用responseDecoder的decode方法,最終返回DnsResponse。

    這里的responseDecoder是一個DnsResponseDecoder。具體decoder的細節這里就不過多闡述了。感興趣的同學可以自行查閱代碼文檔。

    最后,我們得到了DnsResponse對象。

    接下來就是自定義的InboundHandler對消息進行解析了:

    class Do53ChannelInboundHandler extends SimpleChannelInboundHandler<DefaultDnsResponse> 

    在它的channelRead0方法中,我們調用了readMsg方法對消息進行處理:

     private static void readMsg(DefaultDnsResponse msg) {
            if (msg.count(DnsSection.QUESTION) > 0) {
                DnsQuestion question = msg.recordAt(DnsSection.QUESTION, 0);
                log.info("question is :{}",question);
            }
            int i = 0, count = msg.count(DnsSection.ANSWER);
            while (i < count) {
                DnsRecord record = msg.recordAt(DnsSection.ANSWER, i);
                //A記錄用來指定主機名或者域名對應的IP地址
                if (record.type() == DnsRecordType.A) {
                    DnsRawRecord raw = (DnsRawRecord) record;
                    log.info("ip address is: {}",NetUtil.bytesToIpAddress(ByteBufUtil.getBytes(raw.content())));
                }
                i++;
            }
        }

    DefaultDnsResponse是DnsResponse的一個實現,首先判斷msg中的QUESTION個數是否大于零。

    如果大于零,則打印出question的信息。

    然后再解析出msg中的ANSWER并打印出來。

    最后,我們可能得到這樣的輸出:

    INFO  c.f.dnstcp.Do53ChannelInboundHandler – question is :DefaultDnsQuestion(www.flydean.com. IN A)
    INFO  c.f.dnstcp.Do53ChannelInboundHandler – ip address is: 47.107.98.187

    總結

    以上就是使用netty創建DNS client進行TCP查詢的講解。

    本文的代碼,大家可以參考:

    learn-netty4

    分享到:
    標簽:協議 教你 服務器 請求 過程
    用戶無頭像

    網友整理

    注冊時間:

    網站:5 個   小程序:0 個  文章:12 篇

    • 51998

      網站

    • 12

      小程序

    • 1030137

      文章

    • 747

      會員

    趕快注冊賬號,推廣您的網站吧!
    最新入駐小程序

    數獨大挑戰2018-06-03

    數獨一種數學游戲,玩家需要根據9

    答題星2018-06-03

    您可以通過答題星輕松地創建試卷

    全階人生考試2018-06-03

    各種考試題,題庫,初中,高中,大學四六

    運動步數有氧達人2018-06-03

    記錄運動步數,積累氧氣值。還可偷

    每日養生app2018-06-03

    每日養生,天天健康

    體育訓練成績評定2018-06-03

    通用課目體育訓練成績評定