一、原理
用一些比喻,集线器网络中所有主机都连接在一条网线上,也就是说每台主机发送的数据都将经过其他主机的门前(网卡),只是说一般网卡一看数据包报头,这不是发给自己的,就不理这个包了,而我们现在要做的,就是敞开大门,不管这个包是发给谁的,都将其纳入囊中。那么如何做呢?只需将网卡设置为混杂模式。
要用到C语言网络编程中的Socket,一般的Socket只能获取到传输数据包,所以我们要用的是Raw SOCKET(原始Socket),它可以工作在链路层或者IP层(取决于创建时的参数设置)。
二、实现
原始套接字的创建方法跟TCP/IP创建方法几乎一模一样:
int sockfd;
sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
这两句程序你就可以创建一个原始套接字。这种类型套接字的功能与TCP或者UDP类型套接字的功能有很大的
不同:TCP/UDP类型的套接字只能够访问传输层以及传输层以上的数据,因为当IP层把数据传递给传输层时,下的
数据包头已经被丢掉了。而原始套接字却可以访问传输层以下的数据,所以使用raw套接字你可以实现上至应用层
的数据操作,也可以实现下至链路层的数据操作。
三、代码
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#pragma comment(lib,"WS2_32.lib")
#define BUFFER_MAX 2048
#define IP_HDRINCL 2
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
using namespace std;
int main(){
SOCKET sock;
int n_read,proto;
int flag = 1;
char buffer[BUFFER_MAX];
char LocalName[256];
char *ethhead,*iphead,*tcphead,*udphead,*icmphead,*p;
WSADATA WSAData;
if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0)
{
printf("WSAStartup ERROR.\\n"); //如果初始化WSADATA结构得到错误码,则显示出错信息
return -1;
}
// if(sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)<0){
// cout<<"Socket创建失败"<<endl;
// exit(0);
// }
// setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag)); //设置 IP 头操作选项
创建并设置原始套接字
sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP);//启用winsock创建原始套接字,SOCK_RAW 类型表示原始套接字类型混杂模式,也就是接收所有包
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag));//设置原始套接字,设置 IP 头操作选项
把原始套接字绑定到本地主机网卡上,实现将本地网卡置于混杂模式
if(gethostname((char*)LocalName, sizeof(LocalName)-1)!=0) //获取本地主机IP
{
printf("获取主机名失败 Error:%d.\\n",WSAGetLastError());
return -1;
}
gethostname((char*)LocalName, 256);//把本地主机名存放入指定的缓冲区中
hostent *pHost = gethostbyname((char*)LocalName);
SOCKADDR_IN addr_in; //获取本地 IP 地址
addr_in.sin_addr = *(in_addr *)pHost->h_addr_list[0];
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(40000);
if(bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in))!=0) //把原始套接字绑定在本地主机网卡上
{
printf("绑定失败:%d.\\n",WSAGetLastError()); //绑定失败时显示提示信息
return -1;
}
//设置原始套接字能够接受所有的数据
DWORD dwValue = 1;
if(ioctlsocket(sock, SIO_RCVALL, &dwValue)!=0)
{
printf("ioctlsocket Error:%d.\\n",WSAGetLastError()); //设置失败时显示提示信息
return -1;
}
while(true){
int ret = recv(sock,buffer,BUFFER_MAX,0);
if(ret>0){
ethhead = buffer;
p = ethhead;
int n = 0XFF;
//链路层前6+6+2个字节为目的MAC 源MAC type
// printf("MAC: %.2X:%-02X-%.02X-%.02X-%.02X-%.02X ==> %.2X-%.2X-%.2X-%.2X-%.2X-%.2X-\n",p[6]&n,p[7]&n,p[8]&n,p[9]&n,p[10]&n,p[11]&n,
// p[0]&n,p[1]&n,p[2]&n,p[3]&n,p[4]&n,p[5]&n);
iphead = ethhead;// + 14;
p = iphead + 12;
//数据包前14个字节后为20字节的第九个字节为协议,第12-16,17-20个字节为源IP,目的IP
printf("IP: %d.%d.%d.%d ==> %d.%d.%d.%d\n",p[0]&0XFF,p[1]&0XFF,p[2]&0XFF,p[3]&0XFF,p[4]&0XFF,p[5]&0XFF,p[6]&0XFF,p[7]&0XFF);
short length = (short)(iphead+2)[0];
printf("总长度:%d\n",length);
proto = (iphead+9)[0];
p = iphead + 20;
printf("Protocol:");
switch(proto){
case 1: printf("ICMP\n");break;
case 2: printf("IGMP\n");break;
case 4: printf("IP\n");break;
case 6: printf("TCP\n");break;
case 8: printf("EGP\n");break;
case 9: printf("IGP\n");break;
case 17: printf("UDP\n");break;
case 41: printf("IPv6\n");break;
case 50: printf("ESP\n");break;
case 89: printf("OSPF\n");break;
default: printf("不知道,协议编号是%d,请自己查文档\n",proto);break;
}
}
cout<<endl;
Sleep(200);
}
}