pyTCP
用 Python 编写的 TCP/IP 堆栈
PyTCP 是一个用 Python 编写的功能齐全的 TCP/IP 堆栈。它支持基于 TCP 流的传输,以及基于滑动窗口机制和基本拥塞控制的可靠数据包传送。它还支持具有 SLAAC 地址配置的 IPv6/ICMPv6 协议。它作为附加到 Linux TAP 接口的用户空间程序运行。它实现了简单的路由,可以通过本地网络和互联网发送和接收流量。
与之前的版本不同,2.7 版本包含库形式的 PyTCP 堆栈代码,以便外部代码可以轻松导入和使用它。这将使用户体验更加流畅,并最终提供在任何第 3 方应用程序中用 PyTCP 调用替换标准 Linux 堆栈调用(例如套接字库)的完整能力。
该项目最初是纯粹的教育工作,旨在提高我的 Python 技能并刷新我的网络知识,作为 Facebook 网络工程师角色准备的一部分。从那时起,它变得更像是一个“宠物项目”,我不定期地投入了一些时间。然而,通常每隔一两个月就会添加一些更新。
我欢迎任何对网络编程感兴趣的人的贡献和帮助。任何意见都会受到赞赏。另外,请记住,某些堆栈功能可能仅部分实现(根据堆栈操作的需要)。它们可能以次优方式实现,或者不是 100% 符合 RFC 的方式(由于缺乏时间),或者它们可能包含我仍需要修复的错误。
请随时检查我的其他两个相关项目:
- RusTCP - 尝试用 Rust 重写一些 PyTCP 功能并使用它来创建 IPv6/SRv6 实验室路由器。
- SeaTCP - 尝试使用 C 和汇编语言创建低延迟堆栈。
工作原理和测试设置
PyTCP 堆栈依赖于 Linux TAP 接口。 TAP 接口是一个虚拟接口,在网络端,可以通过 Linux 桥或 Open vSwitch“插入”现有虚拟网络基础设施。在内部端,TAP 接口可以像任何其他 NIC 一样使用,通过编程方式向其发送和接收数据包。
如果您想在本地网络中测试 PyTCP 堆栈,我建议创建以下网络设置,该设置允许您同时将 Linux 内核(本质上是您的 Linux 操作系统)和 PyTCP 堆栈连接到本地网络。
<INTERNET> <---> [ROUTER] <---> (eth0)-[Linux bridge]-(br0) <---> [Linux TCP/IP stack]
|
|--(tap7) <---> [PyTCP TCP/IP stack]
示例程序(客户端或服务)启动堆栈后,可以通过简化的 BSD 套接字(如 API 接口)与其进行通信。还可以通过调用PacketHandler
类中的_*_phtx()
方法之一直接发送数据包。
从 GitHub 存储库克隆 PyTCP
在大多数情况下,PyTCP 应直接从 GitHub 存储库克隆,因为这种类型的安装提供了完整的开发和测试环境。
git clone http://github.com/ccie18643/PyTCP
克隆后,我们可以运行其中的示例之一:
- 转到堆栈根目录(称为“PyTCP”)。
- 如果需要,运行
sudo make bridge
命令来创建“br0”桥。 - 运行
sudo make tap
命令创建 tap7 接口并将其分配给“br0”网桥。 - 运行
make
命令来创建适合开发和测试的虚拟环境。 - 跑步
. venv/bin/activate
命令激活虚拟环境。 - 执行任何示例,例如
example/run_stack.py
。 - 按 Ctrl-C 停止它。
要微调各种堆栈操作参数,请相应地编辑pytcp/config.py
文件。
从 PyPi 存储库安装 PyTCP
PyTCP 也可以作为 PyPi 存储库中的常规模块安装。
python -m pip install PyTCP
安装后,请确保 TAP 接口可运行并添加到桥接器中。
sudo ip tuntap add name tap7 mode tap
sudo ip link set dev tap7 up
sudo brctl addbr br0
sudo brctl addif br0 tap7
可以使用以下代码导入并启动 PyTCP 堆栈。它启动堆栈子系统并分别使用 DHCPv4 和 IPv6 SLAAC 自动配置 IPv4 和 IPv6 协议地址。
from pytcp import TcpIpStack
stack = TcpIpStack ( interface = "tap7" )
stack . start ()
堆栈子系统在自己的线程中运行。启动后,堆栈将控制权交还给用户代码,并且可以使用以下调用停止。
特征
已经实施:
- Stack -使用“零复制”方法的快速数据包解析器。
- Stack -使用“零复制”方法的快速数据包组装器。
- 堆栈 - MAC 地址操作库 - 与缓冲区协议 (Memoryview) 兼容。
- Stack - IPv4 地址操作库 - 与缓冲区协议 (Memoryview) 兼容,不依赖于 Python 标准库。
- Stack - IPv6 地址操作库 - 与缓冲区协议 (Memoryview) 兼容,不依赖于 Python 标准库。
- 代码 -一些库和模块的单元测试(基于 Facebook 的 Testslide 框架)
- 以太网协议 -支持以太网 II 标准帧。
- 以太网协议 -单播、IPv4 多播、IPv6 多播和广播寻址。
- ARP 协议 -回复、查询、ARP 缓存机制。
- ARP 协议 - ARP 探测/通告 IP 冲突检测 (ACD) 机制。
- IPv4 协议 -默认路由,堆栈可以使用 IPv4 协议通过 Internet 与主机通信。
- IPv4 协议 -使用 DHCPv4 协议自动配置 IPv4 地址。
- IPv4 协议 -入站数据包碎片整理,强大的机制能够处理无序和重叠的数据片段。
- IPv4 协议 -出站数据包分段。
- IPv4 协议 -接受但不支持 IPv4 选项。
- IPv4 协议 -支持多个堆栈的 IPv4 地址,每个地址的作用就像分配给单独的 VRF 一样
- ICMPv4 协议 -回显请求、回显应答和端口不可达消息。
- IPv6 协议 -默认路由,堆栈可以使用 IPv6 协议通过 Internet 与主机通信。
- IPv6 协议 -使用 EUI64 和重复地址检测自动链路本地地址配置。
- IPv6 协议 -使用路由器通告/EUI64 自动 GUA 地址配置。
- IPv6 协议 -自动分配请求节点多播地址。
- IPv6 协议 -自动分配 IPv6 多播 MAC 地址。
- IPv6 协议 -入站数据包碎片整理,强大的机制能够处理无序和重叠的数据片段。
- IPv6 协议 -出站数据包分段。
- ICMPv6 协议 -回显请求、回显应答和端口不可达消息。
- ICMPv6 协议 -邻居发现、重复地址检测。
- ICMPv6 协议 -邻居发现缓存机制。
- ICMPv6 协议 -多播侦听器发现 v2 (MLDv2) 协议实现(仅堆栈需要的消息)。
- UDP 协议 -完全支持。堆栈可以使用UDP协议与其他主机交换数据。
- UDP 套接字 -完全支持,堆栈的“最终用户”API 类似于 Berkeley 套接字。
- UDP 服务 -出于测试目的而实现的 Echo、Discard 和 Daytime 服务(在“示例”中)。
- TCP 协议 - TCP 有限状态机的完整实现。此时,堆栈可以通过 TCP 协议与其他主机交换批量数据。
- TCP 协议 - TCP 选项支持:MSS、WSCALE、SACKPERM、TIMESTAMP。
- TCP 协议 - TCP 滑动窗口机制和数据重传(快速重传和基于时间的场景)。
- TCP 协议 - TCP 退避机制/基本拥塞控制。
- TCP 协议 - TCP SYN/FIN 数据包重传。
- TCP 套接字 -完全支持,堆栈的“最终用户”API 类似于 Berkeley 套接字
待实施:
示例
几个 ping 数据包和两只猴子是通过 IPv6 协议上的 TCP 传送的。
IPv6 邻居发现/重复地址检测/地址自动配置。
- 堆栈尝试自动配置其链路本地地址。它将其生成为 EUI64 地址。作为 DAD 过程的一部分,它加入适当的请求节点多播组并为其生成的地址发送邻居请求。
- 堆栈不会收到其生成的地址的任何邻居通告,因此会将其分配给其接口。
- 堆栈尝试分配预先配置的静态地址。作为 DAD 过程的一部分,它加入适当的请求节点多播组并发送静态地址的邻居请求。
- 已分配相同地址的另一台主机会回复邻居通告消息。这告诉堆栈另一台主机已经分配了它尝试分配的地址,因此堆栈无法使用它。
- 堆栈发送路由器请求消息来检查是否有应使用的全局前缀。
- 路由器以包含附加前缀的路由器通告进行响应。
- 堆栈尝试分配根据接收到的前缀和 EUI64 主机部分生成的地址。作为 DAD 过程的一部分,它加入适当的请求节点多播组并发送静态地址的邻居请求。
- 堆栈不会收到其生成的地址的任何邻居通告,因此会将其分配给其接口。
- 分配所有地址后,堆栈会再发出一份多播侦听器报告,列出它想要侦听的所有多播地址。
TCP 快速重传在丢失 TX 数据包后生效。
- 由于模拟数据包丢失机制,传出数据包“丢失”。
- 对等方注意到数据包 SEQ 号不一致并发出“快速重传请求”。
- 堆栈接收请求并重传丢失的数据包。
RX 数据包丢失事件期间运行的无序队列
- 由于模拟数据包丢失机制,传入数据包“丢失”。
- 堆栈注意到入站数据包的 SEQ 号不一致,并发送“快速重传”请求。
- 在对等方收到请求之前,它会发送多个 SEQ 高于堆栈预期的数据包。堆栈对所有这些数据包进行排队。
- 对等方重新传输丢失的数据包。
- 堆栈接收到丢失的数据包,拉出存储在乱序队列中的所有数据包并进行处理。
- 堆栈发送 ACK 数据包以确认从队列中拉出的最新数据包。
TCP 有限状态机 - 堆栈正在运行 TCP Echo 服务。
- 对等点打开连接。
- 对等方发送数据。
- 堆栈回显数据。
- 对等方关闭连接。
TCP 有限状态机 - 堆栈正在运行 TCP Echo 客户端。
- 堆栈打开连接。
- 堆栈发送数据。
- Peer 回显数据。
- 堆栈关闭连接。
预解析数据包健全性检查正在进行中。
- 第一个屏幕截图显示了健全性检查已关闭的堆栈。格式错误的 ICMPv6 数据包可能会导致其崩溃。
- 第二个屏幕截图显示了开启健全性检查的堆栈。格式错误的 ICMPv6 数据包在传递到 ICMPv6 协议解析器之前会被丢弃。
- 第三个屏幕截图显示了格式错误的数据包。即使数据包仅包含一条记录,MA 记录字段的数量已设置为 777。
ARP 探测/通告机制。
- 堆栈使用 ARP 探针来查找配置的每个 IP 地址可能存在的冲突。
- 其中一个 IP 地址 (192.168.9.102) 已被占用,因此堆栈会收到有关该地址的通知并跳过它。
- 其余的 IP 地址是免费的,因此堆栈通过为每个地址发送 ARP 公告来声明它们。
ARP 解析和处理 ping 数据包。
- 主机 192.168.9.20 尝试 ping 堆栈。为此,它首先发送 ARP 请求数据包以找出堆栈的 MAC 地址。
- 堆栈通过发送 ARP 应答数据包进行响应(堆栈不需要发送其请求,因为它已经从主机的请求中记录了主机的 MAC)。
- 主机发送 ping 数据包,堆栈响应它们。
IP 碎片。
- 主机使用三个分段的 IP 数据包(三个片段)发送 4Kb UDP 数据报。
- 堆栈接收数据包并将它们组装成一个整体,然后将其(通过 UDP 协议处理程序和 UDP 套接字)传递给 UDO Echo 服务。
- UDP Echo 服务拾取数据并将其放回 UDP 套接字。
- UDP 数据报被传递到 IP 协议处理程序,该处理程序创建一个 IP 数据包,并在检查它是否超出链路后,MTU 将其分段为三个单独的 IP 数据包。
- IP 数据包被封装在以太网帧中并放置在 TX 环上。