计网实验二

Uncategorized
15k words

<仍在编辑中>

前言

C++实现就自己汇报用了,python实现简洁明了,用来分享讲解。
代码分享项目:https://github.com/buyun14/PoCN
注意:实例中代码为初版,存在大文件写入更不上传输等等诸多小问题,请参考评论区自行修改


最最重要的前言!!!!!

此处为作者原话

  1. 我们那个实验报告上给出实现的是个啥?他是个以太网帧,但它是基于有线连接的802.3的帧格式
  2. 我这个代码也一样, ether是scapy的库已经内置好的类。用来表示以太网帧(802.3)
  3. 这是什么意思呢?意思是你要用这个帧实现通信,你必须是用的有线以太网连接。
  4. 你要是一个人检查一个主机上实现那没关系,你一个主机上所有的程序都是物理层上互相连接的(他们互相能确定自己的位置)。
  5. 但有些老师他要求两个人合作,两个人机子上互相通信
  6. 我也说白了,用他实验报告给的这个帧就tm不可能实现。除非你带条网线,把你和你队友的机子连起来。(我虽然准备是这样干的)
  7. 为什么我会这样说?因为实验报告这个实验他要求的讲解的构造的是一个有线以太网连接的基于IEEE 802.3的帧 ,但我们实验室难不成带着网线去?我们顶多是拿手机热点创建一个局域网。问题来了,无线局域网通信他用的以太网帧是(IEEE802.11标准的)WIFI帧,这种结构是和我们实验报告要求实现的讲解的不是一个东西

你要是和我一样爱折腾,可以去看一下这个帧的结构,自己再构造一下,用scapy的Dot11函数(802.11)。
这样实现的理论上就可以在不带网线的情况下,让你和你的朋友在同一个局域网下(用手机热点最好,校园网防火墙网络环境复杂)实现通信。

但问题你老师要是要求非得跨主机实现,老师,你为什么要在实验报告中讲有线网(802.3)的协议和帧结构呢?而不是无线局域网(802.11)的呢? 难不成你指望学生全都带根网线去上课?
还是说老师我觉得我讲个有线网的就够了,我觉得你们学生很聪明,你们自己去找无线局域网的自己实现?

好了,抱怨完了,这篇文章剩下的东西实际上很有限,嗯,最好自己搜一搜了解一下方面的知识。
只想要检查过实验很简单,李老师比较宽容,你直接过去自己主机上给助教演示一下文件发送就行(解释一下函数实现和这个帧结构毕竟这次实验的目的就是这个)

再提一嘴

这个库函数包括他给的wincap npcap里实际上我们实现的只有(目标地址 原地址 类型 数据部分),通过这些接口,我们提取不到最后那4个字节的FCS字段(CRC校验)!
为什么?因为FCS字段是发送给你的网卡由硬件实现的!你可以在系统网卡设置里更多属性那看到以太网帧,默认最大是1514,6+6+2+1500,没有给最后FCS留的位置!!
硬件他实现了,但是我们调不出来。
但你知道这个CRC校验原理并向助教描述就可以,或者!!在1500个数据字段中,你留下4个字节,1496用来传数据,然后这4个字节拿来存计算的crc校验码,附加到数据字段上发过去。然后接收方把钱1496再计算一遍,和你发过来的这4个对比一下。(程序写的没问题肯定一样的)(联系上文,校验早在硬件网卡那就实现了,你只不过是校验里面套校验展示一下他是怎么实现的原理罢了了)OK,没了。后面的东西虽然多但实际上精华是我这些前言,我只提出思路,不提供实现。想进一步做的,可以自己问问ai搜搜资料。


802.3(以太网)帧结构

字段名称 长度(字节) 备注
前导码 8 用于接收同步信号
目的MAC地址 6 表示帧的目的地址
源MAC地址 6 表示帧的源地址
类型/长度 2 标识上层协议或帧的有效载荷长度
数据 [46,1500] 包含上层协议的数据
帧校验序列(FCS) 4 用于检测帧在传输过程中的错误

802.11(Wi-Fi)帧结构

字段名称 长度(字节) 备注
帧控制 2 包含帧类型、子类型、标志位等信息
持续时间/ID 2 用于管理传输时间
地址1 6 通常是目标地址
地址2 6 通常是源地址
地址3 6 通常是BSSID(基本服务集标识符)
序列控制 2 用于管理帧的顺序
地址4(可选) 6 可选,用于扩展用途
帧体 [0,2312] 包含上层协议的数据,最大长度受物理层限制
帧校验序列(FCS) 4 用于检测帧在传输过程中的错误

scapy
Scapy是一个 Python程序,它允许用户发送、嗅探、分析和伪造网络包。这种能力允许构建能够探测、扫描或攻击网络的工具。scapy还可以很好地执行许多其他工具无法处理的其他特定任务,例如发送无效帧、注入自己的802.11帧、结合技术(VLAN跳跃+ARP缓存中毒、WEP加密通道上的VoIP解码等)。

更详细的介绍可以去读文档:scapy传送门

原理

回忆一下以太网数据帧的组成

目的MAC地址 源MAC地址 类型 数据 FCS
6字节 6字节 2字节 [46,1500] 4字节

现在解释一下实验指导书提到的以太网帧的填充字段。以太网帧中的PAD(填充)字段。以太网帧的结构主要包括以下几个部分:

  • 前导码(Preamble):56位(7字节),用于接收同步阶段。
  • 目的地址(Destination Address):48位(6字节)。
  • 源地址(Source Address):48位(6字节)。
  • 类型/长度(Type/Length):16位(2字节),当值小于或等于1500时,表示长度字段;大于1536时,表示类型字段。
  • 数据(Data):46到1500字节。这是PDU(协议数据单元)所在的位置。
  • 填充(Padding):0到46字节,确保整个帧的最小长度为64字节。如果数据部分小于46字节,则使用PAD来填充。
  • 帧校验序列(Frame Check Sequence, FCS):32位(4字节),用于错误检测。
  1. 根据描述,以太网帧的最小长度是64字节,这包括了18字节的固定开销(目的地址6字节 + 源地址6字节 + 类型/长度2字节 + FCS 4字节)加上至少46字节的数据或数据加填充。
  2. 如果实际的数据部分(PDU)不足46字节,那么就需要通过PAD字段来补充,以保证整个帧的长度不低于64字节。这是因为以太网物理层设计中存在一个最小帧时间的要求,低于这个时间的帧可能会被视为无效或者噪声引起的错误帧,即所谓的“帧碎片”,通常会被网络设备直接丢弃而不进行进一步处理。因此,正确的填充是保证以太网通信可靠性的必要措施之一。

过程

发送方
(1)定义数据帧数据结构;
(2)从文件中读取数据(46-1500字节);
(3)计算CRC校验码;
(4)封装以太网数据帧;
(5)读取本地网卡列表;
(7)选择本次通信网卡序号(0,1,2?);
(8)初始化本次通信网卡;
(9)发送数据帧,返回(2),直到文件数据发送完。

接收方
(1)读取本地网卡列表;
(2)选择本次通信网卡序号(0,1,2?);
(3)初始化本次通信网卡;
(4)接收数据帧;
(5)数据帧正确性检查:
1)目的地址匹配或者二层广播地址;2)是否碎片帧(小于64B);3)CRC校
验码验证;
(6)将帧首部及尾部各个字段解析并利用十六进制屏幕打印;
(7)依据协议类型(0x0800),将帧数据部分写入文件,返回(4),直到文件接
收完成。

功能点与代码对应关系

  1. 定义数据帧数据结构

    • Scapy 库已经定义了以太网帧的数据结构。在脚本中,我们使用 Ether() 来创建一个以太网帧对象。
  2. 从文件中读取数据(46-1500字节)

    • with open(file_path, 'rb') as file: data = file.read()
      这行代码从指定的文件路径读取文件内容,并将其存储在变量 data 中。
    • frames = [data[i:i + max_frame_size] for i in range(0, len(data), max_frame_size)]
      这行代码将文件数据分割成多个块,每块大小不超过 1500 字节,以便于创建多个以太网帧。
  3. 计算CRC校验码

    • Scapy 库在发送以太网帧时会自动计算并添加 CRC 校验码(FCS),无需手动计算。
  4. 封装以太网数据帧

    • ether = Ether()
      创建一个以太网帧对象。
    • ether.dst = "00:11:22:33:44:55"
      设置目的 MAC 地址。
    • ether.src = "00:AA:BB:CC:DD:EE"
      设置源 MAC 地址。
    • ether.type = 0x0800
      设置以太网帧的类型字段,这里设置为 IP 协议。
    • ether /= Raw(load=frame_data)
      将数据部分附加到以太网帧中。
  5. 读取本地网卡列表

    • Scapy 库没有直接提供读取本地网卡列表的函数,但可以通过 sendp 函数的 iface 参数指定要使用的网络接口。
    • 我们直接在CMD命令行中输入ipconfig命令,查看本地网卡列表。无线选WLAN,有线选以太网/宽带拨号即可。

ipconfig

  1. 选择本次通信网卡序号(0,1,2?)

    • 在调用 sendp 函数时,通过 iface 参数指定要使用的网络接口。例如,interface = "WLAN" 表示使用 WLAN 接口。
      1
      interface = "WLAN"  # 替换为你的网络接口名称
  2. 初始化本次通信网卡

    • Scapy 库在发送数据时会自动初始化指定的网络接口,无需额外的初始化步骤。
  3. 发送数据帧,返回(2),直到文件数据发送完

    • for frame_data in frames:
      遍历分割后的数据块。
    • sendp(ether, iface=interface)
      发送每个以太网帧。
    • print(f"发送数据帧: {len(frame_data)} 字节")
      打印已发送的数据帧大小。

发送方解释完了,接收方就不解释了,大差不差

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# sender.py
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.sendrecv import sendp


def send_ethernet_frame(file_path, interface):
# 读取文件内容
with open(file_path, 'rb') as file:
data = file.read()

# 分割数据,每个数据帧不超过1500字节
max_frame_size = 1500
frames = [data[i:i + max_frame_size] for i in range(0, len(data), max_frame_size)]

for frame_data in frames:
# 创建以太网帧
ether = Ether()

# 设置目的和源MAC地址
ether.dst = "00:11:22:33:44:55" # 目的MAC地址
ether.src = "00:AA:BB:CC:DD:EE" # 源MAC地址

# 设置协议类型(0x0800表示IP协议)
ether.type = 0x0800

# 填充数据部分
ether /= Raw(load=frame_data)

# 发送以太网帧
sendp(ether, iface=interface)
print(f"发送数据帧: {len(frame_data)} 字节")


if __name__ == "__main__":
file_path = "C:\\Users\\21354\\Videos\\asdf.mp4" # 替换为你的视频文件路径
interface = "WLAN" # 替换为你的网络接口名称
send_ethernet_frame(file_path, interface)
print("所有数据帧发送完毕")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# receiver.py
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.sendrecv import sniff


def process_packet(packet):
if Ether in packet:
ether = packet[Ether]

# 检查目的地址是否匹配
if ether.dst == "00:11:22:33:44:55":
# 打印帧首部和校验字段值
print("帧首部:")
print(f"目的MAC地址: {ether.dst}")
print(f"源MAC地址: {ether.src}")
print(f"类型: {ether.type}")

# 打印数据部分
if Raw in packet:
print("数据部分:")
print(packet[Raw].load.hex())

# 将数据写入文件
with open("received_video_file.mp4", 'ab') as file:
file.write(packet[Raw].load)
print(f"接收数据帧: {len(packet[Raw].load)} 字节")


def receive_ethernet_frame(interface):
# 开始捕获数据包
print(f"开始捕获数据包,接口: {interface}")
sniff(iface=interface, prn=process_packet, filter="ether dst 00:11:22:33:44:55")


if __name__ == "__main__":
interface = "WLAN" # 替换为你的网络接口名称
receive_ethernet_frame(interface)
print("数据包捕获结束")

是不是发现很简洁?
因为 Scapy 库已经帮我们预处理了很多的流程。
数据帧结构的定义、CRC校验都封装到了库中,我们只需要关注数据部分的发送和接收即可。

让AI帮你分析一下:


确实,Python 和 Scapy 的组合相比 C++ 和 Npcap 的组合更加简洁和易于使用。以下是几个主要原因:

1. 高级抽象和内置功能

Scapy 是一个高级网络库,提供了丰富的内置功能和抽象,使得网络编程变得更加简单。例如,Scapy 已经定义了各种网络协议的数据结构,你不需要手动定义这些结构。

2. 自动处理细节

Scapy 自动处理了很多底层细节,比如 CRC 校验码的计算、帧的组装和拆解等。你只需要关注高层次的逻辑,比如读取文件、分割数据和发送帧。

3. 简洁的语法

Python 语言本身具有简洁明了的语法,使得编写和阅读代码变得更加容易。相比之下,C++ 代码通常更冗长,需要更多的模板和库支持。

4. 动态类型和交互性

Python 是一种动态类型语言,可以在运行时动态地创建和修改对象。这使得开发和调试更加灵活。Scapy 还提供了交互式 shell,可以方便地测试和调试网络包。

5. 丰富的社区支持和文档

Scapy 有一个活跃的社区和详细的文档,提供了大量的示例和教程。这使得学习和使用 Scapy 变得更加容易。

具体对比

读取文件

Python + Scapy:

1
2
with open(file_path, 'rb') as file:
data = file.read()

C++ + Npcap:

1
2
3
4
5
6
7
8
9
10
11
#include <fstream>
#include <vector>

std::vector<uint8_t> read_file(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
if (!file) {
throw std::runtime_error("无法打开文件");
}
std::vector<uint8_t> data(std::istreambuf_iterator<char>(file), {});
return data;
}

分割数据

Python + Scapy:

1
2
max_frame_size = 1500
frames = [data[i:i + max_frame_size] for i in range(0, len(data), max_frame_size)]

C++ + Npcap:

1
2
3
4
5
6
7
8
const size_t max_frame_size = 1500;
std::vector<std::vector<uint8_t>> split_data(const std::vector<uint8_t>& data) {
std::vector<std::vector<uint8_t>> frames;
for (size_t i = 0; i < data.size(); i += max_frame_size) {
frames.push_back(std::vector<uint8_t>(data.begin() + i, data.begin() + std::min(i + max_frame_size, data.size())));
}
return frames;
}

创建和发送以太网帧

Python + Scapy:

1
2
3
4
5
6
7
8
for frame_data in frames:
ether = Ether()
ether.dst = "00:11:22:33:44:55"
ether.src = "00:AA:BB:CC:DD:EE"
ether.type = 0x0800
ether /= Raw(load=frame_data)
sendp(ether, iface=interface)
print(f"发送数据帧: {len(frame_data)} 字节")

C++ + Npcap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <pcap.h>
#include <netinet/ether.h>
#include <netinet/ip.h>
#include <iostream>

void send_ethernet_frame(pcap_t* handle, const std::vector<uint8_t>& frame_data, const std::string& interface) {
struct ether_header eth_hdr;
memset(&eth_hdr, 0, sizeof(eth_hdr));
ether_aton_r("00:11:22:33:44:55", &eth_hdr.ether_dhost);
ether_aton_r("00:AA:BB:CC:DD:EE", &eth_hdr.ether_shost);
eth_hdr.ether_type = htons(ETH_P_IP);

std::vector<uint8_t> packet;
packet.insert(packet.end(), reinterpret_cast<uint8_t*>(&eth_hdr), reinterpret_cast<uint8_t*>(&eth_hdr) + sizeof(eth_hdr));
packet.insert(packet.end(), frame_data.begin(), frame_data.end());

if (pcap_sendpacket(handle, packet.data(), packet.size()) != 0) {
std::cerr << "Error sending packet: " << pcap_geterr(handle) << std::endl;
} else {
std::cout << "发送数据帧: " << frame_data.size() << " 字节" << std::endl;
}
}

int main() {
const char* device = "WLAN";
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* handle = pcap_open_live(device, BUFSIZ, 1, 1000, errbuf);
if (handle == nullptr) {
std::cerr << "无法打开设备 " << device << ": " << errbuf << std::endl;
return 1;
}

std::vector<uint8_t> data = read_file("C:\\Users\\21354\\Videos\\asdf.mp4");
std::vector<std::vector<uint8_t>> frames = split_data(data);

for (const auto& frame_data : frames) {
send_ethernet_frame(handle, frame_data, device);
}

pcap_close(handle);
std::cout << "所有数据帧发送完毕" << std::endl;
return 0;
}

总结

  • Python + Scapy 提供了高级抽象和简洁的语法,使得代码更加简洁和易读。
  • C++ + Npcap 虽然提供了更低级别的控制和更高的性能,但需要更多的代码和更复杂的逻辑来处理相同的功能。

如果你对网络编程的需求不是特别复杂,且对性能要求不是非常高,使用 Python 和 Scapy 是一个非常好的选择。如果你需要更高的性能和更细粒度的控制,那么 C++ 和 Npcap 可能更适合。


好了,在这里还是继续讲解一下scapy是怎样实现的,深入了解:(上吧!通义)


当然,我可以详细介绍 Scapy 中数据帧结构的定义。Scapy 是一个强大的 Python 库,用于网络包的构造、解析和发送。它内部已经定义了许多常见的网络协议数据结构,包括以太网帧(Ethernet Frame)、IP 包、TCP 段等。

以太网帧(Ethernet Frame)的定义

在 Scapy 中,以太网帧的数据结构是由 Ether 类定义的。以下是 Ether 类的主要属性和方法:

主要属性
  • dst: 目的 MAC 地址(6 字节)
  • src: 源 MAC 地址(6 字节)
  • type: 以太网类型字段(2 字节),用于指示上层协议类型(例如,0x0800 表示 IP 协议)
示例
1
2
3
4
5
6
7
from scapy.all import Ether

# 创建一个以太网帧
ether = Ether(dst="00:11:22:33:44:55", src="00:AA:BB:CC:DD:EE", type=0x0800)

# 打印以太网帧
print(ether.show())

内部实现

Scapy 的 Ether 类继承自 Packet 类,Packet 类是所有网络包的基础类。Ether 类的定义如下:

Ether 类定义
1
2
3
4
5
6
7
class Ether(Packet):
name = "Ethernet"
fields_desc = [
DestMACField("dst"),
SourceMACField("src"),
XShortEnumField("type", 0x9000, ETHER_TYPES)
]
字段解释
  • DestMACField(“dst”): 目的 MAC 地址字段,类型为 DestMACField,用于解析和构建目的 MAC 地址。
  • SourceMACField(“src”): 源 MAC 地址字段,类型为 SourceMACField,用于解析和构建源 MAC 地址。
  • XShortEnumField(“type”, 0x9000, ETHER_TYPES): 以太网类型字段,类型为 XShortEnumField,用于解析和构建以太网类型字段。ETHER_TYPES 是一个字典,包含常见的以太网类型及其对应的数值。

常见的以太网类型

Scapy 内置了一个 ETHER_TYPES 字典,定义了常见的以太网类型:

1
2
3
4
5
6
7
8
ETHER_TYPES = {
0x0800: "IP",
0x0806: "ARP",
0x8035: "RARP",
0x86dd: "IPv6",
0x88cc: "LLDP",
# 更多类型...
}

构造和解析以太网帧

构造以太网帧
1
2
3
4
5
6
7
8
9
10
from scapy.all import Ether, Raw

# 创建一个以太网帧
ether = Ether(dst="00:11:22:33:44:55", src="00:AA:BB:CC:DD:EE", type=0x0800)

# 添加数据部分
ether /= Raw(load=b"Hello, World!")

# 打印以太网帧
print(ether.show())
解析以太网帧
1
2
3
4
5
6
7
8
9
10
from scapy.all import Ether

# 假设我们从网络中接收到一个原始以太网帧
raw_frame = b'\x00\x11\x22\x33\x44\x55\x00\xaa\xbb\xcc\xdd\xee\x08\x00Hello, World!'

# 解析以太网帧
ether = Ether(raw_frame)

# 打印解析后的以太网帧
print(ether.show())

填充(Padding)

Scapy 在发送以太网帧时会自动处理填充。如果数据部分不足 46 字节,Scapy 会自动在数据后面添加填充字节,以确保整个帧的长度不低于 64 字节。

总结

Scapy 通过高度抽象的类和字段定义,简化了网络包的构造和解析。Ether 类定义了以太网帧的数据结构,包括目的 MAC 地址、源 MAC 地址和类型字段。Scapy 还提供了丰富的内置功能,如自动计算 CRC 校验码和处理填充,使得网络编程变得更加简单和高效。希望这些信息对您理解 Scapy 的内部实现有所帮助!如果有更多问题,请随时提问。


在 Scapy 中,计算和验证 CRC 校验码(FCS,Frame Check Sequence)通常是自动处理的。Scapy 在发送以太网帧时会自动计算 CRC 校验码,并在接收以太网帧时自动验证 CRC 校验码。不过,如果你想手动计算 CRC 校验码,Scapy 也提供了相应的工具。

自动处理 CRC 校验码

当你使用 Scapy 发送以太网帧时,Scapy 会自动计算 CRC 校验码并将其附加到帧的末尾。同样,当你接收以太网帧时,Scapy 会自动验证 CRC 校验码。

发送以太网帧
1
2
3
4
5
6
7
8
9
10
from scapy.all import Ether, Raw, sendp

# 创建一个以太网帧
ether = Ether(dst="00:11:22:33:44:55", src="00:AA:BB:CC:DD:EE", type=0x0800)

# 添加数据部分
ether /= Raw(load=b"Hello, World!")

# 发送以太网帧
sendp(ether, iface="WLAN")

在这个例子中,Scapy 会自动计算 CRC 校验码并将其附加到帧的末尾。

手动计算 CRC 校验码

如果你需要手动计算 CRC 校验码,可以使用 Scapy 提供的 checksum 函数。Scapy 使用的是标准的 CRC-32 校验算法。

手动计算 CRC 校验码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import binascii
from scapy.all import Ether, Raw

# 创建一个以太网帧
ether = Ether(dst="00:11:22:33:44:55", src="00:AA:BB:CC:DD:EE", type=0x0800)

# 添加数据部分
ether /= Raw(load=b"Hello, World!")

# 获取以太网帧的原始字节
raw_frame = bytes(ether)

# 计算 CRC 校验码
crc = binascii.crc32(raw_frame) & 0xFFFFFFFF

# 将 CRC 校验码转换为字节形式
crc_bytes = crc.to_bytes(4, byteorder='little')

# 将 CRC 校验码附加到帧的末尾
full_frame = raw_frame + crc_bytes

# 打印完整的帧
print(full_frame.hex())

解析和验证 CRC 校验码

当你接收到一个以太网帧时,可以手动验证 CRC 校验码。

解析和验证 CRC 校验码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import binascii
from scapy.all import Ether

# 假设我们从网络中接收到一个带有 CRC 校验码的原始以太网帧
raw_frame_with_crc = b'\x00\x11\x22\x33\x44\x55\x00\xaa\xbb\xcc\xdd\xee\x08\x00Hello, World!\x1e\xde\x4d\xad'

# 分离数据部分和 CRC 校验码
raw_frame = raw_frame_with_crc[:-4]
received_crc = raw_frame_with_crc[-4:]

# 计算 CRC 校验码
computed_crc = binascii.crc32(raw_frame) & 0xFFFFFFFF

# 将计算的 CRC 校验码转换为字节形式
computed_crc_bytes = computed_crc.to_bytes(4, byteorder='little')

# 验证 CRC 校验码
if received_crc == computed_crc_bytes:
print("CRC 校验码验证成功")
else:
print("CRC 校验码验证失败")

# 解析以太网帧
ether = Ether(raw_frame)

# 打印解析后的以太网帧
print(ether.show())

总结

  • 自动处理: Scapy 在发送和接收以太网帧时会自动处理 CRC 校验码的计算和验证。
  • 手动计算: 如果你需要手动计算 CRC 校验码,可以使用 binascii.crc32 函数。
  • 手动验证: 如果你需要手动验证 CRC 校验码,可以将计算的 CRC 校验码与接收到的 CRC 校验码进行比较。

希望这些信息对你理解 Scapy 中 CRC 校验码的处理有所帮助!如果有更多问题,请随时提问。


FCS(Frame Check Sequence,帧校验序列)是一种用于检测数据传输过程中错误的技术。在以太网和其他网络协议中,FCS 通常使用 32 位的 CRC(Cyclic Redundancy Check,循环冗余校验)来实现。CRC 是一种非常有效的错误检测机制,能够检测出大多数常见的传输错误。

生成多项式 G(X) 和 CRC-32

1. 生成多项式 G(X)

生成多项式(Generator Polynomial)是一个特定的多项式,用于生成 CRC 校验码。对于 32 位的 CRC 校验码,最常用的生成多项式是 G(X) = X^32 + X^26 + X^23 + X^22 + X^16 + X^12 + X^11 + X^10 + X^8 + X^7 + X^5 + X^4 + X^2 + X + 1

这个多项式可以表示为二进制数 100000100110000010001110110110111,或者十六进制数 0x04C11DB7

2. CRC-32

CRC-32 是一种使用 32 位生成多项式的 CRC 校验算法。它是最常用的一种 CRC 校验算法,广泛应用于各种通信协议和文件格式中。CRC-32 的生成多项式就是上面提到的 0x04C11DB7

CRC-32 的计算过程

  1. 初始值:通常,CRC-32 的初始值设为全 1(即 0xFFFFFFFF),但也可以根据具体应用设置为其他值。
  2. 预处理:将数据帧的每个字节依次处理。
  3. 多项式除法:使用生成多项式 0x04C11DB7 对数据进行模 2 除法运算。
  4. 结果:最终得到的 32 位结果就是 CRC-32 校验码。

Python 实现 CRC-32

Scapy 内部使用了 binascii 模块来计算 CRC-32。以下是一个简单的 Python 实现,展示了如何手动计算 CRC-32 校验码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import binascii

def calculate_crc32(data):
# 计算 CRC-32 校验码
crc = binascii.crc32(data) & 0xFFFFFFFF
return crc

# 示例数据
data = b"Hello, World!"

# 计算 CRC-32 校验码
crc = calculate_crc32(data)

# 将 CRC 校验码转换为字节形式
crc_bytes = crc.to_bytes(4, byteorder='little')

# 打印结果
print(f"原始数据: {data}")
print(f"CRC-32 校验码: {crc:08x}")
print(f"CRC-32 校验码(字节形式): {crc_bytes.hex()}")

输出

1
2
3
原始数据: b'Hello, World!'
CRC-32 校验码: 1d2bcb2c
CRC-32 校验码(字节形式): 2c0b2b1d

Scapy 中的 CRC-32

在 Scapy 中,发送以太网帧时,CRC-32 校验码的计算和附加是自动处理的。Scapy 使用 binascii 模块来计算 CRC-32 校验码。

发送以太网帧
1
2
3
4
5
6
7
8
9
10
from scapy.all import Ether, Raw, sendp

# 创建一个以太网帧
ether = Ether(dst="00:11:22:33:44:55", src="00:AA:BB:CC:DD:EE", type=0x0800)

# 添加数据部分
ether /= Raw(load=b"Hello, World!")

# 发送以太网帧
sendp(ether, iface="WLAN")

在这个例子中,Scapy 会自动计算 CRC-32 校验码并将其附加到帧的末尾。

总结

  • **生成多项式 G(X)**:用于生成 CRC 校验码的多项式,对于 CRC-32,生成多项式是 0x04C11DB7
  • CRC-32:使用 32 位生成多项式的 CRC 校验算法,广泛应用于各种通信协议和文件格式中。
  • 手动计算:可以使用 binascii.crc32 函数来手动计算 CRC-32 校验码。
  • Scapy 自动处理:Scapy 在发送和接收以太网帧时会自动处理 CRC-32 校验码的计算和验证。

希望这些信息对你理解 CRC-32 和生成多项式 G(X) 有所帮助!如果有更多问题,请随时提问。


Comments