1.兩者實現的區別
用 Raw Socket 實現 Sniffer 的方法,實現起來比較簡單,但有個缺點就是隻能截獲 IP 層以上的包,數據包頭不含幀信息。但是相對於來說實現起來會比較簡單。使用的話就見仁見智了,根據自己的需要去選擇即可。
2.Raw Socket
操作環境:vs2005 其他的環境下代碼有可能需要改動
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include "fstream"
#include <iostream>
using namespace std;
#pragma comment (lib,"ws2_32.lib")
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
#define IP_HDRINCL 2
struct IPHEAD
{
unsigned char h_len:4;//4位首部長度+4位IP版本號
unsigned char ver:4;
unsigned char tos;//8位服務類型TOS
unsigned short total_len;//16位總長度(字節)
unsigned short ident;//16位標識
unsigned short frag_and_flags;//3位標誌位
unsigned char ttl;//8位生存時間 TTL
unsigned char proto;//8位協議 (TCP, UDP 或其他)
unsigned short checksum;//16位IP首部校驗和
unsigned int sourceip;//32位源IP地址
unsigned int destip;//32位目的IP地址
};
struct TCPHEAD //定義TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列號
unsigned int th_ack; //32位確認號
unsigned char th_lenres; //4位首部長度/6位保留字
unsigned char th_flag; //6位標誌位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校驗和
USHORT th_urp; //16位緊急數據偏移量
};
char *phostlist[10];//列舉主機網卡的數組
DWORD _stdcall listen(void *p)
{
SOCKET s;
struct sockaddr_in addr;
int itimeout=10000;
int ret;
BOOL flag;
char cbuf[1500];//接收數據緩衝區
struct IPHEAD *piphd;//定義IP頭結構
struct TCPHEAD *ptcphd;//定義TCP頭結構
s=socket(AF_INET,SOCK_RAW,IPPROTO_IP); //創建一個原始套接字
if (s == INVALID_SOCKET)
{
printf("創建原始套接字失敗\n");
system("pause");
}
//設置 IP 頭操作選項
setsockopt(s,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag));
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=inet_addr((char *)p);
addr.sin_port=htons(6000);//設置本地端口號
bind(s,(struct sockaddr *)&addr,sizeof(addr));//綁定端口
//設置sock_raw爲sio_rcvall,以便接收所有IP包
DWORD dwin=1;
ioctlsocket(s,SIO_RCVALL,&dwin);
for(;;)
{
memset(cbuf,0,sizeof(cbuf));
ret=recv(s,cbuf,sizeof(cbuf),0);//接收數據
if(ret==SOCKET_ERROR)
{
if(WSAGetLastError()==WSAETIMEDOUT)continue;
closesocket(s);
return 0;
}
piphd=(struct IPHEAD *)cbuf;//取得IP頭數據的地址
int iIphLen = sizeof(unsigned long) * (piphd->h_len & 0xf);
ptcphd=(struct TCPHEAD *)(cbuf+iIphLen);//取得TCP頭數據的地址
printf("From : %s \t port %d\t",inet_ntoa(*(struct in_addr*)&piphd->sourceip),ntohs(ptcphd->th_sport) );
printf("To : %s \t port %d ",inet_ntoa(*(struct in_addr*)&piphd->destip),ntohs(ptcphd->th_dport));
switch(piphd->proto)//根據IP頭的協議判斷數據包協議類型
{
case 1:
printf("ICMP\n");
break;
case 2:
printf("IGMP\n");
break;
case 6:
printf("TCP\n");
break;
case 17:
printf("UDP\n");
break;
default:
printf("unknow:%d\n",piphd->proto);
}
}
return 1;
}
int main()
{
//初始化sock
WSADATA wsa;
int i=0;
DWORD dwtid;
char chname[128];
hostent *host;
WSAStartup(MAKEWORD(2,2),&wsa);
gethostname(chname,sizeof(chname));
host=gethostbyname(chname);
while(host->h_addr_list[i]!=NULL)//取所有網卡序號,爲每個網卡開啓一個監聽線程
{
phostlist[i]=(char *)malloc(16);
sprintf(phostlist[i],"%s",inet_ntoa(*(struct in_addr *)host->h_addr_list[i]));
printf("Bind to %s\n",phostlist[i]);
CreateThread(NULL,0,listen,phostlist[i],0,&dwtid); //用於定義新線程的安全屬性,一般設置成NULL 分配以字節數表示的線程堆棧的大小,默認值是0; 返回新創建的線程的ID編號
i++;
}
for(;;)//爲每個網卡創建監聽線程後要用一個循環防止主線程退出
{
Sleep(10);
}
}
主要函數:
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
setsockopt(s,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag));
設置套接字的選項。具體的可以看這個博文https://www.cnblogs.com/eeexu123/p/5275783.html
本程序設置SockRaw這個套接字的ip選項中的IP_HDRINCL即在數據包中包含IP首部。
int ioctlsocket( SOCKET s, long cmd, u_long FAR *argp );
s:一個標識套接口的描述字。
cmd:對套接口s的操作命令。
argp:指向cmd命令所帶參數的指針。
即設置sock_raw爲sio_rcvall,以便接收所有IP包
3.winpcap
關於這個的代碼到處都是,我也是直接在網上粘了一段,稍微改了一下。
1)配置環境
參考自:https://blog.csdn.net/gk405128494/article/details/39314467
1.到http://www.winpcap.org/install/default.htm下載winpcap的安裝包,然後到http://www.winpcap.org/devel.htm程序員開發包。
2.執行安裝包,這樣你的機子就能運行winpcap程序了。
3.解壓開發包,在VC6.0的Tools–>Option–>Directories的Include fils 和library files加入winpcap的include和lib目錄。
4.開始編寫wpcap程序。
vs2005:
第一步:下載WinPcap的安裝包;有不同操作系統環境下的包,我下的是win32版本的。下載地址:www.winpcap.org;最新的Release版本是4.0.1的,最高的版本一般是Beta的。這個安裝包主要是註冊一個wpcap.dll的庫到操作系統中。必須安裝,如果不安裝,在運行例子的時候會彈出窗口提示,找不到wpcap.dll文件;
第二步:到上面的網站下載它的開發包,包括一些頭文件和庫文件;解壓到自己指定的目錄中;目錄中還有HTML格式的說明文檔,用於自己學習比較方便;
第三步:設置VS2005;1)設置環境目錄;在菜單:工具->選項;彈出的選項窗體左邊點擊:項目和解決方案->VC++目錄;在右邊:“顯示以下內容的目錄”標籤下面的下拉框中找到“包含文件”,然後對應到第二步下載開發包的Include目錄;在同一下拉框中找到“庫文件”,然後對應到第二步下載開發包的lib目錄;2)設置編譯條件;在項目屬性頁中:配置屬性->C/C++->預處理器->預處理器定義,增加;WPCAP;HAVE_REMOTE;每一個預定義符用”;”隔開;在項目屬性頁中:配置屬性->鏈接器->命令行->附加選項對應的文本框中增加:“wpcap.lib ws2_32.lib”;
第四步:非必要步驟;有的時候可能會有些意外錯誤;比如找不到u_char類型等;我的解決辦法是加上
這裏寫代碼片
#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#else
#include <winsock.h>
#endif
2)代碼段:
參考自:http://doc.okbase.net/29062706/archive/10224.html
正則表達式在設置時一定要注意
#define WIN32
#include <pcap.h>
#include <WinSock.h>
#ifndef WIN32
#include<sys/socket.h>
#include<netinet/in.h>
#else
#include<WinSock.h>
#endif
#pragma comment(lib, "wpcap.lib")
/* 4 字節IP地址 */
typedef struct ip_address
{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/*
// IP包如下所示,請參見 RFC791.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
/* IPv4 頭 */
typedef struct ip_header
{
u_char ver_ihl; //4位首部長度,4位IP版本號
u_char tos; //8位服務類型TOS
u_short tlen; //16位總長度(字節)
u_short identification; //16位標識
u_short flags_fo; //3位標誌位,13位偏移
u_char ttl; //8位生存時間 TTL
u_char proto; //8位協議 (TCP, UDP 或其他)
u_short crc; //16位IP首部校驗和
ip_address saddr; //32位源IP地址
ip_address daddr; //32位目的IP地址
u_int op_pad; //32位選項
}ip_header;
/*
// TCP包如下所示,請參見 RFC793.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
//定義TCP首部
typedef struct tcp_header
{
u_short sport; //16位源端口
u_short dport; //16位目的端口
u_int seq; //32位序列號
u_int ack; //32位確認號
u_char lenres; //4位首部長度/6位保留字
u_char flag; //6位標誌位
u_short win; //16位窗口大小
u_short sum; //16位校驗和
u_short urp; //16位緊急數據偏移量
u_int op_pad; //32位選項
}tcp_header;
/*
// UDP包如下所示,請參見 RFC768.
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
|
| data octets ...
+---------------- ...
*/
/* UDP header */
typedef struct udp_header
{
u_short sport; //16位源端口
u_short dport; //16位目的端口
u_short len; //16位長度
u_short crc; //16位校驗和
}udp_header;
// sniffer.c : 定義控制檯應用程序的入口點。
int main(int argc, char **argv)
{
pcap_if_t* alldevs;
pcap_if_t* d;
int inum;
int i = 0;
pcap_t* fp;
char *ofilename = "sniffer.txt";
char errbuf[PCAP_ERRBUF_SIZE];//存儲錯誤信息
u_int netmask;//掩碼
int res;
struct pcap_pkthdr *header;
const u_char *pkt_data;
char packet_filter[] = "tcp or udp";
pcap_dumper_t *dumpfile;
struct bpf_program fcode;
ip_header* ih;
tcp_header* th;
udp_header* uh;
u_int ip_len;
u_short sport, dport;
// 獲得網絡設備指針
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr, "pcap_findalldevs出錯: %s\n", errbuf);
exit(1);
}
// 枚舉網卡信息,首地址
for (d = alldevs; d; d = d->next)
{
printf("%d. %s", ++ i, d->name);
if (d->description)
{
printf(" (%s)\n", d->description);
}
else
{
printf(" (無描述信息)\n");
}
}
if (i == 0)
{
printf("\n沒發現任何接口,請確認是否已經安裝WinPcap庫.\n");
return -1;
}
printf("請輸入網卡接口號 (1 - %d):", i);
scanf("%d", &inum);
if (inum < 1 || inum > i)
{
printf("\n接口號超出範圍.\n");
// 釋放alldevs資源
pcap_freealldevs(alldevs);
return -1;
}
// 跳到選擇的網卡
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
// 打開網卡
if ((fp = pcap_open(d->name, // 設備名稱
65536,
PCAP_OPENFLAG_PROMISCUOUS, // 混雜模式
1000, // 讀取超時時間
NULL, // 遠程認證,本程序因是本地嗅探,不需要設置
errbuf
)) == NULL)
{
fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Winpcap\n");
// 釋放alldevs資源
pcap_freealldevs(alldevs);
return -1;
}
// 檢查鏈路層,本程序只簡單的支持以太網
if (pcap_datalink(fp) != DLT_EN10MB)
{
fprintf(stderr, "\n本程序只簡單的支持以太網.\n");
// 釋放alldevs資源
pcap_freealldevs(alldevs);
return -1;
}
if (d->addresses != NULL)
{
// 取得一個網絡接口的子網掩碼
netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
}
else
{
netmask = 0xffffffff;
}
// 編譯過濾器
if (pcap_compile(fp, &fcode, packet_filter, 1, netmask) < 0)
{
fprintf(stderr, "\nU不能編譯包過濾,請檢查正則表達式.\n");
// 釋放alldevs資源
pcap_freealldevs(alldevs);
return -1;
}
// 設置過濾器
if (pcap_setfilter(fp, &fcode) < 0)
{
fprintf(stderr, "\n設置過濾器失敗.\n");
// 釋放alldevs資源
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s ...\n", d->description);
// 釋放alldevs資源,因爲不再需要它
pcap_freealldevs(alldevs);
// 打開輸出文件
dumpfile= pcap_dump_open(fp, ofilename);
if (dumpfile == NULL)
{
fprintf(stderr,"\n打開輸出文件錯誤\n");
pcap_close(fp);
return -1;
}
// 開始捕獲數據包
while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
{
if(res == 0)
/* 超時繼續 */
continue;
// 將捕獲的數據包存入文件
pcap_dump((unsigned char *) dumpfile, header, pkt_data);
// 取得IP頭,14爲以太網頭長度
ih = (ip_header*)(pkt_data + 14);
ip_len = (ih->ver_ihl & 0xf) * 4;
if (ih->proto == 6) //TCP
{
//取TCP頭
th = (tcp_header*)((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
sport = ntohs(th->sport); //網絡字節序轉主機字節序
dport = ntohs(th->dport); //網絡字節序轉主機字節序
/* 打印IP地址和端口號 */
printf("%d.%d.%d.%d:%d-> %d.%d.%d.%d:%d\n",
ih->saddr.byte1,
ih->saddr.byte2,
ih->saddr.byte3,
ih->saddr.byte4,
sport,
ih->daddr.byte1,
ih->daddr.byte2,
ih->daddr.byte3,
ih->daddr.byte4,
dport);
}
else if (ih->proto == 17) //UDP
{
//取UDP頭
uh = (udp_header*)((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
sport = ntohs(uh->sport); //網絡字節序轉主機字節序
dport = ntohs(uh->dport); //網絡字節序轉主機字節序
/* 打印IP地址和端口號 */
printf("%d.%d.%d.%d:%d-> %d.%d.%d.%d:%d\n",
ih->saddr.byte1,
ih->saddr.byte2,
ih->saddr.byte3,
ih->saddr.byte4,
sport,
ih->daddr.byte1,
ih->daddr.byte2,
ih->daddr.byte3,
ih->daddr.byte4,
dport);
}
}
pcap_close(fp);
pcap_dump_close(dumpfile);
return 0;}