利用原始套接字的抓包原理

利用原始套接字的抓包原理:

抓包层

发送接收ip数据包 【接收除了以太网帧头部后面的ip层数据】
socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)
发送接收以太网数据帧数据包 【接收包括以太网帧头部的所有 以太网帧层的所有数据】
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))
参数说明:
1)AF_INET和PF_PACKET的区别
使用AF_INET可以接收协议类型为(tcp udp icmp等)发往本机的ip数据包,而使用PF_PACKET可以监听网卡上的所有数据帧。
注:创建原始套接字需要启动管理员权限否则,socket会创建失败

ip与端口的假象【混杂模式下端口号,IP可能都是假象】【原始套接字bind IP的目的是bind网卡】
然后需要为原始套接字 bind ip和端口号;这里端口号可以使用ADDR_ANY随机指定一个;因为对于原始套接字来说 端口号只是一个假象,原始套接字可以接收所有其绑定ip对应的网卡适配器上面流经的数据;
所以ip也是一种假象,重要的是为原始套接字绑定一个网卡适配器也就是所谓的网口;如果一台主机上有多个网口需要抓包,则需要创建多个套接字bind所有这些的网口;然后在这些套接字上监听抓取数据

然后设置混杂模式 SIO_RCVALL

网卡对数据帧进行硬过滤(根据网卡的模式不同采取不同的操作,如果设置了混杂模式,则不做任何过滤直接交给下一层,否则非本机mac或者广播mac的会被直接丢弃)

   DWORD dwBufferLen[10] = { 0 };
   DWORD dwBufferInLen = 1;
   DWORD dwBytesReturned = 0;
   // 设置该SOCKET为接收所有流经绑定的IP的网卡的所有数据,包括接收和发送的数据包
   iRet = WSAIoctl(sock, SIO_RCVALL, &dwBufferInLen, sizeof(dwBufferInLen),//设置混杂模式
          &dwBufferLen, sizeof(dwBufferLen), &dwBytesReturned, NULL, NULL);
   if (iRet != 0) {
          printf("WSAIoctl failed with error %d\n", WSAGetLastError());
   }

最后 可以调用iRet = recvfrom(sock, strBuffer, sizeof(strBuffer), 0, (sockaddr*)&addrFrom, &fromlen); 函数进行数据包抓取了

原始套接字抓取包的大小的决定因素

以socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP) 进行ip包的数据抓取举例

对于ip包层来说,他不管包是TCP发的还是UDP发的,这都是上层协议;包都是ip包
原始套接字上即可以抓取由TCP发送过来的数据也可以抓取由UDP发送来的数据,对于ip包来说他是不管这个包是什么包的;因为那是他的上层协议去做的事情;那么在抓包的过程中接受的包的大小由什么决定了呢

ip包层抓取的数据包大小就是对端用TCP或者UDPsend发送的长度 + 40或者28【即加上IP报头和TCP或者UDP报头的长度】;【另外要说一点是虽然以太网帧协议的数据域大小上线是1500字节;如果发生端send发送的数据超过这个上线,在发送端就会对这个包进行分片;但是这一切对ip包层抓取的包来说是无感的;即在IP包层已经重组了以太网帧的1500数据域,即ip包层可以突破1500的抓包】

IP包层抓包对于这二种协议的接收都不应该低于数据域+40/28【以发送端send为标准】。否则IP包层接收recvform会返回错误;
ip包的接收缓冲区大小不能低于其接收的数据包大小;而其数据包大小=40/28 +数据域大小;这个数据域大小就是发送端用tcp或者udp进行数据发送时候的send的大小【注意是以发送端为标准】
所以对于ip包来说其处理接收缓冲区的方式有点类似于UDP包层的处理;但是仅仅是类似,ip包层不用管上层的协议对应的究竟是什么包;接收的大小也可以突破以太网帧的数据域大小;这就是协议栈分离效果的体现

从这点我们也可以来理解上层UDP/tcp的接收行为;【在udp层udp接受缓冲区必须不小于发生端包长度,TCP层可以流式接收】

对于udp来说很好理解,udp发送端发送的数据包大小就是其udp接收端接收的数据包大小;也是抓包时 ip数据包的大小的数据域组成;=>抓包接收的大小是ip包=28+udp包
但是对于tcp来说在传输层面上,tcp是流式协议,接收方的tcp 可以一个一个字节接收;可以一次性接收都可以;但是这跟ip包的接收recvform得到的大小无关;=>其ip抓包的大小是=40+tcp发送端发送的大小
所以用普通流式套接字你是可以一个一个的接收;但是原始套接字抓包,必须要提供的接收缓冲区不小于 20 + 20/8 +数据域大小 【数据域大小可能是0】

示例代码:
#include 
#include 
#include "protocol.h"
#pragma comment(lib,"WS2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;
WsaInit Init;
// 使用原始套接字抓取网卡数据包
int capturenetpackage()
{
       // 此处必须以管理员运行VS,否则返回10013
       SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP); //处理 ip
       if (sock == SOCKET_ERROR) {
              printf("socket failed with error %d\n", WSAGetLastError());
              exit(-1);
       }
       char strHostName[255];
       int iRet = gethostname(strHostName, sizeof(strHostName));
       if (iRet != 0) {
              printf("gethostname failed with error %d\n", WSAGetLastError());
              exit(-1);
       }
       // 根据主机名取得主机地址
       hostent* pHostent = gethostbyname(strHostName);
//     getaddrinfo()
       sockaddr_in addrSelf;
       addrSelf.sin_family = AF_INET;
       addrSelf.sin_port = htons(ADDR_ANY);//端口是假象
       memcpy(&addrSelf.sin_addr.S_un.S_addr, pHostent->h_addr_list[0],  pHostent->h_length);
       char szip[32]{};
       for (int i = 0; pHostent->h_addr_list[i] != 0; ++i)
       {
              addrSelf.sin_addr.s_addr = *(u_long*)pHostent->h_addr_list[i];
              inet_ntop(AF_INET, &addrSelf.sin_addr, szip, 32);
              //printf("\tIPv4 Address %d: %s\n", i, szip);
       }
       printf("self local ip addr is %s\n", szip);
       iRet = bind(sock, (PSOCKADDR)&addrSelf, sizeof(addrSelf));//bind网口 //ip也是假象 关键在于网口
       if (iRet != 0) {
              printf("bind failed with error %d\n", WSAGetLastError());
              exit(-1);
       }
       DWORD dwBufferLen[10] = { 0 };
       DWORD dwBufferInLen = 1;
       DWORD dwBytesReturned = 0;
       // 设置该SOCKET为接收所有流经绑定的IP的网卡的所有数据,包括接收和发送的数据包
       iRet = WSAIoctl(sock, SIO_RCVALL, &dwBufferInLen, sizeof(dwBufferInLen),
              &dwBufferLen, sizeof(dwBufferLen), &dwBytesReturned, NULL, NULL);
       if (iRet != 0) {
              printf("WSAIoctl failed with error %d\n", WSAGetLastError());
              
       }
       //14+20 +20   14+20+8
       sockaddr_in addrFrom;
       int fromlen = sizeof(addrFrom);
       time_t temp;
       char strFromIP[16]{};
       char strCurTime[32]{};
       char strBuffer[35]{};
       char strData[4096]  {  };
       IPHeader ipData;
       TCPHeader tcpData;
       UDPHeader udpData;
       NetData netData;
       while (true)
       {
              memset(strBuffer, 0, sizeof(strBuffer));
              iRet = recvfrom(sock, strBuffer, sizeof(strBuffer), 0,  (sockaddr*)&addrFrom, &fromlen);
              if (iRet <= 0) {
                     printf("recv failed with error %d\n", WSAGetLastError());
                     continue;
              }
              char szTemp[32]{};
              inet_ntop(AF_INET, &addrFrom.sin_addr, szTemp, 32);
              strcpy(strFromIP, szTemp);
              
              //            if(strcmp(strFromIP, "192.168.0.104") == 0
              //                   || strcmp(strFromIP, "192.168.0.1") == 0
              //                   || strcmp(strFromIP, "192.168.0.103") == 0){
              //                   continue;
              //            }
              /*if (strcmp(strFromIP, "192.168.37.1") != 0) { //设置一种逻辑过滤
                     continue;
              }*/
              // 处理IP包头数据
              memcpy(&ipData, strBuffer, sizeof(ipData));
              BYTE Protocol = 0;
              sockaddr_in addrSrc, addrDst;
              char strSrcIP[16] = { 0 }, strDstIP[16] = { 0 };
              addrSrc.sin_addr.S_un.S_addr = ipData.SrcAddr;
              addrDst.sin_addr.S_un.S_addr = ipData.DstAddr;
              inet_ntop(AF_INET, &addrSrc.sin_addr, szTemp, 32);
              strcpy(strSrcIP, inet_ntoa(addrSrc.sin_addr));
              inet_ntop(AF_INET, &addrDst.sin_addr, szTemp, 32);
              strcpy(strDstIP, inet_ntoa(addrDst.sin_addr));
              /*if (strcmp(strSrcIP, "192.168.0.104") != 0 || strcmp(strDstIP,  "192.168.0.104") != 0) { //设置一种逻辑过滤
                     continue;
              }*/
              int iSrcPort = 0;
              int iDstPort = 0;
              // UDP协议
              if (ipData.Protocol == IPPROTO_UDP)
              {
                     /*if (iRet != 1052) {
                           continue;
                     }*/
                     memcpy(&udpData, strBuffer + sizeof(ipData),  sizeof(udpData));
                      iSrcPort = ntohs(udpData.SrcPort);
                      iDstPort = ntohs(udpData.DstPort);
                     memcpy(&netData, strBuffer + sizeof(ipData) +  sizeof(udpData), sizeof(netData));
              }
              // TCP协议
              else if (ipData.Protocol == IPPROTO_TCP)
              {
                     /*if (iRet != 1064) {
                           continue;
                     }*/
                     memcpy(&tcpData, strBuffer + sizeof(ipData),  sizeof(tcpData));
                      iSrcPort = ntohs(tcpData.SrcPort);
                      iDstPort = ntohs(tcpData.DstPort);
                     memcpy(&netData, strBuffer + sizeof(ipData) +  sizeof(tcpData), sizeof(netData));
              }
              else {}
              time(&temp);
              strftime(strCurTime, sizeof(strCurTime), "%Y-%m-%d %H:%M:%S",  localtime(&temp));
              
              if (ipData.Protocol == IPPROTO_TCP)
                     printf("TCP Catch!\n");
              else if (ipData.Protocol == IPPROTO_UDP)
                     printf("UDP Catch!\n");
              printf("pack catch Time:%s PackLength:%d\n", strCurTime, iRet);
              printf("strSrcIP:%s iSrcPort:%d\n", strSrcIP, iSrcPort);
              printf("strDstIP:%s iDstPort:%d\n", strDstIP, iDstPort);
       }
       return 0;
}
int main()
{
       capturenetpackage();
       return 0;

参考链接:https://blog.csdn.net/Toobad321/article/details/77869543
https://blog.csdn.net/caoshangpa/article/details/51530685

你可能感兴趣的:(网络,udp,tcp/ip)