測試運行平台:CentOS 6.5發行版,內核版本3.11
1. Linux抓包源程序
在OSI七層模型中,網卡工作在物理層和數據鏈路層的MAC子層。
進行網絡通信時,源主機通過socket(或其它)應用程序產生IP報文,經過各個OSI層層封裝,數據包以Ethernet幀的形式進入物理層。Ethernet幀包含源主機地址、IP報文、目標地址(IP地址、端口號或映射的6字節MAC地址)和需要傳送到目標主機的其它信息。
目標的MAC地址是哪裡來的呢?這牽扯到一個ARP協議(介乎於網絡層和數據鏈路層的一個協議)。第一次傳送某個目的IP地址的數據的時候,先會發出一個ARP包,其MAC的目標地址是廣播地址,裡面說到:"誰是xxx.xxx.xxx.xxx這個IP地址的主人?"因為是廣播包,所有這個局域網的主機都收到了這個ARP請求。收到請求的主機將這個IP地址和自己的相比較,如果不相同就不予理會,如果相同就發出ARP響應包。這個IP地址的主機收到這個ARP請求包後回復的ARP響應裡說到:"我是這個IP地址的主人"。這個包裡面就包括了他的MAC地址。以後的給這個IP地址的幀的目標MAC地址就被確定了。
就這樣,以太網幀開始在數據鏈路層傳播。Ethernet幀在鏈路層基於廣播方式傳播,即網段內的所有網卡都能觀察該幀,但只有一個網卡通過對比6字節MAC地址發現與自己相符,然後它就接收該幀。而其它網卡則放棄該幀。(其它網卡也可以接受該幀,即實際的網絡Sniffer,可進行信息竊取等操作)
網卡得到Ethernet幀後,通過網絡驅動程序和上層協議對其進行還原操作,即層層剝離報文頭後將數據交由目標主機的socket(或其它)應用程序使用。
如果我們需要原始的以太網幀,以便觀察與目標主機進行通信的源主機信息,則可以通過建立基於PF_PACKET的socket應用程序實現。PF_PACKET協議簇允許應用程序直接獲得網絡驅動程序得到的數據幀信息。PF_PACKET支持SOCK_DGRAM和SOCK_RAW兩種socket類型,前者利用操作系統處理報文頭,而後者則將以太網幀直接交由應用程序處理。需要注意到是,只有root權限用戶才能使用PF_PACKET程序。
下面的代碼即可實現由應用程序直接獲得以太網幀的需求。
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#define BUFFER_MAX 2048
int main(int argc, char *argv[]){
int SOCKET_SRC;
char buf[BUFFER_MAX];
int n_rd;
if( (SOCKET_SRC = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0 ){
fprintf(stderr, "create socket error.\n");
exit(0);
}
while(1){
n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);
if (n_rd<46) {
perror("recvfrom():");
printf("Incomplete packet (errno is %d)\n", errno);
close(SOCKET_SRC);
exit(0);
}
/* An Ethernet frame was written to buf, frame analysis can be processed here */
/* Termination control */
}
close(SOCKET_SRC);
return 0;
}
2. 數據包(以太網幀)分析
一個以太網幀(RFC894)的數據格式如下圖所示。
以太網幀(RFC894)格式
程序通過執行一次
n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);
就將上面一條以太網幀寫入buf中。
(1) 為了從buf中提取以太網報文頭,我們可以定義如下結構體。
typedef struct mac_frm_hdr {
char dest_addr[6]; //destination MAC address shall be defined first.
char src_addr[6];
short type;
}__attribute__((packed)) MAC_FRM_HDR;
定義該結構體時,需要注意以下幾點。
a.各屬性需按幀格式的出現順序定義。
b.數據類型長度必須和幀中相應區域的長度相同。
c. 使用__attribute__((packed))取消編譯器自動優化對齊結構體,也是為了保證屬性長度和幀中相應區域的長度相同。
(2)為了提取IP報文頭,根據IP報文頭的格式,我們可定義如下結構體。
typedef struct ip_hdr{ //header of IPV4
#ifdef __LITTLE_ENDIAN_BIFIELD
u_char ip_len:4, ip_ver:4;
#else
u_char ip_ver:4, ip_len:4;
#endif
u_char ip_tos;
u_short ip_total_len;
u_short ip_id;
u_short ip_flags;
u_char ip_ttl;
u_char ip_protocol;
u_short ip_chksum;
u_int32 ip_src;
u_int32 ip_dest;
}__attribute__((packed)) IP_HDR;
為保證各屬性長度與IP報文頭中一致,我們應該在定義該結構體前作如下聲明。
typedef int int32;
typedef unsigned int u_int32;
typedef unsigned char u_char;
typedef unsigned short u_short;
注意事項參考定義以太網幀結構體。
(3)為了提取UDP/TCP報文頭,可根據UDP/TCP報文頭格式,定義相應結構體,這裡不作贅述。
下面是對以太網幀進行解析的具體代碼。
MAC_FRM_HDR *mac_hdr; //define a Ethernet frame header
IP_HDR *ip_hdr; //define a IP header
char *tmp1, *tmp2;
int AND_LOGIC = 0xFF;
mac_hdr = buf; //buf is what we got from the socket program
ip_hdr = buf + sizeof(MAC_FRM_HDR);
//udp_hdr = buf + sizeof(MAC_FRM_HDR) + sizeof(IP_HDR); //if we want to analyses the UDP/TCP
tmp1 = mac_hdr->src_addr;
tmp2 = mac_hdr->dest_addr;
/* print the MAC addresses of source and receiving host */
printf("MAC: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X==>" "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X",
tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,
tmp1[4]&AND_LOGIC, tmp1[5]&AND_LOGIC,
tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC,
tmp2[4]&AND_LOGIC, tmp2[5]&AND_LOGIC);
tmp1 = (char*)&ip_hdr->ip_src;
tmp2 = (char*)&ip_hdr->ip_dest;
/* print the IP addresses of source and receiving host */
printf("IP: %d.%d.%d.%d => %d.%d.%d.%d",
tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,
tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC);
/* print the IP protocol which was used by the socket communication */
switch(ip_hdr->ip_protocol) {
case IPPROTO_ICMP: LOGI("ICMP"); break;
case IPPROTO_IGMP: LOGI("IGMP"); break;
case IPPROTO_IPIP: LOGI("IPIP"); break;
case IPPROTO_TCP:
case IPPROTO_UDP:
LOGI("Protocol: %s", ip_hdr->ip_protocol == IPPROTO_TCP ? "TCP" : "UDP");
LOGI("Source port: %u, destination port: %u", udp_hdr->s_port, udp_hdr->d_port);
break;
case IPPROTO_RAW: LOGI("RAW"); break;
default: printf("Unknown, please query in inclued/linux/in.h\n"); break;
}
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-11/109330p2.htm