【计网 从头自己构建协议】一、libpcap 介绍 手撕以太网帧

news2025/1/10 23:25:55

上一篇:IndexError: list index out of range
下一篇:[【计网 从头自己构建协议】二、收发 ARP 请求帧与响应帧]

介绍

理论的学习总是枯燥的,想要加深对理论的理解,最好的方法就是自己实践一遍。

想要亲手实现各种协议,就必须能够接触底层 API。可惜的是,底层的 API 要么是在驱动里,要么是在系统里,都不对外开放,一般只能接触到运输层的 TCP/UDP。我们必须借助第三方库才能实现对底层操控。

libpcap 就是这样一个库,它帮我们实现了底层驱动,并将控制权向上开放,提供了发送和监听数据包的功能。著名的网络分析工具 Wireshark 就是基于这个库实现的。

libpcap 是 Linux 上的库,对应的 Windows 版本叫做 winpcap,不过这个库已经停止开发了,只支持到 Windows 8,没法运行在 Windows 10 上。npcap 在 winpcap 的基础上继续开发,添加了对新版 Windows 的支持和其他的功能。

我们下面就使用 npcap 这个库。

安装

推荐你直接安装 Wireshark,在安装 Wireshark 的同时会一并安装 npcap。所以如果你已经装了 Wireshark ,就不需要再装 npcap 本体了。

除了本体之外,我们还需要开发用的头文件和库文件
去 npcap 官网下载页面,找到“Downloading and Installing Npcap Free Edition”,把 installer(如果你没装 Wireshark) 和 SDK 都下载下来。

installer 是 npcap 本体,直接双击,一路 next 即可。
SDK 是个压缩包,找个地方解压即可。路径最好要短,不要有中文。推荐专门新建一个文件夹“SDK”来放开发包。
例如“D:\SDK\npcap-sdk-1.13”

配置

Visual Studio

先确保项目配置是 x64 的,如果不是,先切换到 x64 再继续下面的步骤。
工具栏

打开菜单栏 项目 -> XXX 属性,后面的设置都在这里面进行。
C/C++ -> 常规,右边的附加包含目录,添加一条记录 npcap-sdk-1.13\Include。(自行把路径补充完整)
连接器 -> 常规,右边的附加库目录,添加一条记录 npcap-sdk-1.13\Lib\x64。(自行把路径补充完整)
链接器 -> 输入,右边的附加依赖项,在最前面加上 Ws2_32.lib;wpcap.lib;
链接器 -> 输入,右边的延迟加载的 DLL,填入 wpcap.dll

一些前置知识

由于之后我们要经常要在比特和字节之间转换,为了方便,后面不用 charintlong,而用 int8_tint16_tint32_t。其中 int 后面的数字表示比特长度。比如 int8_t 长 8 比特,也就是 1 字节。
需要先 #include <inttypes.h> 才能使用。

此外,数据在内容中的储存分大小端
例如 0x0800 这个数字,长 2 字节,以 1 字节为单位分割,结果是 0x08 0x00。
但是计算机内存中不一定是这样储存的,可能是 0x08 0x00,也可能是 0x00 0x08。就好像以前中文的书写顺序是从右往左,而现在是从左往右,如果看的顺序不对,什么都读不出来。

解决方法是统一网络使用的字节顺序,并额外再写几个函数在转换本地机器字节顺序和网络字节顺序之间转换。

htonl() //"Host to Network Long"
ntohl() //"Network to Host Long"
htons() //"Host to Network Short"
ntohs() //"Network to Host Short"   

h 代表 “host 主机”,n 代表 “network 网络”,to 代表“到”,l 代表“long”类型,s代表“short”类型。
例如 htons() 就是把主机字节序转换为网络字节序,传入的参数是 short 类型。

发送数据

发送数据包用的是 int pcap_sendpacket(pcap_t *p, const u_char *buf, int size); 这个函数。
数据包将会直接原封不动地发送到数据链路层上去,也就是说,我们需要自行构造各种协议的头

构造以太网帧

先从以太网开始。

在发送数据之前,我们得自己起一个函数来帮助我们构造以太网头。
至于以太网帧的格式是什么,自己去翻书
可以参考 Wireshark 的 Wiki 页面 或者 维基百科,直接看 packet format 部分即可。

适配器已经帮我们搞好了帧开始之前 010101… 时钟同步信号、帧开始处的定界符、帧结束处的 FCS 帧检验序列,我们只需要搞定核心部分即可。

int make_ethernet_packet(
	uint8_t target_address[6], // 目的 MAC 地址
	uint8_t source_address[6], // 源 MAC 地址
	int16_t ethertype, // 以太网类型(DIX Ethernet II)/帧长(IEEE 802.3)
	uint8_t *payload, // 数据部分
	size_t payload_length, // 数据长度(比特数)
	uint8_t **data, // 返回值:以太网帧。由调用者释放内存。
	size_t *data_length // 返回值:以太网帧长度(字节数)
)
{
	// 计算总长度并分配内存
	*data_length = 6 + 6 + 2 + payload_length;

	// 最大帧长 1500-4 字节
	if (*data_length > 1496)
		return -2;

	// 最小帧长 64-4(FCS长度)-6-6-2(头长度)=46 字节
	// 不够要填充额外数据
	if (*data_length < 46)
	{
		int8_t *new_payload = calloc(60, sizeof(int8_t));
		memcpy(new_payload, payload, payload_length);
		// 覆盖掉原始 payload
		payload = new_payload;
		payload_length = 46;
		// 重新计算 data_length
		*data_length = 6 + 6 + 2 + payload_length;
	}

	*data = calloc(*data_length, sizeof(int8_t));
	if (data == NULL)
		return -1;

	// 处理大小端问题
	ethertype = htons(ethertype);

	// 填充头部
	memcpy(*data, target_address, 6);
	memcpy(*data + 6, source_address, 6);
	memcpy(*data + 12, &ethertype, sizeof(ethertype));

	// 填充数据
	memcpy(*data + 14, payload, payload_length);

	return 0;
}

有几点注意的事项:

  1. 以太网对帧长有要求。最小帧长是 64 字节,最大帧长是 1500 字节,两个都包括 FCS 在内。
    由于我们不需考虑 FCS,所以最小帧长是 60 字节,最大帧长是 1496 字节。
    长度不够的时候要往里面填 0,直到长度够了为止。
  2. ethertype 这个字段在 DIX Ethernet II 和 IEEE 802.3 两个标准里的含义不同。这里我们采用第一个标准。

再定义几段宏,方便使用。

// 合成 MAC 地址(只能是常量)
#define MAC(aa, bb, cc, dd, ee, ff) (int8_t[6]) { 0x##aa, 0x##bb, 0x##cc, 0x##dd, 0x##ee, 0x##ff }

#define ETHERNET_TYPE_IPV4 0x0800 // 以太网类型 IPv4
#define ETHERNET_TYPE_ARP 0x0806 // 以太网类型 ARP

发送数据

在调用 pcap_sendpacket 发送数据之前,我们还需要一些准备工作。
见下面的代码。

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <pcap.h>

// 辅助加载 DLL 的函数
#ifdef _WIN32
#include <tchar.h>
BOOL LoadNpcapDlls()
{
	_TCHAR npcap_dir[512];
	UINT len;
	len = GetSystemDirectory(npcap_dir, 480);
	if (!len) {
		fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
		return FALSE;
	}
	_tcscat_s(npcap_dir, 512, _T("\\Npcap"));
	if (SetDllDirectory(npcap_dir) == 0) {
		fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
		return FALSE;
	}
	return TRUE;
}
#endif

int main()
{
	char error_buffer[PCAP_ERRBUF_SIZE]; // 用于储存错误信息

	// 载入 DLL
#ifdef _WIN32
	if (!LoadNpcapDlls())
	{
		fprintf(stderr, "无法加载 Npcap。\n");
		return -1;
	}
#endif

	// 初始化
	if (pcap_init(PCAP_CHAR_ENC_LOCAL, error_buffer) != 0) {
		printf("初始化 pcap 库失败: %s\n", error_buffer);
		return -2;
	}

	// 获取所有适配器并让用户选择
	pcap_if_t *devices;
	char err_buffer[1024]; // 用来储存错误信息
	if (pcap_findalldevs(&devices, err_buffer) == -1)
	{
		printf("pcap_findalldevs 时出错:%s\n", err_buffer);
		exit(1);
	}

	// 打印所有适配器名称(devices 是个链表)
	int i = 0;
	for (pcap_if_t* device = devices; device; device = device->next)
	{
		i++;
		printf("[%d] %s | 名称=%s\n", i, device->description, device->name);
	}

	int choice = 0;
	printf("请选择一个适配器:");
	scanf("%d", &choice);

	// 找到选中的适配器
	pcap_if_t* device = devices;
	for (int i = 0; i != choice - 1; i++)
	{
		device = device->next;
	}
	// 打开它
	pcap_t *pcap = pcap_open_live(
		device->name, // 适配器名称
		0, // 
		0,
		1000, // 超时时间,毫秒
		&error_buffer
	);
	if (pcap == NULL)
	{
		printf("打开适配器 %s 失败。\n", device->description);
		return -3;
	}

	// 打开之后要释放之前的适配器列表
	pcap_freealldevs(devices);

	// 检查数据链路层协议
	int datalink_type = pcap_datalink(pcap);
	if (datalink_type != DLT_EN10MB) // 10Mb 以上的以太网
	{
		printf("不支持的数据链路层协议。");
		return -4;
	}


	// --------> 在这里开始构造数据帧+发送数据  <---------------



	pcap_close(pcap);
	return 0;
}

具体每一段的含义在注释里已经写的比较清晰了。
上面这段代码可以当成是模板,后面我们还会多次用到。

下面利用刚刚写的函数来构造数据帧并发送。

	// 构造以太网帧
	uint8_t payload[] = "Hello world from MyEthernet!"; // 要发送的数据
	uint8_t *data = NULL;
	size_t data_length = 0;

	int ret = make_ethernet_packet(
		MAC(ff, ff, ff, ff, ff, ff), // 目的地址,广播帧
		MAC(6, 6, 6, 6, 6, 6), // 源地址
		ETHERNET_TYPE_IPV4, // 以太网类型为 IPv4
		&payload,
		sizeof(payload) / sizeof(int8_t),
		&data,
		&data_length
	);

	if (ret != 0)
	{
		printf("创建以太网帧失败");
		return -5;
	}
	
	if (pcap_sendpacket(pcap, data, (int)data_length) != 0) // 返回值不为 0 表示出错
		printf("发送数据包时出错:%s\n", pcap_geterr(pcap));

最后发完了记得 free 掉

free(data);

测试

打开 Wireshark,开始抓包。
运行程序,选择合适的适配器,然后回来 Wireshark,停止抓包。
在最上面的过滤栏里填上 eth.dst==ff:ff:ff:ff:ff:ff
过滤
然后就能看到我们刚刚发的以太网帧了。
抓包结果
源地址全是 06,目标地址是 FF 广播地址,内容也是我们刚刚写的 hello world。
左边有一条是红色的,意思是 IP 数据报部分受损。我们刚刚类型里填的是 IPv4,所以这里显示的是 IPv4,但是数据部分是乱写的,显示受损也不意外,不用在意。

另外,如果收到了到错误信息 “连到系统上的设备没有发挥作用(-31)” 也不用慌张,只要在 Wireshark 里能看到我们发的数据就没问题,这是库本身的问题,不是我们的问题。

继续测试

我们刚刚目的地址写的是广播帧,理论上其他设备也能收到消息。
接下来我们在同局域网的其他设备上也装上 Wireshark,看看能否收到我们发的帧。

如果没有其他机器,可以考虑用虚拟机,并且把网络改成“桥接模式”。

下面是测试结果,第一个就是我们发的包。
虚拟机演示
光有以太网帧什么也干不了,下一篇文章我们来实现相对比较简单的 ARP 协议。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/456630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通讯方式连接成功,其他原因导致的连接失败解决方案

一、电脑中有其他品牌visa导致的冲突&#xff08;以tekvisa为例&#xff09; 1、删除tekvisa 2、下载一个NI Package Manager&#xff0c;卸载里面所有NI的东西 &#xff08;https://www.ni.com/zh-cn/support/downloads/software-products/download.package-manager.html#32…

❀五一劳动节来啦❀

今年“五一”&#xff0c;4月29日至5月3日放假调休&#xff0c;共5天。 如果你在5月4日到5月6日请假3天&#xff0c;加上5月7日周日&#xff0c;就可以形成9天的假期。 一&#xff0c;五一劳动节的由来⭐ 国际劳动节又称“五一国际劳动节”“国际示威游行日”&#xff08;英语…

人生中最好的等待叫做来日可期,社科院与杜兰大学金融管理硕士等你惊艳岁月

有句话说&#xff1a;“去日不可追&#xff0c;来日犹可期”。过去的已经过去&#xff0c;不管好的、坏的都已成为我们的回忆。人生中最好的等待就是未来可期。别辜负现在的好时光&#xff0c;努力做想做的事。社科院与杜兰大学金融管理硕士项目等你惊艳时光。 所有出众者的背…

【计算机网络:自顶向下方法】(三) 运输层 (TCP | UDP | 复用 | 传输原理rdt)

【计算机网络&#xff1a;自顶向下方法】 3.1 概述 传输层协议是在端系统中实现的传输层将发送的应用程序进程接受到的报文转换成传输层分组 (运输层报文段)实现的方法/过程 &#xff1a; 将应用报文划分为较小的块&#xff0c;并为每块加上传输层首部以生成传输层报文段ff。I…

【C++】类和对象(中篇)—— 默认成员函数,const成员函数,运算符重载

前言 类和对象没有技巧&#xff0c;只有多加练习&#xff0c;多多尝试自己完成代码&#xff0c;例如各种运算符的重载&#xff0c;或是实现一个自己的日期类 目录 一、类的六个默认成员函数 二、构造函数 2.1 概念 2.2 特点 2.3 默认无参的构造函数 三、析构函数 3.1 概…

ORB305与CISCO路由器构建L2TP over IPSec VPN操作手册

1、网络拓扑在思科路由器与ORB305之间建立一个安全隧道&#xff0c;对客户路由器端设备子网&#xff0c;与思科路由器端服务器子网之间的数据流进行安全保护&#xff0c;组网拓扑图如图所示。 2、思科路由器端配置指导(此处以多数客户使用专线上网形式为例)Cisco&#xff08;AR…

90年三本程序员,8年5跳,年薪4万变92万……

很多时候&#xff0c;虽然跳槽可能带来降薪的结果&#xff0c;但依然有很多人认为跳槽可以涨薪。近日&#xff0c;看到一则帖子。 发帖的楼主表示&#xff0c;自己8年5跳&#xff0c;年薪4万到92万&#xff0c;现在环沪上海各一套房&#xff0c;再干5年码农&#xff0c;就可以…

2022年NOC大赛创客智慧编程赛道图形化scratch初赛题,包含答案解析

目录 一、单选题 二、多选题 三、判断题 下载打印文档做题: 一、单选题

项目干系人是什么?如何有效管理项目干系人?

项目干系人是指对项目具有利益关系或影响力的个人、团体或组织。他们可能会对项目的目标、范围、进度、成本、质量等方面产生影响&#xff0c;因此&#xff0c;有效地管理项目干系人是项目管理成功的关键之一。 一、干系人识别和分类 项目经理应该首先识别和分类所有与项目有关…

STM32模数转换器(ADC)

1.ADC的简要 我们首先说一下ADC的转换过程&#xff0c;然后说一下原理&#xff0c;当然如果嫌啰嗦可以直接跳过。 ADC是英文Analog-to-Digital Converter缩写&#xff0c;翻译过来就是模数转换器&#xff0c;是指将连续变化的模拟信号转换为离散的数字信号的器件。A/D转换的作…

DiffusionDet源码阅读(1)

本文仅仅适用于已经通读过全文的小伙伴 本文代码节选自 mmdet 中的 DiffusionDet 代码&#xff0c;目前该代码还处于 Development 阶段&#xff0c;所以我博客里写的代码和之后的稳定版本可能稍有不同&#xff0c;不过不用担心&#xff0c;我们只看最关键的部分 DDPM中扩散部…

mybatis中大数据量foreach插入效率对比

1.controller代码 RequestMapping("/testInsert")public String testInsert(Integer sum){testService.testInsert(sum);return "发送成功";}2.service代码 Overridepublic void testInsert(Integer sum) {long start System.currentTimeMillis();List<…

LightGBM面试题

1.偏差 vs 方差? 偏差是指由有所采样得到的大小为m的训练数据集&#xff0c;训练出的所有模型的输出的平均值和真实模型输出之间的偏差。 通常是由对学习算法做了错误的假设导致的描述模型输出结果的期望与样本真实结果的差距。分类器表达能力有限导致的系统性错误&#xff0c…

基于AT89C52单片机的温度检测报警设计

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87708680?spm1001.2014.3001.5503 源码获取 主要内容&#xff1a; 本系统的设计主要是了解了单片机微型计算机&#xff0c;根据现实生活的需要以及已掌握的理论知识…

Linux应用程序开发:静态库与动态库的制作及使用

目录 一、库的简介二、静态库与动态库的简介三、静态库制作与调用案例四、动态库制作与调用案例 一、库的简介 库是一种可执行的二进制文件&#xff0c;是编译好的代码。使用库可以提高开发效率。而Linux库的种类可分为动态库和静态库。 二、静态库与动态库的简介 1、静态库&a…

第十七届中国CFO大会圆满举办 用友蝉联中国CFO首选智能财务厂商!

4月21日&#xff0c;由财政部指导、《新理财》杂志社主办、用友等单位协办的「数字财务 智能引领」第十七届中国CFO大会在北京圆满举办&#xff01;业内专家、权威学者以及众多来自央国企等知名大型企业的财务领路人荟聚一堂&#xff0c;共襄中国CFO领域的顶尖盛会&#xff0c;…

数字化转型中的石头和沙子问题

作者介绍 朱金衡&#xff0c;西门子Mendix 高级技术咨询顾问及架构师&#xff0c;Mendix Certified 中级培训讲师以及TOGAF Certified 企业架构师。作为专家服务架构师提供咨询服务&#xff0c;如方案设计、开发辅导、故障排除、应用程序审查等&#xff0c;同时创造了许多专门…

【算法】从x的n次方看递归时间复杂度计算

从x的n次方看递归时间复杂度计算 1.循环 这个问题&#xff0c;最简单的办法是用循环 int pow1(int x,int n) {int result 1;for(int i0;i<n;i){result*x;}return result; }如上算法的时间复杂度为O(N)&#xff0c;但还是不够理想。这时尝试使用递归算法 2.递归1 int po…

交换机的电口和光口到底是个啥东东,做网络的这个常识得懂!

在计算机网络中&#xff0c;交换机是一个非常重要的设备&#xff0c;它可以将来自不同设备的数据包进行转发和交换。交换机通常具有多个接口&#xff0c;其中包括光口和电口。在本文中&#xff0c;我们将详细讨论交换机的光口和电口的概念以及它们的不同之处。 电口 电口是交换…

应届生的天坑,悔不该进那外包啊.....

关于计算机专业应届生毕业之后会遇到的就业问题&#xff0c;网上已经有许多的套路&#xff0c;实际上许多人在选择专业的时候并没有考虑到之后的就业方向&#xff0c;甚至于自己所学的专业面向的工作岗位都不是特别清楚。计算机专业毕业大概率是要做程序员的&#xff0c;而目前…