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

2023-11-06

引言

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

原始套接字与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 512

int 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;
	}
	//获取本机IP
	local = 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;
		}
		else
			cout << "接受失败:" << WSAGetLastError() << endl;
	} while (iResult > 0);
	{

	}
	system("pause");
	return 0;
}

8.运行截图如下:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 海量图片曝光百度新家“搜索框”大厦

    今天陪朋友到百度办事 有幸参观了百度的新办公大楼 搜索框大厦 大厦特别漂亮 内部设计特别炫 功能更是酷啊 海量图片第一时间与大家分享一下 刚到上地环岛 远远就看到气势宏伟的大厦 非常醒目 波浪形的玻璃外墙 相当气派 无论从正面 侧面还是背面
  • fetch使用

    fetch基本使用方法 1 fetch与ajax作用相同 发送请求 2 ajax是使用XMLHttpRequest对象来请求数据 因此需要先new XMLHttpRequest 然后连接发送接收 3 fetch是一个方法 fetch 地址
  • vue中点击按钮关闭当前页面踩坑记录

    vue中关闭当前页面踩坑记录 当前页面直接使用window close不行 必须是新窗口才能使用window close 所以要router跳转时打开新窗口才能关闭 直接使用 不行 window close 先使用下面跳转对应页面 let
  • windows下两种方法通过cmd进入指定目录

    方法一 通过cmd cd命令进入 相同盘符下的目录可直接使用cd 但是windows下不同于linux 不能直接跨盘符cd进入目录 例如 从C盘进入E盘下面的目录 需要两行命令 跨盘符 跨盘符目录 先后顺序都可以 先输入跨盘符目录 再输入跨
  • C语言求班级平均分案例讲解

    我们先看例题 统计3个班成绩情况 每个班有5个同学 求出所有班级的平均分以及各个班级的平均分 从键盘输入成绩 思路分析 1 我们定义一个3行5列的二维数组用来存放学生的成绩 1行表示1个班的学生成绩 总共3行 可以存放3个班的成绩 每行有5
  • 菜鸟视角的openwrt(一) 初识openwrt

    作为一只菜鸟 为了熟悉openwrt系统 看了很多前辈的文章 因为写作的角度或者说目标人群不同 侧重点也不同 学到的知识零零碎碎 等积累的知识多了 回头再来看 才发现 原来如此 原来作者已经帮我们总结好了 这篇文章对老鸟来说 可以直接忽略
  • 二分模板——数的范围

    789 数的范围 先用二分求出x的左边界 a mid gt x mid在x的右边 所以右边界变为mid 即 if a mid gt x r mid else l mid 1 根据模板得出mid mid l r gt gt 1 若得出的左边界
  • stm32+DS1302+TM1638驱动程序

    TM1638数码管显示驱动程序 参考 1 TM1638与STM32连接 1 1 硬件连接 Vcc 电源 GND 电源地 STB PA0 CLK PA1 DIO PA2 1 2 驱动程序 TM1638 c文件 Program Assignme

随机推荐

  • MySQL基础入门语法

    一 数据库基础概念 1 1 数据库定义 数据库 存储数据的软件 长期存储在计算机内 有组织的数据集合 表 数据库存储数据的基本单位 数据按照分类存储到不同的表中 能够高效的的查询其中数据 对于测试工作 如果项目页面没有实现 需要校验数据 则
  • 数据结构之常见排序算法

    文章目录 1 排序概念 2 10种排序比较 3 排序算法 3 1直接插入排序 元素越有序 越高效 3 2希尔排序序 缩小增量排序 3 3直接选择排序 3 4堆排序 3 5冒泡排序 3 6快速排序 递归实现 无序使用最好 3 6 1挖坑法 建
  • 如何使用云桌面进行开发?

    云桌面又称桌面虚拟化 云电脑 是替代传统电脑的一种新模式 采用云桌面后 用户无需再购买电脑主机 主机所包含的CPU 内存 硬盘等组件全部在后端的服务器中虚拟出来 为什么要用云桌面进行开发 采用了云桌面 大量的终端电脑都可以统一集中管理 系统
  • vs2015写的程序,在vs2019下无法运行,报错代码为-1073741701

    vs2015写的程序 在vs2019下无法运行 报错代码为 1073741701 1 现象 2 vs2019下的配置 3 VS015下配置 4 在VS015下运行结果 5 解决 vs2019能够完美的向下兼容vs2017 vs2015的项目
  • C语言函数大全-- x 开头的函数(5)

    x 开头的函数 5 1 xdrmem create 1 1 函数说明 1 2 演示示例 2 xdrmem destroy 2 1 函数说明 2 2 演示示例 3 xdrrec create 3 1 函数说明 3 2 演示示例 4 xdrre
  • flutter手势onLongPress 的默认时间

    这个长按的时间是100ms左右 import dart async import package flutter material dart import package learn flutter02 extension size fit
  • DeviceDriver(十四):多点触摸(MT协议,Input子系统)

    Input子系统框架参考 02 输入子系统 猩猩 點燈的博客 CSDN博客 电阻式多点触摸驱动参考 05 触摸屏驱动 猩猩 點燈的博客 CSDN博客 一 电容触摸屏知识点 1 电容触摸屏是I2C接口 需要触摸IC 因此框架为I2C设备驱动框
  • 文件上传upload-labs第十一至十九关

    第十一关 白名单 get类型 00截断 需要两个条件 php版本小于5 3 4 php的magic quotes gpc为OFF状态 另save path等于下面的值 upload 4 php 00 BP修改一下抓到的包 第十二关 POST
  • 【MySQL存储过程】存储过程的查看与删除

    目录 一 查看存储过程 1 SHOW STATUS语句查看存储过程 2 使用SHOW CREATE语句查看存储过程的定义 3 从information schema Routine表中查看存储过程的信息 二 存储过程的删除 一 查看存储过程
  • QT中HASH函数方法

    包含的头文件 include QCryptographicHash 具体代码实现 通过hash中的sha1加密方式加密 QCryptographicHash Hash QCryptographicHash Sha1 QString word
  • pdf文件太大如何处理?教你pdf压缩简单方法

    PDF文件过大 是很多人在使用PDF文件时都遇到过的一个常见问题 过大的PDF文件不仅会占用大量的存储空间 还会影响文件传输和处理效率 下面给大家总结了几个方法 帮助大家解决PDF文件过大的问题 方法一 嗨格式压缩大师 这是一款专业的文件压
  • PSM:协议状态机(Protocol State Machine),一款用于流式传输的数据协议解析组件

    PSM 协议状态机 Protocol State Machine 一款用于流式传输的数据协议解析组件 介绍 PSM Protocol State Machine 协议状态机 一款用于流式传输的数据协议解析组件 可有效解决沾包 断帧问题 PS
  • 文字识别:Tesseract OCR

    一 安装并配置Tesseract 1 下载Tesseract OCR 网上直接下载即可 2 双击安装 选择所有人均可使用 避免权限问题 勾选最后一项添加语言包 但是全部勾选需要1 3G 可以点开加号 选择自己所需的语言包即可 注意 这里最好
  • 面试经典(4)--链表逆序

    题目 输入单链表的头结点 反转链表并输出反转之后的头结点 pNode代表当前节点 修改pNode的时候 要知道他的前面节点 并保存后面节点 因为一旦pNode gt next被修改 就和后边断开 ListNode reverse ListN
  • MySQL报错:this is incompatible with sql_mode=only_full_group_by

    错误场景 今天在自己电脑运行一个刚clone下来的项目 在登录完成进入主页的时候 报了一串this is incompatible with sql mode only full group by 错误 我很确定之前在公司电脑上是不会有这个
  • 如何命令杀死yarn的作业

    yarn application kill 命令即可kill掉
  • php安装swoole扩展(linux)

    在Linux中php安装swoole扩展 相关环境 LAMP或LNMP开发环境 L 指linux A 指Apache M 指MySQL P 指PHP N 指Nginx 下载swoole wget c https github com swo
  • 33 KVM管理设备-配置虚拟机PCIe控制器

    文章目录 33 KVM管理设备 配置虚拟机PCIe控制器 33 1 概述 33 2 配置PCIe Root PCIe Root Port和PCIe PCI Bridge 33 2 1 简化配置方法 33 2 1完整配制方法 33 KVM管理
  • 提供许可证到期新通知!标签管理软件BarTender v2019 R5上线!

    BarTender在150多个国家 地区拥有成千上百的用户 在标签 条形码 证卡和 RFID 标记的设计和打印领域是全球首屈一指的软件 BarTender既可以单独运行 也可以与任何其他程序集成 几乎是所有按需打印或打标应用的完美解决方案
  • 【网络编程】---C++实现原始套接字捕获数据包

    C 实现原始套接字捕获数据包 引言 原始套接字与TCP套接字和UDP套接字的区别 原始套接字编程使用的场合 原始套接字的通信过程 1 基于原始套接字的数据发送过程 2 基于原始套接字的数据接收过程 创建原始套接字 常用协议定义列表 使用原始