1. 程式人生 > >用C++抓取網路資料包--嗅探器的設計原理

用C++抓取網路資料包--嗅探器的設計原理

嗅探器作為一種網路通訊程式,也是通過對網絡卡的程式設計來實現網路通訊的,對網絡卡的程式設計也是使用通常的套接字(socket)方式來進行。但是,通常的套接字程式只能響應與自己硬體地址相匹配的或是以廣播形式發出的資料幀,對於其他形式的資料幀比如已到達網路介面但卻不是發給此地址的資料幀,網路介面在驗證投遞地址並非自身地址之後將不引起響應,也就是說應用程式無法收取到達的資料包。而網路嗅探器的目的恰恰在於從網絡卡接收所有經過它的資料包,這些資料包即可以是發給它的也可以是發往別處的。顯然,要達到此目的就不能再讓網絡卡按通常的正常模式工作,而必須將其設定為混雜模式。
具體到程式設計實現上,這種對網絡卡混雜模式的設定是通過原始套接字(raw socket)來實現的,這也有別於通常經常使用的資料流套接字和資料報套接字。在建立了原始套接字後,需要通過setsockopt()函式來設定IP頭操作選項,然後再通過bind()函式將原始套接字繫結到本地網絡卡。為了讓原始套接字能接受所有的資料,還需要通過ioctlsocket()來進行設定,而且還可以指定是否親自處理IP頭。至此,實際就可以開始對網路資料包進行嗅探了,對資料包的獲取仍象流式套接字或資料報套接字那樣通過recv()函式來完成。但是與其他兩種套接字不同的是,原始套接字此時捕獲到的資料包並不僅僅是單純的資料資訊,而是包含有 IP頭、 TCP頭等資訊頭的最原始的資料資訊,這些資訊保留了它在網路傳輸時的原貌。通過對這些在低層傳輸的原始資訊的分析可以得到有關網路的一些資訊。由於這些資料經過了網路層和傳輸層的打包,因此需要根據其附加的幀頭對資料包進行分析。下面先給出結構.資料包的總體結構:
資料包
IP頭 TCP頭(或其他資訊頭) 資料

資料在從應用層到達傳輸層時,將新增TCP資料段頭,或是UDP資料段頭。其中UDP資料段頭比較簡單,由一個8位元組的頭和資料部分組成,具體格式如下:
16位 16位
源埠 目的埠
UDP長度 UDP校驗和

而TCP資料頭則比較複雜,以20個固定位元組開始,在固定頭後面還可以有一些長度不固定的可選項,下面給出TCP資料段頭的格式組成:
16位 16位
源埠 目的埠
順序號
確認號
TCP頭長 (保留)7位 URG ACK PSH RST SYN FIN 視窗大小
校驗和 緊急指標
可選項(0或更多的32位字)
資料(可選項)

對於此TCP資料段頭的分析在程式設計實現中可通過資料結構_TCP來定義:
typedef struct _TCP{ WORD SrcPort; // 源埠
WORD DstPort; // 目的埠
DWORD SeqNum; // 順序號
DWORD AckNum; // 確認號
BYTE DataOff; // TCP頭長
BYTE Flags; // 標誌(URG、ACK等)
WORD Window; // 視窗大小
WORD Chksum; // 校驗和
WORD UrgPtr; // 緊急指標
} TCP;
typedef TCP *LPTCP;
typedef TCP UNALIGNED * ULPTCP;



在網路層,還要給TCP資料包新增一個IP資料段頭以組成IP資料報。IP資料頭以大端點機次序傳送,從左到右,版本欄位的高位位元組先傳輸(SPARC是大端點機;Pentium是小端點機)。如果是小端點機,就要在傳送和接收時先行轉換然後才能進行傳輸。IP資料段頭格式如下:
16位 16位
版本 IHL 服務型別 總長
標識 標誌 分段偏移
生命期 協議 頭校驗和
源地址
目的地址
選項(0或更多)

同樣,在實際程式設計中也需要通過一個數據結構來表示此IP資料段頭,下面給出此資料結構的定義:
typedef struct _IP{
union{ BYTE Version; // 版本
BYTE HdrLen; // IHL
};
BYTE ServiceType; // 服務型別
WORD TotalLen; // 總長
WORD ID; // 標識
union{ WORD Flags; // 標誌
WORD FragOff; // 分段偏移
};
BYTE TimeToLive; // 生命期
BYTE Protocol; // 協議
WORD HdrChksum; // 頭校驗和
DWORD SrcAddr; // 源地址
DWORD DstAddr; // 目的地址
BYTE Options; // 選項
} IP; 
typedef IP * LPIP;
typedef IP UNALIGNED * ULPIP;



在明確了以上幾個資料段頭的組成結構後,就可以對捕獲到的資料包進行分析了。