抓包首先便要知道通過網卡的數據其實都是經過底層的鏈路層(MAC),在Linux系統中咱們獲取網卡的數據流量實際上是直接從鏈路層收發數據幀。至於如何進行TCP/UDP鏈接本文就再也不贅述(以前的一段關於web server的程序已經大概說明),直接從最關鍵的原始套接字( raw socket)開始。web
一般狀況下程序設計人員接觸的網絡知識限於以下兩類:網絡
(1)流式套接字(SOCK_STREAM),它是一種面向鏈接的套接字,對應於TCP應用程序。數據結構
(2)數據報套接字(SOCK_DGRAM),它是一種無鏈接的套接字,對應於的UDP應用程序。socket
除了以上兩種基本的套接字外還有一類原始套接字,它是一種對原始網絡報文進行處理的套接字。函數
流式套接字(SOCK_STREAM)和數據報套接字(SOCK_DGRAM)涵蓋了通常應用層次的TCP/IP應用。spa
原始套接字的建立使用與通用的套接字建立的方法是一致的,只是在套接字類型的選項上使用的是另外一個SOCK_RAW。在使用socket函數進行函數建立完畢的時候,還要進行套接字數據中格式類型的指定,設置從套接字中能夠接收到的網絡數據格式:設計
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
fd爲返回的套接字描述符SOCK_RAW即建立原始套接字,這裏的htons(ETH_P_ALL)是socket()的第三個參數 protocol ,即協議類型。是一個定義在<netinet/in.h>中的常量,基本上咱們經常使用的是,ETH_P_IP、ETH_P_ARP、ETH_P_RARP或ETH_P_ALL,固然鏈路層協議不少,確定不止咱們說的這幾個,但咱們通常只關心這幾個就夠咱們用了。這裏簡單 提一下網絡數據收發的一點基礎。code
協議棧在組織數據收發流程時須要處理好兩個方面的問題:「從上到下」,即數據發送的任務;「從下到上」,即數據接收的任 務。數據發送相對接收來講要容易些,由於對於數據接收而言,網卡驅動還要明確什麼樣的數據該接收、什麼樣的不應接收等問題。server
他們都有特定的含義:對象
protocol |
值 |
做用 |
ETH_P_IP |
0X0800 |
只接收發往目的MAC是本機的IP類型的數據幀 |
ETH_P_ARP |
0X0806 |
只接收發往目的MAC是本機的ARP類型的數據幀 |
ETH_P_RARP |
0X8035 |
只接受發往目的MAC是本機的RARP類型的數據幀 |
ETH_P_ALL |
0X0003 |
接收發往目的MAC是本機的全部類型(ip,arp,rarp)的數據幀,同時還能夠接收從本機發出去的全部數據幀。在混雜模式打開的狀況下,還會接收到發往目的MAC爲非本地硬件地址的數據幀。 |
根據本身的抓包的對象咱們選取相應的參數值,就能夠獲取不一樣的數據包。
這裏我選擇的是ETH_P_ALL,能夠獲取全部通過本機的數據包,但有一個前提即是須要設置網卡爲混雜模式(Promiscuous Mode),這裏咱們就必須去了解一個重要的數據類型struct ifreq ;關於他的定義直接百度以下:
struct ifreq { # define IFHWADDRLEN 6 # define IFNAMSIZ IF_NAMESIZE union { char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */ } ifr_ifrn; union { struct sockaddr ifru_addr; struct sockaddr ifru_dstaddr; struct sockaddr ifru_broadaddr; struct sockaddr ifru_netmask; struct sockaddr ifru_hwaddr; short int ifru_flags; int ifru_ivalue; int ifru_mtu; struct ifmap ifru_map; char ifru_slave[IFNAMSIZ]; /* Just fits the size */ char ifru_newname[IFNAMSIZ]; __caddr_t ifru_data; } ifr_ifru; }; # define ifr_name ifr_ifrn.ifrn_name /* interface name*/ 在這裏就是網卡eth0或eth1 # define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ mac地址 # define ifr_addr ifr_ifru.ifru_addr /* address */ source地址 # define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */ 目的ip地址 # define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ 廣播地址 # define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ 子網掩碼 # define ifr_flags ifr_ifru.ifru_flags /* flags */ 模式標誌 設置混雜模式 # define ifr_metric ifr_ifru.ifru_ivalue /* metric */ # define ifr_mtu ifr_ifru.ifru_mtu /* mtu */ # define ifr_map ifr_ifru.ifru_map /* device map */ # define ifr_slave ifr_ifru.ifru_slave /* slave device */ # define ifr_data ifr_ifru.ifru_data /* for use by interface */ # define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */ # define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */ # define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */ # define ifr_newname ifr_ifru.ifru_newname /* New name */ # define _IOT_ifreq _IOT(_IOTS(char),IFNAMSIZ,_IOTS(char),16,0,0) # define _IOT_ifreq_short _IOT(_IOTS(char),IFNAMSIZ,_IOTS(short),1,0,0) # define _IOT_ifreq_int _IOT(_IOTS(char),IFNAMSIZ,_IOTS(int),1,0,0)
上面好多的定義,但用到的只是幾個,我作了相應的中文標註,設置混雜模式須要的就是將ifr_flags 設置爲 IFF_PROMISC就能夠了。
struct ifreq ethreq; if(-1 == ioctl(sock,SIOCGIFFLAGS,ðreq)){ //獲取接口標誌 perror("ioctl"); close(sock); exit(1); } ethreq.ifr_flags |=IFF_PROMISC; //IFF_PROMISC 這個標誌設置(由網絡代碼)來激活混雜操做
在這以後須要設置socket選項以下:
ret= int setsockopt( intsocket, int level, int option_name, const void *option_value, size_toption_len);
第一個參數socket是套接字描述符。第二個參數level是被設置的選項的級別,若是想要在套接字級別上設置選項,就必須把level設置爲 SOL_SOCKET。option_name指定準備設置的選項,option_name能夠有哪些取值,這取決於level。
完成這步後就能夠經過:
{ i struct ifreq ifr; strcpy(ifr.ifr_name, 「eth0」); ioctl(fd, SIOCGIFHWADDR, &ifr); }
獲取網卡的索引接口了,接着咱們要作的就是將建立的原始套接字綁定在指定的網卡上了
ret= bind(fd, (struct sockaddr *)& sock, sizeof(sock));這裏的sock便再也不是以前TCP鏈接時的 struct sockaddr_in 了,而是struct sockaddr_ll,定義以下:
struct sockaddr_ll{ unsigned short sll_family; /* 老是 AF_PACKET */ unsigned short sll_protocol; /* 物理層的協議 */ int sll_ifindex; /* 接口號 */ unsigned short sll_hatype; /* 報頭類型 */ unsigned char sll_pkttype; /* 分組類型 */ unsigned char sll_halen; /* 地址長度 */ unsigned char sll_addr[8]; /* 物理層地址 */ };對應設置以下:
struct sockaddr_ll sock; sock.sll_family = AF_PACKET; sock.sll_ifindex = stIf.ifr_ifindex;(struct ifreq) sock.sll_protocol = htons(ETH_P_ALL); ret = bind(fd, (struct sockaddr *)&, sizeof(sock));
這樣咱們就能夠開始經過recvfrom()等函數獲取對應套接字上的數據了。
說到這裏咱們已經得到了網卡上的一段數據包,但是數據包是什麼樣子的?這裏你就必須瞭解什麼是Ethnet,即以太數據幀
以太幀首部中2字節的幀類型字段指定了其上層所承載的具體協議,常見的有:
0x0800表示是IP報文、0x0806表示RARP協議、0x0806即爲咱們將要討論的ARP協議。
硬件類型: 1表示以太網。
網卡從線路上收到信號流,網卡的驅動程序會去檢查數據幀開始的前6個字節,即目的主機的MAC地址,若是和本身的網卡地址一致它纔會接收這個幀,不符合的通常都是直接無視。而後該數據幀會被網絡驅動程序分解,IP報文將經過網絡協議棧,最後傳送到應用程序那裏。
從這裏咱們能夠看出以太數據幀頭包含了MAC地址與幀類型,這正是咱們須要的,因此咱們便要開始對以太幀頭進行解析,這就涉及另外一個重要的數據類型struct ether_header;
其中重要的數據信息有:
u_charether_dhost[ETHER_ADDR_LEN];//dest 的MAC地址信息 u_char ether_shost[ETHER_ADDR_LEN];// source的MAC地址信息 u_short ether_type; // ip 協議類型 :ipv4……….等
static int ethdump_parseEthHead(const struct ether_header *pstEthHead) { unsigned short usEthPktType; /* 協議類型、源MAC、目的MAC */ usEthPktType=ntohs(pstEthHead->ether_type); printf("EthType:0x%04x(%s)\n",usEthPktType,ethdump_getProName(usEthPktType)); ethdump_showMac( pstEthHead->ether_shost); ethdump_showMac(pstEthHead->ether_dhost); return 0; } ntohs(ether_type)返回一個以主機字節順序表達的數 static void ethdump_showMac(const char acHWAddr[]) { for(i = 0; i < ETHER_ADDR_LEN - 1; i++) { printf("%02x:", *((unsigned char *)&(acHWAddr[i]))); } printf("%02x] \n", *((unsigned char *)&(acHWAddr[i]))); }
接着咱們就須要對下一段內容,即IP數據包進行解析,這裏對應的數據結構爲struct ip,對於他的定義有些複雜,我也不是很懂,可是咱們只須要獲取:
u_int8_tip_p; /* protocol */協議類型 structin_addr ip_src, ip_dst; /* source and dest address */源IP與目的IP地址
經過getprotobynumber( ); 返回對應於給定協議號的相關協議信息,輸出對應的p_name。以及經過inet_nota(ip_src)打印出對應的source IP 與 dest IP。就能夠獲取目前咱們須要的內容。
static int ethdump_parseIpHead(const struct ip *pstIpHead) { struct protoent *pstIpProto = NULL; /* 協議類型、源IP地址、目的IP地址 */ pstIpProto = getprotobynumber(pstIpHead->ip_p); if(NULL != pstIpProto) { printf("IP-Type: %d (%s) \n", pstIpHead->ip_p, pstIpProto->p_name); } else { printf("IP-Type: %d (%s)\n ", pstIpHead->ip_p, "None"); } printf("SAddr=[%s] \n", inet_ntoa(pstIpHead->ip_src)); printf("DAddr=[%s] ", inet_ntoa(pstIpHead->ip_dst)); printf("\n========================================\n"); return 0; }到這裏基本上咱們就完成了對數據包的抓取,以及獲取 source mac 、dest mac、source ip 、des ip以及對應協議類型的分析。固然還能夠進行進一步的數據分析,以及IP段、協議棧等的分析。越日後走涉及的東西就越複雜。