利用原始套接字实现对流经本机IP包的捕获

时间:2021-05-14 10:59:45

经过上一篇博客的总结,我知道到了原始套接字接收到的字符串的开始字段是IP数据报的首部,所以我想除了之前利用win_pcap可以捕获数据包以外,理论上来说原始套接字也可以实现对IP数据报的捕获。思路也很简单,捕获到字符串以后转换成指向IP首部结构体的指针,再打印相关信息就可以了。

当然由于网卡会默认丢掉不属于本机的数据包,所以需要将套接字设置为接收所有数据。win_pcap是直接对网卡进行设置。ioctlsocket( )函数用于控制套接字上的I/O行为,同时获取与那个套接字上挂起的网络I/O操作的有关信息。

int ioctlsocket(
SOCKET s,//套接字
long cmd,//对s的命令
u_long* argp//命令参数指针
)//如果成功的话返回0,如果失败的话返回错误代码
函数的示例(让socket接收所有数据包):

u_long flag=1;
ioctlsocket(s,SIO_RCVALL,&flag);

于是有了以下一段代码:

#include "stdafx.h"
#include <winsock2.h>
#include <ws2tcpip.h> 
#include "mstcpip.h"     //自定义
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

struct IPHEADER
{
	unsigned char h_verlen; //4位首部长度,4位IP版本号 
	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首部校验和 
	in_addr sourceIP; //32位源IP地址 
	in_addr destIP; //32位目的IP地址 
};

#define BUFSIZE 256
#define RECVBUF 1520

int analyzeIP(IPHEADER *pip, int buflen)
{
	if (buflen < 20)
	{
		printf("此包不完整!\n");
	}
	printf("版本号:%d\n", pip->h_verlen >> 4);
	printf("首部长度(字节):%d\n", (pip->h_verlen & 0x0f)*4);
	printf("总长度:%d\n", pip->total_len);
	printf("协议类型:%d\n", pip->proto);
	printf("源地址:%s\n", inet_ntoa(pip->sourceIP));
	printf("目的地址:%s\n", inet_ntoa(pip->destIP));
	return 0;
}

int main()
{
	SOCKET s;
	WSADATA wsa;
	int retval;
	retval = WSAStartup(MAKEWORD(2, 2), &wsa);
	if (retval == SOCKET_ERROR)
	{
		printf("startup failed\n");
		WSACleanup();
		return 0;
	}
	s = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
	if (s == INVALID_SOCKET)
	{
		printf("socket error\n");
		WSACleanup();
		return 0;
	}
	sockaddr_in addr;
	char hostname[BUFSIZE] = { 0 };
	int namelen;
	namelen = gethostname(hostname, BUFSIZE);//取得本机名,存放在hostname里面
	hostent *ph = gethostbyname(hostname);
	if (ph == NULL)
	{
		printf("gethostbyname failed\n");
		WSACleanup();
		closesocket(s);
		return 0;
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons(0);
	memcpy(&(addr.sin_addr), ph->h_addr, ph->h_length);
	retval = bind(s, (sockaddr*)&addr, sizeof(sockaddr_in));
	if (retval == SOCKET_ERROR)
	{
		printf("bind error\n");
		closesocket(s);
		WSACleanup();
		return 0;
	}
	unsigned long flag = 1;
	retval = ioctlsocket(s, SIO_RCVALL, &flag);//将网卡设置为混杂
	if (retval != 0)
	{
		printf("ioctlsocket error\n");
		closesocket(s);
		WSACleanup();
		return 0;
	}
	char recvbuf[RECVBUF];
	size_t size;
	IPHEADER *p_ipheader;
	int cnt = 0;
	while (1)
	{
		size = recv(s, recvbuf, RECVBUF, 0);
		if (size == 0 || size == SOCKET_ERROR)
		{
			printf("recv error\n");
			closesocket(s);
			WSACleanup();
			continue;
		}
		cnt++;
		printf("第 %d 个包\n", cnt);
		p_ipheader = (IPHEADER*)recvbuf;
		analyzeIP(p_ipheader, size);
		memset(recvbuf, 0, sizeof(recvbuf));
	}
	WSACleanup();
	closesocket(s);
	return 0;
}

利用原始套接字实现对流经本机IP包的捕获

但当我把这个程序和win_pcap的程序一起运行的时候,发现这个程序捕获的包没有win_pcap的程序捕获得多,而且这个程序捕获的包部分信息也很奇怪,比如源地址,目的地址等,于是我猜想这可能是windows处于安全原因对原始套接字的使用也做出了限制。