使用go解析二進制tcp數據包

tcp全名是傳輸控制協議,tcp協議在ip協議基礎上增長了數據包完整性檢查等保證傳輸完整性的機制,使其在如今的數據領域獲得了普遍的應用html

按照下面的步驟能夠快速瞭解tcp數據包中包含的信息git

tcp協議rfc文檔解讀

rfc參考:tools.ietf.org/html/rfc793github

核心tcp數據包結構以下 bash

能夠看到tcp報文由十餘個字段組成,最後一個data字段表明了本次tcp數據報文承載的數據,這個數據通常是應用層的數據,好比http報文數據就是在這個tcp包的data字段中

其中經常使用字段以下app

字段 做用
Source Port 發包機器的端口號
Destination Port 收包機器的端口號
Sequence Number 包編號
Acknowledgment Number 確認包號
urg/ack/psh/rst/syn/fin 標誌位,設置是/否的操做標誌
Window 流量控制窗口
Checksum 包完整性校驗

注意:客戶端和服務端使用獨立的包編號計數器器 checksum服務端和客戶端會分別計算,客戶端依靠這個值判斷tcp包在傳輸過程當中是否被異常改變/篡改tcp

獲取案例數據包

可使用wireshark獲取一個tcp數據包,在tcp層單擊右鍵->複製->as a Hex Stream便可 ui

這裏獲得的tcp層包數據以下google

1f90f04f3747d146dcae23f3801831bf14ef00000101080a3176450b31764503
複製代碼

下面就能夠編寫程序從這個tcp的16進制中解析出報文的具體數據了spa

解析tcp數據報文

二進制數字位數和16進制位數換算關係:1個16進制數能夠表示4個二進制數 好比二進制:00011111 10010000 可使用16進製表示爲:1f90code

可使用下面的代碼將16進制轉成二進制字符串

func hex2bin(hex string) string {
	var bin string

	for i := 0; i < len(hex); i++ {
		hex2int, _ := strconv.ParseInt(string(hex[i]), 16, 64)
		bin = bin + fmt.Sprintf("%04b", hex2int)
	}

	return bin
}
複製代碼

而後就能夠按二進制位讀取tcp數據報信息了,參考代碼以下

func main() {
	atcp := "1f90f04f3747d146dcae23f3801831bf14ef00000101080a3176450b31764503"
	bintcp := hex2bin(atcp)

	sourcePort, _  := strconv.ParseInt(bintcp[0:16], 2, 64)
	fmt.Printf("sourcePort is %d \n", sourcePort)

	destinationPort, _  := strconv.ParseInt(bintcp[16:32], 2, 64)
	fmt.Printf("destinationPort is %d \n", destinationPort)

	sequenceNumber, _  := strconv.ParseInt(bintcp[32:64], 2, 64)
	fmt.Printf("sequenceNumber is %d \n", sequenceNumber)

	acknowledgmentNumber, _  := strconv.ParseInt(bintcp[64:96], 2, 64)
	fmt.Printf("acknowledgmentNumber is %d \n", acknowledgmentNumber)

	dataOffset, _  := strconv.ParseInt(bintcp[96:100], 2, 64)
	fmt.Printf("dataOffset is %d \n", dataOffset)

	reserved, _  := strconv.ParseInt(bintcp[100:106], 2, 64)
	fmt.Printf("reserved is %d \n", reserved)

	// Control Bits 控制位,從106-1012共有6位,每位表示一個控制位的開關
	urg, _ := strconv.ParseInt(bintcp[106:107], 2, 64)
	ack, _ := strconv.ParseInt(bintcp[107:108], 2, 64)
	psh, _ := strconv.ParseInt(bintcp[108:109], 2, 64)
	rst, _ := strconv.ParseInt(bintcp[109:110], 2, 64)
	syn, _ := strconv.ParseInt(bintcp[110:111], 2, 64)
	fin, _ := strconv.ParseInt(bintcp[111:112], 2, 64)
	fmt.Printf("控制位標識以下:\n")
	fmt.Printf(" urg: %d\n", urg)
	fmt.Printf(" ack: %d\n", ack)
	fmt.Printf(" psh: %d\n", psh)
	fmt.Printf(" rst: %d\n", rst)
	fmt.Printf(" syn: %d\n", syn)
	fmt.Printf(" fin: %d\n", fin)

	// 數據窗口 16位
	window, _  := strconv.ParseInt(bintcp[112:128], 2, 64)
	fmt.Printf("window is %d \n", window)

	// checksum 16位
	checksum, _  := strconv.ParseInt(bintcp[128:144], 2, 64)
	fmt.Printf("checksum is %d \n", checksum)

	// urgentPointer
	urgentPointer, _ := strconv.ParseInt(bintcp[144:160], 2, 64)
	fmt.Printf("urgentPointer is %d \n", urgentPointer)

	// options and padding
	optionsAndPaddings := bintcp[160:]
	fmt.Printf("optionsAndPaddings is %s \n", optionsAndPaddings)

	fmt.Printf("tcp raw data is %s \n", atcp)
	fmt.Printf("tcp bin data is %s \n", bintcp)
	fmt.Printf("tcp bin data length is %d\n", len(bintcp))
}
複製代碼

執行效果以下

wireshark解析結果以下

能夠看到解析是ok的

一些注意的點

wireshark中複製的tcp包數據是16進制的,可是tcp協議中,部分字段僅佔用一位,16進制是2進制的4的整數倍,直接解析16進制會致使tcp中的某些字段沒法獲取,須要先轉成二進制進行處理

tcp包數據最終會進行32位對齊,整個tcp數據包大小若是不是正好是32位長度的整數倍,會用0在末尾填充到32位整數倍

tcp協議在rfc3168中新增了cwr和ece標誌位,能夠參考:tools.ietf.org/html/rfc316…

抓包可使用tcpdump: tcpdump -n -XX -i lo0 -s0 'tcp port 8080'

參考資料

  1. github.com/google/pack…
  2. klamath.stanford.edu/~nickm/pape…
  3. pcapplusplus.github.io/docs/tutori…
  4. tools.ietf.org/html/rfc793…
  5. forums.ni.com/t5/LabVIEW/…
相關文章
相關標籤/搜索