netty系列之: 在netty中使用 tls 協(xié)議請(qǐng)求 DNS 服務(wù)器

簡(jiǎn)介

在前面的文章中我們講過(guò)了如何在netty中構(gòu)造客戶端分別使用tcp和udp協(xié)議向DNS服務(wù)器請(qǐng)求消息。在請(qǐng)求的過(guò)程中并沒(méi)有進(jìn)行消息的加密错妖,所以這種請(qǐng)求是不安全的。

那么有同學(xué)會(huì)問(wèn)了格粪,就是請(qǐng)求解析一個(gè)域名的IP地址而已舍哄,還需要安全通訊嗎?

事實(shí)上普舆,不加密的DNS查詢消息是很危險(xiǎn)的恬口,如果你在訪問(wèn)一個(gè)重要的網(wǎng)站時(shí)候,DNS查詢消息被監(jiān)聽(tīng)或者篡改沼侣,有可能你收到的查詢返回IP地址并不是真實(shí)的地址祖能,而是被篡改之后的地址,從而打開(kāi)了釣魚(yú)網(wǎng)站或者其他惡意的網(wǎng)站蛾洛,從而造成了不必要的損失养铸。

所以DNS查詢也是需要保證安全的。

幸運(yùn)的是在DNS的傳輸協(xié)議中特意指定了一種加密的傳輸協(xié)議叫做DNS-over-TLS轧膘,簡(jiǎn)稱(chēng)("DoT")钞螟。

那么在netty中可以使用DoT來(lái)進(jìn)行DNS服務(wù)查詢嗎?一起來(lái)看看吧谎碍。

支持DoT的DNS服務(wù)器

因?yàn)镈NS中有很多傳輸協(xié)議規(guī)范鳞滨,但并不是每個(gè)DNS服務(wù)器都支持所有的規(guī)范,所以我們?cè)谑褂肈oT之前需要找到一個(gè)能夠支持DoT協(xié)議的DNS服務(wù)器蟆淀。

這里我還是選擇使用阿里DNS服務(wù)器:

223.5.5.5

之前使用TCP和UDP協(xié)議的時(shí)候查詢的DNS端口是53太援,如果換成了DoT,那么端口就需要變成853扳碍。

搭建支持DoT的netty客戶端

DoT的底層還是TCP協(xié)議提岔,也就是說(shuō)TLS over TCP,所以我們需要使用NioEventLoopGroup和NioSocketChannel來(lái)搭建netty客戶端笋敞,如下所示:

EventLoopGroup group = new NioEventLoopGroup();
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new DotChannelInitializer(sslContext, dnsServer, dnsPort));
            final Channel ch = b.connect(dnsServer, dnsPort).sync().channel();

這里選擇的是NioEventLoopGroup和NioSocketChannel碱蒙。然后向Bootstrap中傳入自定義的DotChannelInitializer即可。

DotChannelInitializer中包含了自定義的handler和netty自帶的handler。

我們來(lái)看下DotChannelInitializer的定義和他的構(gòu)造函數(shù):

class DotChannelInitializer extends ChannelInitializer<SocketChannel> {

    public DotChannelInitializer(SslContext sslContext, String dnsServer, int dnsPort) {
        this.sslContext = sslContext;
        this.dnsServer = dnsServer;
        this.dnsPort = dnsPort;
    }

DotChannelInitializer需要三個(gè)參數(shù)分別是sslContext赛惩,dnsServer和dnsPort哀墓。

這三個(gè)參數(shù)都是在sslContext中使用的:

    protected void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();
        p.addLast(sslContext.newHandler(ch.alloc(), dnsServer, dnsPort))
                .addLast(new TcpDnsQueryEncoder())
                .addLast(new TcpDnsResponseDecoder())
                .addLast(new DotChannelInboundHandler());
    }

SslContext主要用來(lái)進(jìn)行TLS配置,下面是SslContext的定義:

SslProvider provider =
                    SslProvider.isAlpnSupported(SslProvider.OPENSSL)? SslProvider.OPENSSL : SslProvider.JDK;
            final SslContext sslContext = SslContextBuilder.forClient()
                    .sslProvider(provider)
                    .protocols("TLSv1.3", "TLSv1.2")
                    .build();

因?yàn)镾slProvider有很多種喷兼,可以選擇openssl篮绰,也可以選擇JDK自帶的。

這里我們使用的openssl,要想提供openssl的支持季惯,我們還需要提供openssl的依賴包如下:

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-tcnative</artifactId>
            <version>2.0.51.Final</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-tcnative-boringssl-static</artifactId>
            <version>2.0.51.Final</version>
        </dependency>

有了provider之后吠各,就可以調(diào)用SslContextBuilder.forClient方法來(lái)創(chuàng)建SslContext。

這里我們指定SSL的protocol是"TLSv1.3"和"TLSv1.2"勉抓。

然后再調(diào)用sslContext的newHandler方法就創(chuàng)建好了支持ssl的handler:

sslContext.newHandler(ch.alloc(), dnsServer, dnsPort)

newHandler還需要指定dnsServer和dnsPort信息贾漏。

處理完ssl,接下來(lái)就是對(duì)dns查詢和響應(yīng)的編碼解碼器藕筋,這里使用的是TcpDnsQueryEncoder和TcpDnsResponseDecoder纵散。

TcpDnsQueryEncoder和TcpDnsResponseDecoder在之前介紹使用netty搭建tcp客戶端的時(shí)候就已經(jīng)詳細(xì)解說(shuō)過(guò)了,這里就不再進(jìn)行講解了隐圾。

編碼解碼之后伍掀,就是自定義的消息處理器DotChannelInboundHandler:

class DotChannelInboundHandler extends SimpleChannelInboundHandler<DefaultDnsResponse> 

DotChannelInboundHandler中定義了消息的具體處理方法:

    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);
            if (record.type() == DnsRecordType.A) {
                //A記錄用來(lái)指定主機(jī)名或者域名對(duì)應(yīng)的IP地址
                DnsRawRecord raw = (DnsRawRecord) record;
                log.info("ip address is: {}",NetUtil.bytesToIpAddress(ByteBufUtil.getBytes(raw.content())));
            }
            i++;
        }
    }

讀取的邏輯很簡(jiǎn)單,先從DefaultDnsResponse中讀取QUESTION暇藏,打印出來(lái)蜜笤,然后再讀取它的ANSWER,因?yàn)檫@里是A address叨咖,所以調(diào)用NetUtil.bytesToIpAddress方法將ANSWER轉(zhuǎn)換為ip地址打印出來(lái)瘩例。

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

INFO  c.f.dnsdot.DotChannelInboundHandler - question is :DefaultDnsQuestion(www.flydean.com. IN A)
INFO  c.f.dnsdot.DotChannelInboundHandler - ip address is: 47.107.98.187

TLS的客戶端請(qǐng)求

我們創(chuàng)建好channel之后啊胶,就需要向DNS server端發(fā)送查詢請(qǐng)求了甸各。因?yàn)槭荄oT,那么和普通的TCP查詢有什么區(qū)別呢焰坪?

答案是并沒(méi)有什么區(qū)別趣倾,因?yàn)門(mén)LS的操作SslHandler我們已經(jīng)在handler中添加了。所以這里的查詢和普通查詢沒(méi)什么區(qū)別某饰。

int randomID = (int) (System.currentTimeMillis() / 1000);
            DnsQuery query = new DefaultDnsQuery(randomID, DnsOpCode.QUERY)
                    .setRecord(DnsSection.QUESTION, new DefaultDnsQuestion(queryDomain, DnsRecordType.A));
            ch.writeAndFlush(query).sync();
            boolean result = ch.closeFuture().await(10, TimeUnit.SECONDS);
            if (!result) {
                log.error("DNS查詢失敗");
                ch.close().sync();
            }

同樣我們需要構(gòu)建一個(gè)DnsQuery儒恋,這里使用的是DefaultDnsQuery,通過(guò)傳入一個(gè)randomID和opcode即可黔漂。

因?yàn)槭遣樵兘刖。赃@里的opcode是DnsOpCode.QUERY。

然后需要向QUESTION section中添加一個(gè)DefaultDnsQuestion炬守,用來(lái)查詢具體的域名和類(lèi)型牧嫉。

這里的queryDomain是www.flydean.com,查詢類(lèi)型是A,表示的是對(duì)域名進(jìn)行IP解析。

最后將得到的query,寫(xiě)入到channel中即可酣藻。

總結(jié)

這里我們使用netty構(gòu)建了一個(gè)基于TLS的DNS查詢客戶端曹洽,除了添加TLS handler之外,其他操作和普通的TCP操作類(lèi)似辽剧。但是要注意的是送淆,要想客戶端可以正常工作,我們需要請(qǐng)求支持DoT協(xié)議的DNS服務(wù)器才可以怕轿。

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

learn-netty4

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市撤卢,隨后出現(xiàn)的幾起案子环凿,更是在濱河造成了極大的恐慌,老刑警劉巖放吩,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件智听,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡渡紫,警方通過(guò)查閱死者的電腦和手機(jī)到推,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惕澎,“玉大人莉测,你說(shuō)我怎么就攤上這事∵蠛恚” “怎么了捣卤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)八孝。 經(jīng)常有香客問(wèn)我董朝,道長(zhǎng),這世上最難降的妖魔是什么干跛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任子姜,我火速辦了婚禮,結(jié)果婚禮上楼入,老公的妹妹穿的比我還像新娘哥捕。我一直安慰自己,他們只是感情好嘉熊,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布遥赚。 她就那樣靜靜地躺著,像睡著了一般阐肤。 火紅的嫁衣襯著肌膚如雪凫佛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音御蒲,去河邊找鬼衣赶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厚满,可吹牛的內(nèi)容都是我干的府瞄。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼碘箍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼遵馆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起丰榴,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤货邓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后四濒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體换况,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年盗蟆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了戈二。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喳资,死狀恐怖觉吭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仆邓,我是刑警寧澤鲜滩,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站节值,受9級(jí)特大地震影響徙硅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜察署,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一闷游、第九天 我趴在偏房一處隱蔽的房頂上張望峻汉。 院中可真熱鬧贴汪,春花似錦、人聲如沸休吠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瘤礁。三九已至阳懂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背岩调。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工巷燥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人号枕。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓缰揪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親葱淳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钝腺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容