利用Java實(shí)現(xiàn)輕松解析DNS報(bào)文
前言
最近在做網(wǎng)絡(luò)包分析的一個(gè)功能,其中有一點(diǎn)就是要判斷抓包期間應(yīng)用是否有存在異常的DNS解析行為,并且要寫在一個(gè)使用Java語言的后端服務(wù)中,由于之前使用Go語言做過類似的事情,所以我第一反應(yīng)就是這個(gè)應(yīng)該實(shí)現(xiàn)起來比較簡單,但是實(shí)際上手后,發(fā)現(xiàn)在Java中要拿到DNS報(bào)文還沒那么容易,當(dāng)然也有可能是我沒有找到正確的工具,如果有方便的工具,歡迎在評論區(qū)留言。那么本文就結(jié)合我如何從網(wǎng)絡(luò)包中拿到DNS報(bào)文,進(jìn)行一個(gè)簡單介紹。
一. DNS報(bào)文格式簡析
DNS報(bào)文本質(zhì)是UDP報(bào)文,UDP報(bào)文的格式如下所示。
而DNS相關(guān)內(nèi)容,就體現(xiàn)在UDP報(bào)文載荷中。DNS報(bào)文分為DNS請求報(bào)文和DNS響應(yīng)報(bào)文,兩種報(bào)文結(jié)構(gòu)一樣,只是內(nèi)容稍有差別,如下是DNS報(bào)文的格式。
DNS報(bào)文內(nèi)容包含如下三個(gè)部分。
第一部分是基礎(chǔ)結(jié)構(gòu)部分,解釋如下。
- Transaction ID。DNS報(bào)文的事務(wù)ID標(biāo)識,DNS請求報(bào)文和DNS響應(yīng)報(bào)文的Transaction ID是一樣的,故可以通過這個(gè)ID關(guān)聯(lián)DNS請求和響應(yīng)報(bào)文;
- Flags。DNS報(bào)文的標(biāo)志,標(biāo)志由若干個(gè)含義不同的字段組成,較為常用的是第0位可以表示當(dāng)前是請求DNS報(bào)文還是響應(yīng)DNS報(bào)文,第12-15位可以表示響應(yīng)DNS報(bào)文的Reply Code;
- Questions。問題數(shù),表示后面Queries的數(shù)量;
- Answer RRs。回答資源記錄數(shù),表示后面Answers的數(shù)量;
- Authority RRs。授權(quán)資源記錄數(shù),表示后面Authoritative nameservers的數(shù)量;
- Additional RRs。附加資源記錄數(shù),表示后面Additional records的數(shù)量。
第二部分是問題部分,對應(yīng)Queries,該部分表示DNS查詢請求的問題信息,包含查詢的域名Name,查詢的類型Type和查詢的類Class,解釋如下。
Name。就是請求DNS解析的域名地址,這里的Name是一個(gè)不定長的字段,格式示意如下。
Type。表示DNS查詢的資源類型,Name字段結(jié)束后的兩個(gè)字節(jié)就是Type,通常關(guān)注較多的有0x0001,助記符是A,表示通過域名查詢IPv4地址,以及0x001C,助記符是AAAA,表示通過域名查詢IPv6地址;
Class。表示地址類型,通常為0x0001,表示互聯(lián)網(wǎng)地址。
第三部分是資源記錄部分,解釋如下。
- Answers。記錄域名解析出來的地址信息;
- Authoritative nameservers。記錄解析該域名對應(yīng)的權(quán)威名稱服務(wù)器信息;
- Additional records。記錄解析該域名對應(yīng)的一些附加信息。
二. DNS報(bào)文解析實(shí)現(xiàn)
先回顧一下需求,就是需要得到應(yīng)用是否存在異常的DNS解析,那么結(jié)合上面的DNS報(bào)文格式,我們的判斷邏輯可以像下面這樣。
- 先找到只有請求DNS報(bào)文但沒有響應(yīng)DNS報(bào)文的DNS解析,這是一種異常的DNS解析情況,即DNS服務(wù)器沒有響應(yīng)解析請求;
- 拿到響應(yīng)DNS報(bào)文的Reply Code,然后根據(jù)Reply Code得到異常的DNS解析情況,Reply Code的枚舉如下所示。
Reply Code | 說明 |
---|---|
0 | 正常 |
1 | 報(bào)文格式錯誤 |
2 | 域名服務(wù)器異常 |
3 | 域名不存在 |
4 | 解析類型Type不支持 |
5 | 域名服務(wù)器拒絕請求 |
計(jì)算DNS解析的請求和響應(yīng)報(bào)文的時(shí)間差,得到解析耗時(shí)過長的異常情況。
那么其實(shí)我們需要的DNS報(bào)文的內(nèi)容就很明確了,如下所示。
1. Transaction ID,事務(wù)ID;
2. Reply Code,響應(yīng)碼;
3. Name,域名;
4. Type,解析類型。
現(xiàn)在就開始本文的正題,如何使用Java語言來解析網(wǎng)絡(luò)包并得到DNS報(bào)文。在Go語言中,可以使用google/gopacket來方便的拿到DNS報(bào)文,但是在Java中,要一步拿到DNS報(bào)文尚有點(diǎn)困難,但是可以基于如下步驟來操作。
- 基于io.pkts的工具包來解析網(wǎng)絡(luò)包并拿到UDP報(bào)文;
- 過濾出源或目標(biāo)端口號為53的UDP報(bào)文,這是因?yàn)橥ǔ?strong>DNS服務(wù)器會工作在53號端口上;
- 拿到過濾后的UDP報(bào)文的載荷,按照DNS報(bào)文的格式解析得到Transaction ID,Reply Code,Name和Type。
現(xiàn)在進(jìn)行實(shí)操。先引入io.pkts的依賴,如下所示。
<dependency> <groupId>io.pkts</groupId> <artifactId>pkts-streams</artifactId> <version>3.0.10</version> </dependency> <dependency> <groupId>io.pkts</groupId> <artifactId>pkts-core</artifactId> <version>3.0.10</version> </dependency>
然后基于Spring的MultipartFile來上傳網(wǎng)絡(luò)包并使用io.pkts工具解析出UDP報(bào)文,實(shí)現(xiàn)如下所示。
@RestController public class FileUpload { @PostMapping("/upload/udp") public void uploadUdp(MultipartFile uploadFile) throws IOException { try (InputStream inputStream = uploadFile.getInputStream()) { GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream); Pcap pcap = Pcap.openStream(gzipInputStream); pcap.loop(packet -> { if (packet.hasProtocol(Protocol.UDP)) { UDPPacket udpPacket = (UDPPacket) packet.getPacket(Protocol.UDP); if (udpPacket.getSourcePort() == 53 || udpPacket.getDestinationPort() == 53) { final byte[] payloadByteArray = udpPacket.getPayload().getArray(); // 在這里根據(jù)DNS報(bào)文格式解析數(shù)據(jù) ...... } } return true; }); } } }
解析Transaction ID的邏輯如下所示。
private String parseTransactionId(byte[] array) { // 前兩字節(jié)是Transaction ID,表示會話標(biāo)識 // DNS請求報(bào)文和響應(yīng)報(bào)文通過Transaction ID進(jìn)行匹配 return HexUtils.toHexString(Arrays.copyOfRange(array, 0, 2)); }
解析請求或響應(yīng)報(bào)文類型的邏輯如下所示。
private int parseDnsPackageType(byte[] array) { // 第3和第4字節(jié)是Flags,表示標(biāo)志 // Flags的第0位代表請求或響應(yīng)的類型 // 0表示DNS請求報(bào)文,1表示DNS響應(yīng)報(bào)文 String binaryString = String.format("%08d", Integer.parseInt(Integer.toBinaryString(array[2] & 0xFF))); return Integer.parseInt(binaryString.substring(0, 1), 2); }
解析Reply Code的邏輯如下所示。
private int parseDnsRcode(byte[] array) { // 第3和第4字節(jié)是Flags,表示標(biāo)志 // Flags的最后四位表示Reply Code String binaryString = String.format("%08d", Integer.parseInt(Integer.toBinaryString(array[3] & 0xFF))); return Integer.parseInt(binaryString.substring(4, 8), 2); }
解析Type的邏輯如下所示。
private int parseDnsQueryType(byte[] array) { // 第13字節(jié)開始,是域名,域名以0x00結(jié)尾 // 域名結(jié)束后的兩個(gè)字節(jié)就代表DNS查詢類型 int domainEndIndex = -1; for (int i = 12; i < array.length; i++) { if ((array[i] & 0xFF) == 0) { domainEndIndex = i; break; } } String s = HexUtils.toHexString(Arrays.copyOfRange(array, domainEndIndex + 1, domainEndIndex + 3)); return Integer.parseInt(s, 16); }
解析Name的邏輯如下所示。
private String parseDnsQueryDomain(byte[] array) { // 從第13字節(jié)開始,遵循[域名長度][域名][域名長度][域名]...0x00的規(guī)律 // 故按照上述規(guī)律,從第13字節(jié)開始,將域名的所有組成部分獲取出來并拼接 List<String> domainParts = new ArrayList<>(); int lengthIndex = 12; do { int partLength = array[lengthIndex] & 0xFF; String s = new String(Arrays.copyOfRange(array, lengthIndex + 1, lengthIndex + partLength + 2)).trim(); domainParts.add(s); lengthIndex = lengthIndex + partLength + 1; } while ((array[lengthIndex] & 0xFF) != 0); return String.join(".", domainParts); }
那么至此我們期望得到的DNS報(bào)文的數(shù)據(jù),我們就拿到了,后續(xù)就是將這些數(shù)據(jù)組裝為一個(gè)Entity來方便我們在程序中使用和處理,這里就不再演示了。
總結(jié)
我使用Java語言從網(wǎng)絡(luò)包中解析出DNS報(bào)文的步驟總結(jié)如下。
- 使用io.pkts解開網(wǎng)絡(luò)包并過濾得到UDP報(bào)文;
- 過濾出源或目標(biāo)端口號為53的UDP報(bào)文;
- 拿到過濾后的UDP報(bào)文的載荷,按照DNS報(bào)文的格式解析得到想要的DNS數(shù)據(jù)。
以上就是利用Java實(shí)現(xiàn)輕松解析DNS報(bào)文的詳細(xì)內(nèi)容,更多關(guān)于Java解析DNS報(bào)文的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java使用正則表達(dá)式提取XML節(jié)點(diǎn)內(nèi)容的方法示例
這篇文章主要介紹了Java使用正則表達(dá)式提取XML節(jié)點(diǎn)內(nèi)容的方法,結(jié)合具體實(shí)例形式分析了java針對xml格式字符串的正則匹配相關(guān)操作技巧,需要的朋友可以參考下2017-08-08java實(shí)現(xiàn)ip地址與十進(jìn)制數(shù)相互轉(zhuǎn)換
本文介紹在java中IP地址轉(zhuǎn)換十進(jìn)制數(shù)及把10進(jìn)制再轉(zhuǎn)換成IP地址的方法及實(shí)例參考,曬出來和大家分享一下2012-12-12springboot mybatis里localdatetime序列化問題的解決
這篇文章主要介紹了springboot mybatis里localdatetime序列化問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10Java多線程批量數(shù)據(jù)導(dǎo)入的方法詳解
這篇文章主要介紹了Java多線程批量數(shù)據(jù)導(dǎo)入的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面小編和大家來一起學(xué)習(xí)下吧2019-06-06Java數(shù)據(jù)結(jié)構(gòu)之LinkedList從鏈表到實(shí)現(xiàn)
LinkedList是Java中常用的數(shù)據(jù)結(jié)構(gòu)之一,實(shí)現(xiàn)了鏈表的特性,支持快速添加、刪除元素,可以用于實(shí)現(xiàn)隊(duì)列、棧、雙向隊(duì)列等數(shù)據(jù)結(jié)構(gòu)。LinkedList的內(nèi)部實(shí)現(xiàn)采用了雙向鏈表,其中每個(gè)節(jié)點(diǎn)都包含前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)的引用,可以直接訪問鏈表的頭尾元素2023-04-04