C++实现原始套接字捕获数据包

  • 引言
  • 原始套接字与TCP套接字和UDP套接字的区别
  • 原始套接字编程使用的场合
  • 原始套接字的通信过程
    • (1)基于原始套接字的数据发送过程
    • (2)基于原始套接字的数据接收过程
  • 创建原始套接字
  • 常用协议定义列表
  • 使用原始套接字接收数据
  • 使用原始套接字接收数据包
  • 源代码如下

引言

原始套接字是允许访问底层传输协议的一种套接字类型,提供了普通套接字所不具备的功能,能够对网络数据包进行某种程度的控制操作。因此原始套接字通常用开发简单网络性能监视程序以及网络探测、网络攻击等工具。今天我们来探索一下,从实现原始套接字到捕获数据包的整个过程。

原始套接字与TCP套接字和UDP套接字的区别

Berkeley套接字将流式套接字和数据报套接字定义为标准套接字,用于在主机之间通过TCP和UDP来传输数据。为了保证Internet的使用效率,除了传输数据之外,操作系统的协议栈还处理了大量的非数据流量,如果程序员在创建应用时也需要对这些非数据流量进行控制的话,那么此时就需要另一种套接字,即原始套接字。这种套接字越过了TCP/IP协议栈的部分层次,为程序员提供了完全且直接的的数据包级别的Internet访问能力,如下图所示。

  • 具有发送和接收ICMPv4、IGMPv4、ICMPv6等分组
  • 具有发送和接收内核不处理其协议字段的IPv4数据包
  • 可以控制IPv4首部


从图中我们可以清晰看出,对于普通流式套接字和数据包套接字的应用程序,他们只能控制数据包的数据部分,也就是除了传输层首部和网络层首部以外的,需要通过网络传输的数据部分。而传输层首部和网络层首部则由协议栈根据创建套接字时候指定的参数负责填充,显而易见的是,这两部分,开发者是无法实现管理的,此时就有了原始套接字的用武之地,它可以控制传输层首部,也可以控制网络层层首部,给程序员带来了很大的灵活性。

原始套接字编程使用的场合

尽管原始套接字的功能强大,可以构造TCP和UDP的协议数据完成数据传输,但是该套接字类型也有其局限性。在网络层上,原始套接字基于不可靠的IP分组传输服务,与数据报套接字类似,这种服务的特点是无连接、不可靠。无连接的特点决定了原始套接字的传输非常灵活,具有资源消耗小,处理速度快的巨大优点,不可靠也意味着在网络质量不好的情况下,数据包的丢失情况可能会非常严重。结合原始套接字的开发层次和能力,适用于以下场合。

  • 1.一些特殊用途的探测应用
    原始套接字提供了直接访问硬件通信的相关能力,其工作层次决定了此类套接字具有灵活的数据构造能力,应用程序可以利用原始套接字操控TCP/IP数据包的结构和内容没实现向特殊用途的探测和扫描。因此,原始套接字适用于要求数据包构造灵活性高的应用。
  • 2.基于数据包捕获的应用
    对于从事协议分析或网络管理的人来说,各种入侵检测、流量监控以及协议分析软件是必备的工具,这些软件都有具有数据包捕获和分析的功能。原始套接字能够操控网卡进入混杂模式的工作状态,从而达到捕获流经网卡的所有数据包的目的。因此,使用原始套接字可以满足数据包的捕获和分析的应用需求。
  • 3.特俗用途的传输应用
    原始套接字能够处理内核不认识的协议数据,对于一些特殊应用,我们希望不增加内核功能,而是完全在用户层面完成对某些协议的支持,原始套接字能帮助应用数据在构造过程中修改IP首部协议字段值,并接收处理这些内核不认识的协议数据,从而完成协议功能在用户层面的扩展。

原始套接字的通信过程

(1)基于原始套接字的数据发送过程

在通信过程中,数据发送方根据协议要求,将要发送的数据填充进发送缓冲区,同时给发送数据附加上必要的协议首部,全部填写好后,将数据发送出去。
基本通信过程如下:
1)Windows Sockets DLL 初始化,协商版本号
2)创建套接字,指定使用原始套接字进行通信,根据需要设置IP控制选项
3)指定目的地址和通信端口
4)填充首部和数据
5)发送数据
6)关闭套接字
7)结束对Windows Sockets DLL 的使用,释放资源

(2)基于原始套接字的数据接收过程

在通信过程中,数据接收方设定好接受条件后,从网络中接收到与预设条件相匹配的网络数据后,,如果出现了噪声,对数据进行过滤,然后协商版本号。
1)Windows Sockets DLL 初始化,协商版本号
2)创建套接字,指定使用原始套接字进行通信,并声明特定的协议类型
3)根据需要设定接受选项
4)接收数据
5)过滤数据
6)关闭套接字
7)结束对Windows Sockets DLL 的使用,释放资源

创建原始套接字

创建原始套接字,程序首先要求操作系统创建套接字抽象层的实例,在WinSock2中,完成这个人的函数是socket()和WSASocket()。
socket()

SOCKET WSAAPT socket(_in  int af,         //缺顶套接字的通信地址族_in  int type,       //指定套接字类型_in  int protocol    //指定要使用的特定传输协议);

WSASocket()

SOCKET WSASocket(_in  int af,         //缺顶套接字的通信地址族_in  int type,       //指定套接字类型_in  int protocol    //指定要使用的特定传输协议_in  LPWSAPROTOCOL_INFO lpProtocolInfo,   //与前三个参数互斥使用,是一个指向LPWSAPROTOCOL_INFO结构的指针,该结构定义所创建套接字的特性_in  GROUP g,       //标识一个已存在的套接字组ID或指明创建一个新的套接字组_in  DWORD  dwFlags //声明一组套接字属性的描述);

bind()

int bind(_in  SOCKET  s,   //调用socket()返回的描述符_in  const struct sockaddr *name,  //地址参数,被声明为一个指向sockaddr结构的指针_in  int namelen);

常用协议定义列表

使用原始套接字接收数据

通常使用原始套接字接受数据可以调用recvfrom()或WSARecvFrom()函数实现。在这里我们需要关心两个问题。接收数据的内容和接受数据的类型。

  • 1.接受数据的内容 从接受数据的内容来看,不论套接字如何设置发送选项,对于IPV4,原始套接字接收到的数据是包括IP首部在内的完整的数据包,对于IPV6,原始套接字接收到的都是去掉了IPV6首部和所有扩展首部的净载荷。
  • 2.接收数据的类型 对于接受数据的类型,数据从协议栈提交到使用套接字的应用程序涉及两层数据的提交。如下图:


第一步:由步骤①来看,在接收到一个数据包之后,协议栈把满足以下条件的IP数据包传递到套接字实现的原始套接字部分:
1.非UDP分组或TCP分组
2.部分ICMP
3.所有的IGMP分组
4.协议栈不认识其协议字段所有IP数据包
5.重组后的分片数据

第二步:由步骤②来看,当协议栈有一个需传递到原始套接字的IP数据包时,它将检查所有进程的所有打开的原始套接字,寻找满足条件的套接字,如果满足条以下条件,每个匹配的套接字的接收缓冲区中都将受到数据包的一份拷贝:

  • 匹配的协议:对应于socket()函数或WSASocket(),如果在创建原始套接字时指定了非0的协议参数,那么接收到的数据包IP首部中的协议字段必须与指定的协议参数相匹配;
  • 匹配的目的地址:对应于bind()函数,如果通过bind()函数将原始套接字绑定到某个固定的本地IP地址,那么接收到的数据包目的地址必须与绑定地址相符,如果没有将原始套接字绑定到本地的某个IP地址上,那么不考虑数据包的目标IP,将符合其他条件的所有IP数据包都复制到该套接字的接收缓冲区
    中;
  • 匹配的源地址:对应于connect()函数或WSASocket()函数,如果通过调用connect()函数或WSASocket()函数为原始套接字指定外部地址,那么接收到的数据包的源IP地址必须与上述已连接大外部IP地支相匹配,如果没有为该原始套接字指定外部地址,那么所有来源的满足条件的IP数据包都将被复制到套接字的接收缓冲区中。

使用原始套接字接收数据包

在使用原始套接字发送数据是以五连接的方式完成的,创建好原始套接字后可以直接将构造好的数据发送发送出去,但是由于原始套接字工作的层次比数据报套接字更低,在发送内容上有一定的区别。
发送数据的目标
从发送数据的目标来看,原始套接字不存在端口号的概念,对目的地址描述时,端口号是被忽略的,但是任然可以再连接模式和非连接模式两种方式下为套接字管理远端地址。

  • (1)非连接模式
    在非连接模式下,应用程序在每次数据发送前指定目的IP,然后调用sendto()函数或WSASendTo()函数将数据发送出去,并在数据接收时,调用recvfrom()函数或WSARecvFrom()函数,从函数返回参数中读取接收的数据包的来源地址。这种模式也同样适用于广播地址或多广播地址的放松,此时需要通过setsockopt()函数设置选项SO_BROADCAST以允许广播数据的发送。
  • (2)连接模式
    在连接模式下,应用程序首先调用connect()函数WSACnnect()函数指定远端地址,即确定唯一的通信对方地址没在数据发送和接受过程中,不用每次重复指明远程地址就可以发送和接收报文,此时,send()函数或WSASned()函数和sendto()函数就可以通用,recv()函数/WSARecv()函数和recvfrom()函数/WASRecvFrom()函数也可以通用。

2.发送数据的内容
从发送数据的内容来看,原始套接字发送内容涉及多种协议首部的构造,对于IPv4或IPv6数据的发送,IP首部控制选项为协议首部填充了两个层次的选择:如果是IPV4,选项为IP_HDRINCL,选项级别为IPPRPOTO_IP;如果是IPv6,选项为IPv6_HDRINCL,选项级别为IPPROTO_IPv6。

  • (1)IP首部控制选项未开启
    在原始套接字创建后默认发送的数据是IP数据部分,那么不需要设置IP首部控制选项,此山是程序设计人员负责构造IP协议承载的首部和协议数据,IP协议首部是由协议栈负责填充的。
  • (2)IP首部控制选项开启
    如果希望对IP首部进行个性填充,则需要设置IP首部控制选项,此时包括IP首部在内的整个数据包都由用户来完成构造,而协议栈则极少参与数据包的形成过程。

源代码如下

#include "winsock2.h"
#include "mstcpip.h"
#include "iostream"
#include<stdlib.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")#define DEFAULT_BUFLEN 65535
#define DEFAULT_NAMELEN 512int main()
{WSADATA wsaData;SOCKET SnifferSocket = INVALID_SOCKET;char recvbuf[DEFAULT_BUFLEN];int iResult;int recvbuflen = DEFAULT_BUFLEN;HOSTENT* local;char HostName[DEFAULT_NAMELEN];IN_ADDR addr;SOCKADDR_IN LocalAddr, RemoteAddr;int addrlen = sizeof(SOCKADDR_IN);int in = 0, i = 0;DWORD dwBufferLen[10];DWORD Optval = 1;DWORD dwBytesReturned = 0;//初始化套接字iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0){cout << "初始化失败:" << iResult << endl;return 1;}//创建套接字SnifferSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);if (INVALID_SOCKET == SnifferSocket){cout << "创建套接字失败:" << WSAGetLastError() << endl;WSACleanup();return 1;}//获取本机名称memset(HostName, 0, DEFAULT_NAMELEN);iResult = gethostname(HostName, sizeof(HostName));if (SOCKET_ERROR == iResult){cout << "获取本机名称失败:" << WSAGetLastError() << endl;WSACleanup();return 1;}//获取本机IPlocal = gethostbyname(HostName);cout << "本机可用的IP地址有:" << endl;if (NULL == local){cout << "获取IP失败:" << WSAGetLastError() << endl;WSACleanup();return 1;}while (local->h_addr_list[i] != 0){addr.s_addr = *(u_long*)local->h_addr_list[i++];cout << "\t" << i << ":\t" << inet_ntoa(addr) << endl;}cout << "请选择捕获数据包待使用的接口号:";cin >> in;memset(&LocalAddr, 0, sizeof(LocalAddr));memcpy(&LocalAddr.sin_addr.S_un.S_addr, local->h_addr_list[in - 1], sizeof(LocalAddr.sin_addr.S_un.S_addr));LocalAddr.sin_family = AF_INET;LocalAddr.sin_port = 0;//绑定iResult = bind(SnifferSocket, (SOCKADDR*)&LocalAddr, sizeof(LocalAddr));if (SOCKET_ERROR == iResult){cout << "绑定失败:" << WSAGetLastError() << endl;closesocket(SnifferSocket);WSACleanup();return 1;}cout << "成功绑定套接字和" << in << "号借口地址";//设置套接字接受命令iResult = WSAIoctl(SnifferSocket, SIO_RCVALL, &Optval, sizeof(Optval), &dwBufferLen, sizeof(dwBufferLen), &dwBytesReturned, NULL, NULL);if (SOCKET_ERROR == iResult){cout << "套接字设置失败:" << WSAGetLastError() << endl;closesocket(SnifferSocket);WSACleanup();return 1;}//开始接受数据cout << "开始接受数据" << endl;do{//接受数据iResult = recvfrom(SnifferSocket, recvbuf, DEFAULT_BUFLEN, 0, (SOCKADDR*)&RemoteAddr, &addrlen);if (iResult > 0){cout << "接受来自" << inet_ntoa(RemoteAddr.sin_addr) << "的数据包," << "长度为" << iResult << endl;}elsecout << "接受失败:" << WSAGetLastError() << endl;} while (iResult > 0);{}system("pause");return 0;
}

8.运行截图如下:

【网络编程】---C++实现原始套接字捕获数据包相关推荐

  1. 《UNIX网络编程 卷1:套接字联网API(第3版)》——第2章 传输层:TCP、UDP和SCTP 2.1概述...

    本节书摘来自异步社区<UNIX网络编程 卷1:套接字联网API(第3版)>一书中的第2章,第2.1节,作者:[美]W. Richard Stevens , Bill Fenner , An ...

  2. 网络编程之端口与套接字

    网络编程之端口与套接字 写给将来的自己看,对于其他人不敢保证可读性. 端口 概念与用途 端口是TCP/IP协议簇中,应用层进程与传输层协议实体间的通信接口. 应用层进程通过系统调用与某个端口进行绑定, ...

  3. 《UNIX网络编程 卷1:套接字联网API》学习笔记——基本TCP套接字编程

    UNIX网络编程--基本TCP套接字编程 socket 函数 connect 函数 bind 函数 listen 函数 accept 函数 fork 和 exec 函数 并发服务器 close 函数 ...

  4. 图灵社区 : 图书 : UNIX网络编程 卷1:套接字联网API(英文版•第3版)

    图灵社区 : 图书 : UNIX网络编程 卷1:套接字联网API(英文版•第3版) 图灵社区 : 图书 : UNIX网络编程 卷1:套接字联网API(英文版•第3版) UNIX网络编程 卷1:套接字联 ...

  5. JavaEE 网络编程示例1 UDP套接字数据报编程 == 一发一收

    鲁吼呀,胶己人! 文章目录 JavaEE & 网络编程示例1 & UDP套接字数据报编程 ==> 一发一收 1. 协议分层(回顾) 2. 套接字 Socket 2.1 UDP与T ...

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

    利用原始套接字的抓包原理: 抓包层 发送接收ip数据包 [接收除了以太网帧头部后面的ip层数据] socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|I ...

  7. 《Unix网络编程卷1:套接字联网API》读书笔记

    第一部分:简介和TCP/IP 第1章:简介 第2章:传输层:TCP.UDP和SCTP TCP:传输控制协议,复杂.可靠.面向连接协议 UDP:用户数据报协议,简单.不可靠.无连接协议 SCTP:流控制 ...

  8. 2021版!万字UNIX网络编程学习笔记(套接字篇)

    目录 1.一个简单的时间获取服务器的程序 2.套接字篇 2.1 套接字简介 2.2 套接字中常用的函数 2.3 基本TCP套接字编程 2.3.1.socket函数 2.3.2 connect函数 2. ...

  9. 《网络编程》基本 TCP 套接字编程

    在进行套接字编程之前必须熟悉其地址结构,有关套接字的地址结构可参考文章<套接字编程简介>.基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述 ...

最新文章

  1. 3行代码,Python数据预处理提速6倍!(附链接)
  2. 苹果CEO 斯蒂夫.乔布斯:我如何工作
  3. java听课笔记(十五)之 网络编程
  4. MTV: Django眼中的MVC
  5. intellij idea 中去除 @Autowired 注入对象带来的红色下划线报错提示
  6. python绘制汉字_OpenCV Python 绘制中文字
  7. 微服务实战——Spring Cloud 第四篇 将服务注册到Eureka Server上
  8. 栈的pop和peek_从堆栈中移除项目,而不使用pop、peek、push
  9. Python出租车GPS数据处理(TransBigData)
  10. 众达说两化融合A044B-记录文件之机房巡检UPS充放电记录
  11. 102个快递编码的对照表
  12. VS2015大括号高亮显示的设置方法
  13. Shiro记住我无效,被拦截;
  14. 单点登录SSO:可一键运行的完整代码
  15. (SSM,JQUERY-EASYUI,MYSQL)快递物流系统
  16. 好用的parallel命令
  17. 《算法导论》:跳跃表(Skip List)
  18. 跑马灯实现的三种方式
  19. 完全二叉树的顺序存储与非递归算法前序遍历
  20. 解决阿里云OSS使用URL无法访问图片

热门文章

  1. Angular Hello World,Angular 简单DEMO
  2. 2016年毕业设计指导与总结
  3. 阿里讽腾讯整条命是小学生给的;百度公布起诉前高管“实锤”;微信大规模封号丨价值早报
  4. java画雪花_java版雪花生成
  5. 苹果13适合什么充电宝?苹果手机可以用的充电宝推荐
  6. android长虹面试题,长虹电子面试笔试题目
  7. unicloud使用云开发每天定时向女朋友发送短信(api获取/数据库固定+情话用完短信警告/自定义情话/晚安)
  8. 极速办公(word)如何插入横线
  9. Java高级工程师必看系列,面试真题解析
  10. 如何自己计算机硬盘故障,怎样检测硬盘有没有坏 电脑硬盘故障检测软件