1. 程式人生 > >Winpcap網路程式設計九之Winpcap實戰,ARP協議獲得MAC表及主機通訊

Winpcap網路程式設計九之Winpcap實戰,ARP協議獲得MAC表及主機通訊

大家好,本次我們需要完成的任務是:

 完成兩臺主機之間的資料通訊(資料鏈路層)

  • 模擬ARP協議獲得網段內主機的MAC表
  • 使用幀完成兩臺主機的通訊(Hello! I’m …)        
宣告:本文章的目的是為大家的Winpcap程式設計帶來一定的借鑑,希望對大家的課程設計有一定的幫助。總之,我相信,大家看了前幾篇 Winpcap 程式設計基礎知識,再加上這篇文章的講解,一步一步做下來,相信你能成功的。

P.S. 對Winpcap程式設計的基礎知識有一定了解的就不用再去費工夫學習咯。我也是一點一點學習的,在此提供給大家一個學習文件,Winpcap中文文件

P.P.S. 另外....CSDN略坑爹....我的程式碼它可能自動轉碼...我都為此改了好多次了...程式碼有顯示問題與我聯絡...郵箱

[email protected] ...以後轉自己個人空間...

好了話不多說,我們步入正題...

首先我們要理解ARP是幹嘛的,ARP主要作用就是通過IP地址來獲取MAC地址。那麼怎樣獲取呢?本機向區域網內主機發送ARP包,ARP包內包含了目的IP,源IP,目的MAC,源MAC,其中目的MAC地址為廣播地址,FF-FF-FF-FF-FF-FF,即向區域網內所有主機發送一個ARP請求,那麼其他主機收到這個請求之後則會向請求來源返回一個數據包。在這個返回的資料包中包含了自身的MAC地址。那麼本機收到這些返回的資料包進行解析之後便會得到區域網內所有主機的MAC地址了..

程式設計開始:

新建一個C++專案,配好環境,引入Winpcap相關的庫,這些不再贅述。

標頭檔案引入

#define HAVE_REMOTE
#define WPCAP
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
在main函式中首先宣告一系列變數如下
	char *ip_addr;                                    //IP地址
	char *ip_netmask;                             //子網掩碼
	unsigned char *ip_mac;          //本機MAC地址
為這三個變數分配地址空間
ip_addr = (char *) malloc(sizeof(char) * 16); //申請記憶體存放IP地址
	if (ip_addr == NULL)
	{
		printf("申請記憶體存放IP地址失敗!\n");
		return -1;
	}
	ip_netmask = (char *) malloc(sizeof(char) * 16); //申請記憶體存放NETMASK地址
	if (ip_netmask == NULL)
	{
		printf("申請記憶體存放NETMASK地址失敗!\n");
		return -1;
	}
	ip_mac = (unsigned char *) malloc(sizeof(unsigned char) * 6); //申請記憶體存放MAC地址
	if (ip_mac == NULL)
	{
		printf("申請記憶體存放MAC地址失敗!\n");
		return -1;
	}
接下來就是爛大街的程式,獲取介面卡列表並選中相應的介面卡,註釋已經在程式碼中了,如果還有不明白的請參照前幾次的講解。
//獲取本地介面卡列表
	if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){
		//結果為-1代表出現獲取介面卡列表失敗
		fprintf(stderr,"Error in pcap_findalldevs_ex:\n",errbuf);
		//exit(0)代表正常退出,exit(other)為非正常退出,這個值會傳給作業系統
		exit(1);
	}
	

	for(d = alldevs;d !=NULL;d = d->next){
		printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name);
		if(d->description){
			//列印介面卡的描述資訊
			printf("description:%s\n",d->description);
		}else{
			//介面卡不存在描述資訊
			printf("description:%s","no description\n");
		}
		//列印本地環回地址
		 printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
		 /**
		 pcap_addr *  next     指向下一個地址的指標
		 sockaddr *  addr       IP地址
		 sockaddr *  netmask  子網掩碼
		 sockaddr *  broadaddr   廣播地址
		 sockaddr *  dstaddr        目的地址
		 */
		 pcap_addr_t *a;       //網路介面卡的地址用來儲存變數
		 for(a = d->addresses;a;a = a->next){
			 //sa_family代表了地址的型別,是IPV4地址型別還是IPV6地址型別
			 switch (a->addr->sa_family)
			 {
				 case AF_INET:  //代表IPV4型別地址
					 printf("Address Family Name:AF_INET\n");
					 if(a->addr){
						 //->的優先順序等同於括號,高於強制型別轉換,因為addr為sockaddr型別,對其進行操作須轉換為sockaddr_in型別
						 printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
					 }
					if (a->netmask){
						 printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
					}
					if (a->broadaddr){
						   printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
					 }
					 if (a->dstaddr){
						   printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
					 }
        			 break;
				 case AF_INET6: //代表IPV6型別地址
					 printf("Address Family Name:AF_INET6\n");
					 printf("this is an IPV6 address\n");
					 break;
				 default:
					 break;
			 }
		 }
	}
	//i為0代表上述迴圈未進入,即沒有找到介面卡,可能的原因為Winpcap沒有安裝導致未掃描到
	if(i == 0){
		printf("interface not found,please check winpcap installation");
	}

	int num;
	printf("Enter the interface number(1-%d):",i);
	//讓使用者選擇選擇哪個介面卡進行抓包
	scanf_s("%d",&num);
	printf("\n");

	//使用者輸入的數字超出合理範圍
	if(num<1||num>i){
		printf("number out of range\n");
		pcap_freealldevs(alldevs);
		return -1;
	}
	//跳轉到選中的介面卡
	for(d=alldevs, i=0; i< num-1 ; d=d->next, i++);

	//執行到此處說明使用者的輸入是合法的
	if((adhandle = pcap_open(d->name,		//裝置名稱
														65535,       //存放資料包的內容長度
														PCAP_OPENFLAG_PROMISCUOUS,  //混雜模式
														1000,           //超時時間
														NULL,          //遠端驗證
														errbuf         //錯誤緩衝
														)) == NULL){
        //開啟介面卡失敗,列印錯誤並釋放介面卡列表
		fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
        // 釋放裝置列表 
        pcap_freealldevs(alldevs);
        return -1;
	}
上述程式碼中需要另外宣告的有:
	pcap_if_t  * alldevs;       //所有網路介面卡
	pcap_if_t  *d;					//選中的網路介面卡
	char errbuf[PCAP_ERRBUF_SIZE];   //錯誤緩衝區,大小為256
	pcap_t *adhandle;           //捕捉例項,是pcap_open返回的物件
	int i = 0;                            //介面卡計數變數
char *iptos(u_long in);       //u_long即為 unsigned long
/* 將數字型別的IP地址轉換成字串型別的 */
#define IPTOSBUFFERS    12
char *iptos(u_long in)
{
    static char output[IPTOSBUFFERS][3*4+3+1];
    static short which;
    u_char *p;

    p = (u_char *)&in;
    which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
    sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
    return output[which];
}
到此程式應該會編譯通過,可以試著編譯一下執行。

GO ON...

接下來我們首先要用ifget方法獲取自身的IP和子網掩碼

函式宣告:

void ifget(pcap_if_t *d, char *ip_addr, char *ip_netmask);   
//獲取IP和子網掩碼賦值為ip_addr和ip_netmask
void ifget(pcap_if_t *d, char *ip_addr, char *ip_netmask) {
	pcap_addr_t *a;
	//遍歷所有的地址,a代表一個pcap_addr
	for (a = d->addresses; a; a = a->next) {
		switch (a->addr->sa_family) {
		case AF_INET:  //sa_family :是2位元組的地址家族,一般都是“AF_xxx”的形式。通常用的都是AF_INET。代表IPV4
			if (a->addr) {
				char *ipstr;
				//將地址轉化為字串
				ipstr = iptos(((struct sockaddr_in *) a->addr)->sin_addr.s_addr); //*ip_addr
				printf("ipstr:%s\n",ipstr);
				memcpy(ip_addr, ipstr, 16);
			}
			if (a->netmask) {
				char *netmaskstr;
				netmaskstr = iptos(((struct sockaddr_in *) a->netmask)->sin_addr.s_addr);
				printf("netmask:%s\n",netmaskstr);
				memcpy(ip_netmask, netmaskstr, 16);
			}
		case AF_INET6:
			break;
		}
	}
}
main函式繼續寫,如下呼叫,之前宣告的ip_addr和ip_netmask就已經被賦值了
ifget(d, ip_addr, ip_netmask); //獲取所選網絡卡的基本資訊--掩碼--IP地址
到現在我們已經獲取到了本機的IP和子網掩碼,下一步傳送一個ARP請求來獲取自身的MAC地址

這個ARP請求的源IP地址就隨便指定了,就相當於你構造了一個外來的ARP請求,本機捕獲到了請求,然後傳送迴應給對方的資料包也被本機捕獲到了並解析出來了。解析了自己發出去的資料包而已。

那麼我們就宣告一個函式並實現:

int GetSelfMac(pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac);
// 獲取自己主機的MAC地址
int GetSelfMac(pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac) {
	unsigned char sendbuf[42]; //arp包結構大小
	int i = -1;
	int res;
	EthernetHeader eh; //乙太網幀頭
	Arpheader ah;  //ARP幀頭
	struct pcap_pkthdr * pkt_header;
	const u_char * pkt_data;
	//將已開闢記憶體空間 eh.dest_mac_add 的首 6個位元組的值設為值 0xff。
	memset(eh.DestMAC, 0xff, 6); //目的地址為全為廣播地址
	memset(eh.SourMAC, 0x0f, 6);
	memset(ah.DestMacAdd, 0x0f, 6);
	memset(ah.SourceMacAdd, 0x00, 6);
	//htons將一個無符號短整型的主機數值轉換為網路位元組順序
	eh.EthType = htons(ETH_ARP);
	ah.HardwareType= htons(ARP_HARDWARE);
	ah.ProtocolType = htons(ETH_IP);
	ah.HardwareAddLen = 6;
	ah.ProtocolAddLen = 4;
	ah.SourceIpAdd = inet_addr("100.100.100.100"); //隨便設的請求方ip
	ah.OperationField = htons(ARP_REQUEST);
	ah.DestIpAdd = inet_addr(ip_addr);
	memset(sendbuf, 0, sizeof(sendbuf));
	memcpy(sendbuf, &eh, sizeof(eh));
	memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
	printf("%s",sendbuf);
	if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
		printf("\nPacketSend succeed\n");
	} else {
		printf("PacketSendPacket in getmine Error: %d\n", GetLastError());
		return 0;
	}
	//從interface或離線記錄檔案獲取一個報文
	//pcap_next_ex(pcap_t* p,struct pcap_pkthdr** pkt_header,const u_char** pkt_data)
	while ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
		if (*(unsigned short *) (pkt_data + 12) == htons(ETH_ARP)
				&& *(unsigned short*) (pkt_data + 20) == htons(ARP_REPLY)
				&& *(unsigned long*) (pkt_data + 38)
						== inet_addr("100.100.100.100")) {
			for (i = 0; i < 6; i++) {
				ip_mac[i] = *(unsigned char *) (pkt_data + 22 + i);
			}
			printf("獲取自己主機的MAC地址成功!\n");
			break;
		}
	}
	if (i == 6) {
		return 1;
	} else {
		return 0;
	}
}
其中我們需要定義一下常量如下
#define ETH_ARP         0x0806  //乙太網幀型別表示後面資料的型別,對於ARP請求或應答來說,該欄位的值為x0806
#define ARP_HARDWARE    1  //硬體型別欄位值為表示乙太網地址
#define ETH_IP          0x0800  //協議型別欄位表示要對映的協議地址型別值為x0800表示IP地址
#define ARP_REQUEST     1   //ARP請求
#define ARP_REPLY       2      //ARP應答
#define HOSTNUM         255   //主機數量

另外發送ARP請求少不了幀頭和ARP頭的結構,我們需要宣告出來,另外我們構建傳送包需要再宣告兩個結構體sparam和gparam
//幀頭部結構體,共14位元組
struct EthernetHeader
{
    u_char DestMAC[6];    //目的MAC地址 6位元組
    u_char SourMAC[6];   //源MAC地址 6位元組
    u_short EthType;         //上一層協議型別,如0x0800代表上一層是IP協議,0x0806為arp  2位元組
};

//28位元組ARP幀結構
struct Arpheader {
	unsigned short HardwareType; //硬體型別
	unsigned short ProtocolType; //協議型別
	unsigned char HardwareAddLen; //硬體地址長度
	unsigned char ProtocolAddLen; //協議地址長度
	unsigned short OperationField; //操作欄位
	unsigned char SourceMacAdd[6]; //源mac地址
	unsigned long SourceIpAdd; //源ip地址
	unsigned char DestMacAdd[6]; //目的mac地址
	unsigned long DestIpAdd; //目的ip地址
};

//arp包結構
struct ArpPacket {
	EthernetHeader ed;
	Arpheader ah;
};

struct sparam {
	pcap_t *adhandle;
	char *ip;
	unsigned char *mac;
	char *netmask;
};
struct gparam {
	pcap_t *adhandle;
};

struct sparam sp;
struct gparam gp;
到現在程式碼也是完整可以執行的,如果有問題請檢查上述程式碼完整性和位置。

可能出現的BUG:

只顯示ARP傳送成功,沒有接受到並解析列印。可能的原因是幀構造有問題,位元組沒有對齊,有偏差,像#define一樣

寫入如下程式碼:

#pragma pack(1)  //按一個位元組記憶體對齊

GO ON..

獲取到了自身的MAC地址之後,就可以在本機上構建ARP廣播請求,向區域網內的所有主機發送ARP請求,得到迴應之後解析迴應的資料包並進行解析,得到對方的MAC地址。在這裡我們需要開啟兩個執行緒,一個用來發送一個用來接收。好,我們繼續..

先宣告兩個執行緒

	HANDLE sendthread;      //傳送ARP包執行緒
	HANDLE recvthread;       //接受ARP包執行緒
在main方法中繼續寫,對sp和gp兩個ARP請求所需要的結構體進行賦值。賦值什麼?就是你之前用ifget獲取來的IP地址和子網掩碼以及用getSelfMac獲取來的MAC地址。
sp.adhandle = adhandle;
	sp.ip = ip_addr;
	sp.mac = ip_mac;
	sp.netmask = ip_netmask;
	gp.adhandle = adhandle;
接下來直接建立兩個執行緒,一個是傳送一個接受,分別呼叫兩個方法。
sendthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) SendArpPacket,
			&sp, 0, NULL);
	recvthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) GetLivePC, &gp,
			0, NULL);
	printf("\nlistening on 網絡卡%d ...\n", i);
那麼傳送資料包的方法和接收解析資料包的方法怎樣實現呢?

傳送資料包,傳送資料包先對結構體資料進行賦值,就像getSelfMac方法一樣,然後聲明瞭一個buffer用來儲存每一個位元組內容。

利用memset方法對buffer進行賦值。再利用一個for迴圈對255個主機進行傳送,指定他們的IP地址。另外定義了一個flag,當傳送成功之後將flag設定為1

/* 向區域網內所有可能的IP地址傳送ARP請求包執行緒 */
DWORD WINAPI SendArpPacket(LPVOID lpParameter) //(pcap_t *adhandle,char *ip,unsigned char *mac,char *netmask)
{
	sparam *spara = (sparam *) lpParameter;
	pcap_t *adhandle = spara->adhandle;
	char *ip = spara->ip;
	unsigned char *mac = spara->mac;
	char *netmask = spara->netmask;
	printf("ip_mac:%02x-%02x-%02x-%02x-%02x-%02x\n", mac[0], mac[1], mac[2],
			mac[3], mac[4], mac[5]);
	printf("自身的IP地址為:%s\n", ip);
	printf("地址掩碼NETMASK為:%s\n", netmask);
	printf("\n");
	unsigned char sendbuf[42]; //arp包結構大小
	EthernetHeader eh;
	Arpheader ah;
	//賦值MAC地址
	memset(eh.DestMAC, 0xff, 6);       //目的地址為全為廣播地址
	memcpy(eh.SourMAC, mac, 6);
	memcpy(ah.SourceMacAdd, mac, 6);
	memset(ah.DestMacAdd, 0x00, 6);
	eh.EthType = htons(ETH_ARP);
	ah.HardwareType = htons(ARP_HARDWARE);
	ah.ProtocolType = htons(ETH_IP);
	ah.HardwareAddLen = 6;
	ah.ProtocolAddLen = 4;
	ah.SourceIpAdd = inet_addr(ip); //請求方的IP地址為自身的IP地址
	ah.OperationField = htons(ARP_REQUEST);
	//向區域網內廣播發送arp包
	unsigned long myip = inet_addr(ip);
	unsigned long mynetmask = inet_addr(netmask);
	unsigned long hisip = htonl((myip & mynetmask));
	//向255個主機發送
	for (int i = 0; i < HOSTNUM; i++) {
		ah.DestIpAdd = htonl(hisip + i);
		//構造一個ARP請求
		memset(sendbuf, 0, sizeof(sendbuf));
		memcpy(sendbuf, &eh, sizeof(eh));
		memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
		//如果傳送成功
		if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
			//printf("\nPacketSend succeed\n");
		} else {
			printf("PacketSendPacket in getmine Error: %d\n", GetLastError());
		}
		Sleep(50);
	}
	Sleep(1000);
	flag = TRUE;
	return 0;
}
注: 此函式和flag變數在前面別忘了宣告一下...

然後是接收資料包並列印MAC地址:

/* 分析截留的資料包獲取活動的主機IP地址 */
DWORD WINAPI GetLivePC(LPVOID lpParameter) //(pcap_t *adhandle)
{
	gparam *gpara = (gparam *) lpParameter;
	pcap_t *adhandle = gpara->adhandle;
	int res;
	unsigned char Mac[6];
	struct pcap_pkthdr * pkt_header;
	const u_char * pkt_data;
	while (true) {
		if (flag) {
			printf("獲取MAC地址完畢,請輸入你要傳送對方的IP地址:\n");
			break;
		}
		if ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
			if (*(unsigned short *) (pkt_data + 12) == htons(ETH_ARP)) {
				ArpPacket *recv = (ArpPacket *) pkt_data;
				if (*(unsigned short *) (pkt_data + 20) == htons(ARP_REPLY)) {
					printf("-------------------------------------------\n");
					printf("IP地址:%d.%d.%d.%d   MAC地址:",
						     recv->ah.SourceIpAdd & 255,
							 recv->ah.SourceIpAdd >> 8 & 255,
							 recv->ah.SourceIpAdd >> 16 & 255,
							 recv->ah.SourceIpAdd >> 24 & 255);
					for (int i = 0; i < 6; i++) {
						Mac[i] = *(unsigned char *) (pkt_data + 22 + i);
						printf("%02x", Mac[i]);
					}
					printf("\n");
				}
			}
		}
		Sleep(10);
	}
	return 0;
}
以上暫告一段落,通過整合以上程式碼,我們可以得到如下執行結果:
--------------------------------------------------
number:1
name:rpcap://\Device\NPF_{5AC72F8D-019C-4003-B51B-
description:Network adapter 'Microsoft' on local h
        Loopback: no
Address Family Name:AF_INET6
this is an IPV6 address
Address Family Name:AF_INET6
this is an IPV6 address
--------------------------------------------------
number:2
name:rpcap://\Device\NPF_{C17EB3F6-1E86-40E5-8790-
description:Network adapter 'Microsoft' on local h
        Loopback: no
Address Family Name:AF_INET6
this is an IPV6 address
Address Family Name:AF_INET
Address:192.168.95.1
        Netmask: 255.255.255.0
        Broadcast Address: 255.255.255.255
--------------------------------------------------
number:3
name:rpcap://\Device\NPF_{33E23A2F-F791-409B-8452-
description:Network adapter 'Qualcomm Atheros Ar81
oller' on local host
        Loopback: no
Address Family Name:AF_INET6
this is an IPV6 address
Address Family Name:AF_INET6
this is an IPV6 address
Address Family Name:AF_INET6
this is an IPV6 address
Address Family Name:AF_INET
Address:121.250.216.237
        Netmask: 255.255.255.0
        Broadcast Address: 255.255.255.255
--------------------------------------------------
number:4
name:rpcap://\Device\NPF_{DCCF036F-A9A8-4225-B980-
description:Network adapter 'Microsoft' on local h
        Loopback: no
Address Family Name:AF_INET6
this is an IPV6 address
Address Family Name:AF_INET6
this is an IPV6 address
--------------------------------------------------
number:5
name:rpcap://\Device\NPF_{D62A0060-F424-46FC-83A5-
description:Network adapter 'Microsoft' on local h
        Loopback: no
Address Family Name:AF_INET6
this is an IPV6 address
Address Family Name:AF_INET
Address:192.168.191.1
        Netmask: 255.255.255.0
        Broadcast Address: 255.255.255.255
--------------------------------------------------
number:6
name:rpcap://\Device\NPF_{B5224A53-8450-4537-AB3B-
description:Network adapter 'Microsoft' on local h
        Loopback: no
Address Family Name:AF_INET6
this is an IPV6 address
Address Family Name:AF_INET
Address:192.168.191.2
        Netmask: 255.255.255.0
        Broadcast Address: 255.255.255.255
Enter the interface number(1-6):3

ipstr:121.250.216.237
netmask:255.255.255.0

PacketSend succeed
獲取自己主機的MAC地址成功!

listening on 網絡卡2 ...
ip_mac:dc-0e-a1-ec-53-c3
自身的IP地址為:121.250.216.237
地址掩碼NETMASK為:255.255.255.0

請按任意鍵繼續. . . ------------------------------
IP地址:121.250.216.1   MAC地址:000fe28e6100
-------------------------------------------
IP地址:121.250.216.3   MAC地址:089e012d20d5
-------------------------------------------
IP地址:121.250.216.5   MAC地址:5404a6af5f2d
-------------------------------------------
IP地址:121.250.216.6   MAC地址:28d244248d81
-------------------------------------------
IP地址:121.250.216.7   MAC地址:80fa5b0283f3
-------------------------------------------
IP地址:121.250.216.8   MAC地址:14dae9005b9e
-------------------------------------------
IP地址:121.250.216.9   MAC地址:b82a72bf8bce
-------------------------------------------
IP地址:121.250.216.12   MAC地址:84c9b2fefeed
-------------------------------------------
IP地址:121.250.216.15   MAC地址:28d2440b4b1b
-------------------------------------------
IP地址:121.250.216.16   MAC地址:bcee7b969beb
-------------------------------------------
........此處省略一萬字....
接下來我們讓使用者輸入要傳送的IP地址和要傳送的資料
u_int ip1,ip2,ip3,ip4;
		scanf_s("%d.%d.%d.%d",&ip1,&ip2,&ip3,&ip4);
		printf("請輸入你要傳送的內容:\n");
		getchar();
		gets_s(TcpData);
		printf("要傳送的內容:%s\n",TcpData);
宣告一下TcpData
	char TcpData[20];   //傳送內容
接下來就是重頭戲了,需要宣告各種結構體,我們傳送的是TCP資料,這樣,TCP的TcpData 就作為真正的內容,然後在前面加上TCP頭,IP頭,幀頭,還有校驗和要正確。

最後構成一個完整的幀,那麼另外宣告的結構體如下,前面程式碼宣告過的幀頭部結構體就去掉了。

//IP地址格式
struct IpAddress
{
    u_char byte1;
    u_char byte2;
    u_char byte3;
    u_char byte4;
};

//幀頭部結構體,共14位元組
struct EthernetHeader
{
    u_char DestMAC[6];    //目的MAC地址 6位元組
    u_char SourMAC[6];   //源MAC地址 6位元組
    u_short EthType;         //上一層協議型別,如0x0800代表上一層是IP協議,0x0806為arp  2位元組
};

//IP頭部結構體,共20位元組
struct IpHeader
{
    unsigned char Version_HLen;   //版本資訊4位 ,頭長度4位 1位元組
    unsigned char TOS;                    //服務型別    1位元組
    short Length;                              //資料包長度 2位元組
    short Ident;                                 //資料包標識  2位元組
    short Flags_Offset;                    //標誌3位,片偏移13位  2位元組
    unsigned char TTL;                   //存活時間  1位元組
    unsigned char Protocol;          //協議型別  1位元組
    short Checksum;                       //首部校驗和 2位元組
	IpAddress SourceAddr;       //源IP地址   4位元組
	IpAddress DestinationAddr; //目的IP地址  4位元組
};

//TCP頭部結構體,共20位元組
struct TcpHeader
{
    unsigned short SrcPort;                        //源埠號  2位元組
    unsigned short DstPort;                        //目的埠號 2位元組
    unsigned int SequenceNum;               //序號  4位元組
    unsigned int Acknowledgment;         //確認號  4位元組
    unsigned char HdrLen;                         //首部長度4位,保留位6位 共10位
    unsigned char Flags;                              //標誌位6位
    unsigned short AdvertisedWindow;  //視窗大小16位 2位元組
    unsigned short Checksum;                  //校驗和16位   2位元組
    unsigned short UrgPtr;						  //緊急指標16位   2位元組
};

//TCP偽首部結構體 12位元組
struct PsdTcpHeader
{
	IpAddress SourceAddr;                     //源IP地址  4位元組
	IpAddress DestinationAddr;             //目的IP地址 4位元組
    char Zero;                                                    //填充位  1位元組
    char Protcol;                                               //協議號  1位元組
    unsigned short TcpLen;                           //TCP包長度 2位元組
};
繼續main函式中對各種結構體的資料進行初始化賦值,並計算校驗和。
//結構體初始化為0序列
		memset(&ethernet, 0, sizeof(ethernet));
		BYTE destmac[8];
		//目的MAC地址,此處沒有對幀的MAC地址進行賦值,因為網絡卡設定的混雜模式,可以接受經過該網絡卡的所有幀。當然最好的方法是賦值為ARP剛才獲取到的MAC地址,當然不賦值也可以捕捉到並解析,在此處僅做下說明。
		destmac[0] = 0x00;
		destmac[1] = 0x11;
		destmac[2] = 0x22;
		destmac[3] = 0x33;
		destmac[4] = 0x44;
		destmac[5] = 0x55;
		//賦值目的MAC地址
		memcpy(ethernet.DestMAC, destmac, 6);
		BYTE hostmac[8];
		//源MAC地址
		hostmac[0] = 0x00;
		hostmac[1] = 0x1a;
		hostmac[2] = 0x4d;
		hostmac[3] = 0x70;
		hostmac[4] = 0xa3;
		hostmac[5] = 0x89;
		//賦值源MAC地址
		memcpy(ethernet.SourMAC, hostmac, 6);
		//上層協議型別,0x0800代表IP協議
		ethernet.EthType = htons(0x0800);
		//賦值SendBuffer
		memcpy(&SendBuffer, ðernet, sizeof(struct EthernetHeader));
		//賦值IP頭部資訊
		ip.Version_HLen = 0x45;
		ip.TOS = 0;
		ip.Length = htons(sizeof(struct IpHeader) + sizeof(struct TcpHeader) + strlen(TcpData));
		ip.Ident = htons(1);
		ip.Flags_Offset = 0;
		ip.TTL = 128;
		ip.Protocol = 6;
		ip.Checksum = 0;
		//源IP地址
		ip.SourceAddr.byte1 = 127;
		ip.SourceAddr.byte2 = 0;
		ip.SourceAddr.byte3 = 0;
		ip.SourceAddr.byte4 = 1;
		//目的IP地址
		ip.DestinationAddr.byte1 = ip1;
		ip.DestinationAddr.byte2 = ip2;
		ip.DestinationAddr.byte3 = ip3;
		ip.DestinationAddr.byte4 = ip4;
		//賦值SendBuffer
		memcpy(&SendBuffer[sizeof(struct EthernetHeader)], &ip, 20);
		//賦值TCP頭部內容
		tcp.DstPort = htons(102);
		tcp.SrcPort = htons(1000);
		tcp.SequenceNum = htonl(11);
		tcp.Acknowledgment = 0;
		tcp.HdrLen = 0x50;
		tcp.Flags = 0x18;
		tcp.AdvertisedWindow = htons(512);
		tcp.UrgPtr = 0;
		tcp.Checksum = 0;
		//賦值SendBuffer
		memcpy(&SendBuffer[sizeof(struct EthernetHeader) + 20], &tcp, 20);
		//賦值偽首部
		ptcp.SourceAddr = ip.SourceAddr;
		ptcp.DestinationAddr = ip.DestinationAddr;
		ptcp.Zero = 0;
		ptcp.Protcol = 6;
		ptcp.TcpLen = htons(sizeof(struct TcpHeader) + strlen(TcpData));
		//宣告臨時儲存變數,用來計算校驗和
		char TempBuffer[65535];
		memcpy(TempBuffer, &ptcp, sizeof(struct PsdTcpHeader));
		memcpy(TempBuffer + sizeof(struct PsdTcpHeader), &tcp, sizeof(struct TcpHeader));
		memcpy(TempBuffer + sizeof(struct PsdTcpHeader) + sizeof(struct TcpHeader), TcpData, strlen(TcpData));
		//計算TCP的校驗和
		tcp.Checksum = checksum((USHORT*)(TempBuffer), sizeof(struct PsdTcpHeader) + sizeof(struct TcpHeader) + strlen(TcpData));
		//重新把SendBuffer賦值,因為此時校驗和已經改變,賦值新的
		memcpy(SendBuffer + sizeof(struct EthernetHeader) + sizeof(struct IpHeader), &tcp, sizeof(struct TcpHeader));
		memcpy(SendBuffer + sizeof(struct EthernetHeader) + sizeof(struct IpHeader) + sizeof(struct TcpHeader), TcpData, strlen(TcpData));
		//初始化TempBuffer為0序列,儲存變數來計算IP校驗和
		memset(TempBuffer, 0, sizeof(TempBuffer));
		memcpy(TempBuffer, &ip, sizeof(struct IpHeader));
		//計算IP校驗和
		ip.Checksum = checksum((USHORT*)(TempBuffer), sizeof(struct IpHeader));
		//重新把SendBuffer賦值,IP校驗和已經改變
		memcpy(SendBuffer + sizeof(struct EthernetHeader), &ip, sizeof(struct IpHeader));
		//傳送序列的長度
		int size = sizeof(struct EthernetHeader) + sizeof(struct IpHeader) + sizeof(struct TcpHeader) + strlen(TcpData);
		int result = pcap_sendpacket(adhandle, SendBuffer,size);
		if (result != 0)
		{
			printf("Send Error!\n");
		} 
		else
		{
			printf("Send TCP Packet.\n");
			printf("Dstination Port:%d\n", ntohs(tcp.DstPort));
			printf("Source Port:%d\n", ntohs(tcp.SrcPort));
			printf("Sequence:%d\n", ntohl(tcp.SequenceNum));
			printf("Acknowledgment:%d\n", ntohl(tcp.Acknowledgment));
			printf("Header Length:%d*4\n", tcp.HdrLen >> 4);
			printf("Flags:0x%0x\n", tcp.Flags);
			printf("AdvertiseWindow:%d\n", ntohs(tcp.AdvertisedWindow));
			printf("UrgPtr:%d\n", ntohs(tcp.UrgPtr));
			printf("Checksum:%u\n", ntohs(tcp.Checksum));
			printf("Send Successfully!\n");
		}
校驗和方法如下:
//獲得校驗和的方法
unsigned short checksum(unsigned short *data, int length)
{
    unsigned long temp = 0;
    while (length > 1)
    {
        temp +=  *data++;
        length -= sizeof(unsigned short);
    }
    if (length)
    {
        temp += *(unsigned short*)data;
    }
    temp = (temp >> 16) + (temp &0xffff);
    temp += (temp >> 16);
    return (unsigned short)(~temp);
}
記得在宣告一下這個方法。如果放在main函式前當然就不用宣告啦。

另外需要宣告的變數有
			struct EthernetHeader ethernet;    //乙太網幀頭
    struct IpHeader ip;                            //IP頭
    struct TcpHeader tcp;                      //TCP頭
    struct PsdTcpHeader ptcp;             //TCP偽首部
unsigned char SendBuffer[200];       //傳送佇列
接下來的執行結果:
獲取MAC地址完畢,請輸
121.250.216.112
請輸入你要傳送的內容
what is tcp
要傳送的內容:what i
Send TCP Packet.
Dstination Port:102
Source Port:1000
Sequence:11
Acknowledgment:0
Header Length:5*4
Flags:0x18
AdvertiseWindow:512
UrgPtr:0
Checksum:17149
Send Successfully!

截圖如下:


好啦,傳送幀到此就告一段落啦!如果有疑問請留言。

幀的接收很簡單,直接貼原始碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>


char *iptos(u_long in);       //u_long即為 unsigned long
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
//struct tm *ltime;					//和時間處理有關的變數

struct IpAddress
{
    u_char byte1;
    u_char byte2;
    u_char byte3;
    u_char byte4;
};

//幀頭部結構體,共14位元組
struct EthernetHeader
{
    u_char DestMAC[6];    //目的MAC地址 6位元組
    u_char SourMAC[6];   //源MAC地址 6位元組
    u_short EthType;         //上一層協議型別,如0x0800代表上一層是IP協議,0x0806為arp  2位元組
};

//IP頭部結構體,共20位元組
struct IpHeader
{
    unsigned char Version_HLen;   //版本資訊4位 ,頭長度4位 1位元組
    unsigned char TOS;                    //服務型別    1位元組
    short Length;                              //資料包長度 2位元組
    short Ident;                                 //資料包標識  2位元組
    short Flags_Offset;                    //標誌3位,片偏移13位  2位元組
    unsigned char TTL;                    //存活時間  1位元組
    unsigned char Protocol;           //協議型別  1位元組
    short Checksum;                        //首部校驗和 2位元組
    IpAddress SourceAddr;           //源IP地址   4位元組
    IpAddress DestinationAddr;   //目的IP地址  4位元組
};

//TCP頭部結構體,共20位元組
struct TcpHeader
{
    unsigned short SrcPort;                        //源埠號  2位元組
    unsigned short DstPort;                        //目的埠號 2位元組
    unsigned int SequenceNum;               //序號  4位元組
    unsigned int Acknowledgment;         //確認號  4位元組
    unsigned char HdrLen;                         //首部長度4位,保留位6位 共10位
    unsigned char Flags;                              //標誌位6位
    unsigned short AdvertisedWindow;  //視窗大小16位 2位元組
    unsigned short Checksum;                  //校驗和16位   2位元組
    unsigned short UrgPtr;						  //緊急指標16位   2位元組
};

//TCP偽首部結構體 12位元組
struct PsdTcpHeader
{
    unsigned long SourceAddr;                     //源IP地址  4位元組
    unsigned long DestinationAddr;             //目的IP地址 4位元組
    char Zero;                                                    //填充位  1位元組
    char Protcol;                                               //協議號  1位元組
    unsigned short TcpLen;                           //TCP包長度 2位元組
};


int main(){

	EthernetHeader *ethernet;    //乙太網幀頭
    IpHeader *ip;                            //IP頭
    TcpHeader *tcp;                      //TCP頭
    PsdTcpHeader *ptcp;             //TCP偽首部

	pcap_if_t  * alldevs;       //所有網路介面卡
	pcap_if_t  *d;					//選中的網路介面卡
	char errbuf[PCAP_ERRBUF_SIZE];   //錯誤緩衝區,大小為256
	char source[PCAP_ERRBUF_SIZE];
	pcap_t *adhandle;           //捕捉例項,是pcap_open返回的物件
	int i = 0;                            //介面卡計數變數
	struct pcap_pkthdr *header;    //接收到的資料包的頭部
    const u_char *pkt_data;			  //接收到的資料包的內容
	int res;                                    //表示是否接收到了資料包
	u_int netmask;                       //過濾時用的子網掩碼
	char packet_filter[] = "tcp";        //過濾字元
	struct bpf_program fcode;                     //pcap_compile所呼叫的結構體

	u_int ip_len;                                       //ip地址有效長度
	u_short sport,dport;                        //主機位元組序列
	u_char packet[100];                       //傳送資料包目的地址
	pcap_dumper_t *dumpfile;         //堆檔案

	//time_t local_tv_sec;				//和時間處理有關的變數
    //char timestr[16];					//和時間處理有關的變數

	
	//獲取本地介面卡列表
	if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){
		//結果為-1代表出現獲取介面卡列表失敗
		fprintf(stderr,"Error in pcap_findalldevs_ex:\n",errbuf);
		//exit(0)代表正常退出,exit(other)為非正常退出,這個值會傳給作業系統
		exit(1);
	}
	//列印裝置列表資訊
	for(d = alldevs;d !=NULL;d = d->next){
		printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name);
		if(d->description){
			//列印介面卡的描述資訊
			printf("description:%s\n",d->description);
		}else{
			//介面卡不存在描述資訊
			printf("description:%s","no description\n");
		}
		//列印本地環回地址
		printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
		
		 pcap_addr_t *a;       //網路介面卡的地址用來儲存變數
		 for(a = d->addresses;a;a = a->next){
			 //sa_family代表了地址的型別,是IPV4地址型別還是IPV6地址型別
			 switch (a->addr->sa_family)
			 {
				 case AF_INET:  //代表IPV4型別地址
					 printf("Address Family Name:AF_INET\n");
					 if(a->addr){
						 //->的優先順序等同於括號,高於強制型別轉換,因為addr為sockaddr型別,對其進行操作須轉換為sockaddr_in型別
						 printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
					 }
					if (a->netmask){
						 printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
					}
					if (a->broadaddr){
						   printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
					 }
					 if (a->dstaddr){
						   printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
					 }
        			 break;
				 case AF_INET6: //代表IPV6型別地址
					 printf("Address Family Name:AF_INET6\n");
					 printf("this is an IPV6 address\n");
					 break;
				 default:
					 break;
			 }
		 }
	}
	//i為0代表上述迴圈未進入,即沒有找到介面卡,可能的原因為Winpcap沒有安裝導致未掃描到
	if(i == 0){
		printf("interface not found,please check winpcap installation");
	}

	int num;
	printf("Enter the interface number(1-%d):",i);
	//讓使用者選擇選擇哪個介面卡進行抓包
	scanf_s("%d",&num);
	printf("\n");

	//使用者輸入的數字超出合理範圍
	if(num<1||num>i){
		printf("number out of range\n");
		pcap_freealldevs(alldevs);
		return -1;
	}
	//跳轉到選中的介面卡
	for(d=alldevs, i=0; i< num-1 ; d=d->next, i++);

	//執行到此處說明使用者的輸入是合法的
	if((adhandle = pcap_open(d->name,		//裝置名稱
														65535,       //存放資料包的內容長度
														PCAP_OPENFLAG_PROMISCUOUS,  //混雜模式
														1000,           //超時時間
														NULL,          //遠端驗證
														errbuf         //錯誤緩衝
														)) == NULL){
        //開啟介面卡失敗,列印錯誤並釋放介面卡列表
		fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
        // 釋放裝置列表 
        pcap_freealldevs(alldevs);
        return -1;
	}
	

	//列印輸出,正在監聽中
	printf("\nlistening on %s...\n", d->description);

	//所在網路不是乙太網,此處只取這種情況
	if(pcap_datalink(adhandle) != DLT_EN10MB)
    {
        fprintf(stderr,"\nThis program works only on Ethernet networks.\n");
        //釋放列表
        pcap_freealldevs(alldevs);
        return -1;
    }

	//先獲得地址的子網掩碼
	if(d->addresses != NULL)
        //獲得介面第一個地址的掩碼 
        netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
    else
        // 如果介面沒有地址,那麼我們假設一個C類的掩碼
        netmask=0xffffff;

	//pcap_compile()的原理是將高層的布林過濾表
	//達式編譯成能夠被過濾引擎所解釋的低層的位元組碼
	if(pcap_compile(adhandle,	//介面卡處理物件
										&fcode,
										packet_filter,   //過濾ip和UDP
										1,                       //優化標誌
										netmask           //子網掩碼
										)<0)
	{
		//過濾出現問題
		fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
        // 釋放裝置列表
        pcap_freealldevs(alldevs);
        return -1;
	}

	//設定過濾器
    if (pcap_setfilter(adhandle, &fcode)<0)
    {
        fprintf(stderr,"\nError setting the filter.\n");
        //釋放裝置列表
        pcap_freealldevs(alldevs);
        return -1;
    }


	//利用pcap_next_ex來接受資料包
	while((res = pcap_next_ex(adhandle,&header,&pkt_data))>=0)
	{
		if(res ==0){
			//返回值為0代表接受資料包超時,重新迴圈繼續接收
			continue;
		}else{
			//執行到此處代表接受到正常從資料包
			//header為幀的頭部
			printf("%.6ld len:%d ", header->ts.tv_usec, header->len);
			// 獲得IP資料包頭部的位置
			ip = (IpHeader *) (pkt_data +14);    //14為乙太網幀頭部長度
			//獲得TCP頭部的位置
			ip_len = (ip->Version_HLen & 0xf) *4;
			printf("ip_length:%d ",ip_len);
			tcp = (TcpHeader *)((u_char *)ip+ip_len);
			char * data;
			 data = (char *)((u_char *)tcp+20);
			 //將網路位元組序列轉換成主機位元組序列
			sport = ntohs( tcp->SrcPort );
			dport = ntohs( tcp->DstPort );
			printf("srcport:%d desport:%d\n",sport,dport);
			printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n",
					ip->SourceAddr.byte1,
					ip->SourceAddr.byte2,
					ip->SourceAddr.byte3,
					ip->SourceAddr.byte4,
				    sport,
				    ip->DestinationAddr.byte1,
				    ip->DestinationAddr.byte2,
				    ip->DestinationAddr.byte3,
				    ip->DestinationAddr.byte4,
				    dport);
			printf("%s\n",data);
		}

	}

	
	//釋放網路介面卡列表
	pcap_freealldevs(alldevs);

	/**
	int pcap_loop  ( pcap_t *  p,  
								  int  cnt,  
								  pcap_handler  callback,  
								  u_char *  user   
								 );
     typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *,
                 const u_char *);
	*/
	//開始捕獲資訊,當捕獲到資料包時,會自動呼叫這個函式
	//pcap_loop(adhandle,0,packet_handler,NULL);

	int inum;
	scanf_s("%d", &inum);

	return 0;

}

/* 每次捕獲到資料包時,libpcap都會自動呼叫這個回撥函式 */
/**
pcap_loop()函式是基於回撥的原理來進行資料捕獲的,如技術文件所說,這是一種精妙的方法,並且在某些場合下,
它是一種很好的選擇。但是在處理回撥有時候會並不實用,它會增加程式的複雜度,特別是在多執行緒的C++程式中
*/
/*
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
    struct tm *ltime = NULL;
    char timestr[16];
    time_t local_tv_sec;

    // 將時間戳轉換成可識別的格式
    local_tv_sec = header->ts.tv_sec;
    localtime_s(ltime,&local_tv_sec);
    strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);

    printf("%s,%.6ld len:%d\n", timestr, header->ts.tv_usec, header->len);

}
*/
/* 將數字型別的IP地址轉換成字串型別的 */
#define IPTOSBUFFERS    12
char *iptos(u_long in)
{
    static char output[IPTOSBUFFERS][3*4+3+1];
    static short which;
    u_char *p;

    p = (u_char *)&in;
    which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
    sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
    return output[which];
}

執行截圖如下




Thank You

如有問題,歡迎留言~