andersonjackc/CS544-Phanatwork
GitHub: andersonjackc/CS544-Phanatwork
一个基于 QUIC 的有状态网络协议教学项目,通过棒球游戏场景展示二进制 PDU 设计与服务器端 DFA 状态验证的完整实现。
Stars: 0 | Forks: 0
# CS544-Phanatwork
用于存放 Drexel CS544 课程期末项目的代码仓库。
这是一个基于 QUIC 的协议,用于验证客户端与服务器之间传输的消息,其中客户端正在进行一场棒球比赛。该协议并非特定于某个应用,因为协议本身仅负责验证消息类型。
本仓库包含主要的 v1.0 Phanatwork 协议客户端/服务器实现,以及一个简单的棒球模拟器。
Phanatwork 是一个使用 QUIC 传输的有状态协议,并且具有二进制 PDU,因此使用其他语言编写并遵循该规范的客户端,同样能够与服务器进行通信。
## 已实现的功能
- 基于 QUIC 的客户端/服务器通信
- 针对请求头和所有负载使用二进制 PDU
- 带有版本号的协议头
- 在服务器端进行 DFA 状态验证
- 完整的客户端设置流程:HELLO -> AUTH -> JOIN -> ROSTER_UPDATE -> READY
- 完整的游戏循环:GAME_UPDATE -> PLAY_ACTION -> ACTION_ACK -> GAME_UPDATE(循环直到 GAME_OVER)
- CLOSE / ERROR 处理
- 在服务器端进行角色分配
- 基础身份验证处理(用户名和密码为硬编码,但预留了接入更复杂验证方式的能力)
- 将 QUIC 的默认空闲超时时间从 1 分钟修改为 5 分钟,以便于测试和游玩
- 实现了 Session ID 和 Turn ID,使得服务器始终能够确保在正确的时间由正确的参与者进行通信
- 多种游戏模式
- 手动模式:由玩家自己游玩,亲自选择动作
- 自动模式:用于模拟比赛,或与人机对战
- 设置了在服务器初始化时注入游戏规则的功能,用于处理比赛判定,从而支持动态规则
- 这也证明了该协议是独立于具体实现的
- 配置了自动化测试
- DFA 验证通过及错误测试
- 格式错误的 PDU 测试
- 小规模的模糊测试
# 主要文件摘要
`pdu.py` 定义了 Phanatwork 的二进制消息类型。它包含了消息/角色/动作/错误常量以及二进制结构体的定义。同时还包含了序列化和反序列化方法。基础版本改编自课堂上的 QUIC 示例。
`phanatwork_server.py` 包含了服务器协议循环,负责获取 QUIC 流数据、解析 PDU,并将信息传递给状态机。
`phanatwork_state.py` 包含了服务器的状态机,也是 DFA 验证逻辑执行的地方。它负责跟踪各个客户端的会话、分配的角色以及提交的动作。
`phanatwork_client.py` 包含了客户端的协议交互流程。它向服务器发送建立连接的 PDU,等待服务器的响应,并在游戏循环中提交动作。
`quic_engine.py` 改编自课堂示例并进行了少许修改。它处理了 Phanatwork 的作用域,采用了不同的关闭连接实现方式,并更新了默认的空闲超时时间。此外,还对其进行了修改以支持处理多个客户端。
`phanatwork.py` 是一个旨在直接与协议交互的脚本,不需要直接应用游戏规则。它不是应用层的棒球模拟器,而是一种用于调试和直接与 Phanatwork 消息类型交互的方式。
`test_phanatwork_protocol.py` 包含了所有关于 Phanatwork 的自动化测试。它提供了 DFA 验证、格式错误的 PDU 测试以及一些简单的模糊测试。
`baseball_simulator/simple_baseball.py` 包含了棒球比赛应用层的规则。它与协议本身完全分离,以此证明该协议并不与游戏本身绑定。
`baseball_app.py` 是使用 Phanatwork 作为其传输协议的主游戏模拟器。它导入了 `simple_baseball.py` 的规则,随后在实际游戏循环中判定比赛结果。它不显示任何特定的 Phanatwork 信息,仅展示 CLI 游戏本身。它允许导入配置文件以方便使用。配置文件包含服务器地址、端口号、显示名称、用户名、密码、队伍名称、角色、游戏模式(自动/手动)、延迟(自动模式下动作之间的间隔时间)、回合数(游戏被迫结束前允许的回合数)以及玩家字典。它可以配置为在服务器端使用来自 `simple_baseball.py` 的不同规则集,例如为了方便测试而采用普通规则但只打 3 局的版本,将每个半局的出局数从 3 次减少到 1 次的“一人出局”版本,以及只有 1 局的缩短版游戏。此外,还有一个用于确定性测试的名为 "AlwaysOut" 的测试规则集。
# 环境要求
Phanatwork 是使用 Python 3.10.18 和 `aioquic` 包开发的。其他类似的 Python 版本极有可能也可以正常运行,但 3.10.18 是我在测试和开发过程中所使用的版本。
所使用的 Python 包已在 `requirements.txt` 文件中定义,可以通过运行以下命令进行安装:
```python -m pip install -r requirements.txt```
# 证书
由于 Phanatwork 是基于 QUIC 构建的,它需要证书和私钥才能使 TLS 正常工作。我使用了示例项目中的 `gencert.sh` 脚本,它有自己单独的 `README.md`(在示例项目中提供)。我还提交了开发证书,因此在 2027 年 6 月之前应该不需要重新生成。
# 运行 Phanatwork
客户端和服务器可以通过主要的 UI 棒球游戏应用和调试脚本来运行。下面将对这两种方式进行详细说明,但主要建议运行、使用和用于评分的脚本是 `baseball_app.py` 版本。
你需要使用三个终端来运行 Phanatwork 示例。一个用于运行服务器,另外两个分别用于每个客户端。
## 运行服务器
你可以通过几种不同的方式来运行服务器。
要运行棒球游戏模拟器版本的服务器,你可以执行以下任一操作。
```python baseball_app.py host``` 将使用所有默认值来运行棒球模拟器。即使用 `localhost` 作为服务器地址,`4433` 作为端口号,默认的证书路径位置,默认的普通棒球游戏规则集,并且不为随机数提供种子(也就是说,让比赛结果尽可能随机)。
也可以通过以下方式运行:
```python baseball_app.py host --listen localhost --port 4433 --cert-file ./certs/quic_certificate.pem --key-file ./certs/quic_private_key.pem --rule-set standard --seed 1234```
rule-set 参数可以是 "standard"、"short"、"one-out" 中的任意一个,而 seed 参数可以省略,以生成随机的游戏对局。
要运行 Phanatwork 协议调试器版本的服务器,你可以执行以下任一操作。此服务器与棒球游戏模拟器版本没有太大区别,因为两者都会记录服务器端发生的事情,但它不运行游戏逻辑。
```python phanatwork.py server``` 将使用服务器地址、端口号、证书路径和密钥路径的所有默认值来运行调试版本服务器。
```python phanatwork.py server --listen localhost --port 4433 --cert-file ./certs/quic_certificate.pem --key-file ./certs/quic_private_key.pem``` 允许你自定义地址、端口号以及证书/密钥的位置。
## 运行客户端
与服务器类似,你也可以通过多种方式运行客户端。
```python baseball_app.py join``` 将使用所有默认参数加入一个已存在的服务器。即使用 localhost 作为地址,4433 作为端口号,与服务器默认路径相同的证书文件路径,"Team" 作为队伍名称,"Player" 作为显示名称,"either" 作为所需角色,"player" 作为用户名,"password" 作为密码,9999 作为回合数,"manual" 作为游戏模式,并且自动模式下动作间的延迟为 0.25 秒。
你也可以通过命令行传入所有这些参数。
```python baseball_app.py join --server localhost --port 4433 --cert-file ./certs/quic_certificate.pem --name Jack --team Phillies --role home --username home --password password --play-mode manual --turns 9999```
最简单的运行方式是通过以下方式导入配置文件:
```python baseball_app.py join --config-file path/to/config.json```
并且在 `configs` 文件夹中已经提供了一些预先配置好的配置文件。如果证书路径不是默认路径,则仍需通过 CLI 指定。
你还可以直接与 Phanatwork 交互,并查看反序列化后的负载数据报,以便于调试。可以使用以下命令运行一个默认实例:
```python phanatwork.py client```
这会将服务器地址、端口号、证书路径、显示名称、队伍名称、角色、用户名、密码、游戏模式和回合数全部置为默认值。
以上每一项参数也都可以作为 CLI 参数传入,方式如下:
```python phanatwork.py client --server localhost --port 4433 --cert-file ./certs/quic_certificate.pem --name Player --team Team --role either --username player --password password --play-mode auto --turns 3```
# 游戏流程
一旦服务器启动,并且两个客户端都完成了 HELLO -> AUTH -> JOIN 流程,游戏便正式开始。
在手动模式下,会出现提示要求用户选择他们的动作。目前提供了一个最小化的投球/进攻选项列表。一旦服务器收到来自双方用户的动作,它就会根据预定义的规则集判定比赛结果,并将新的游戏状态信息返回给用户。随后,用户会再次收到提示以进行下一个动作。这个循环会一直持续到游戏结束。
自动模式几乎与此相同,只不过自动用户更像是一个机器人。它在游戏开始后不会提示进行任何输入,而是随机选择一个动作发送给服务器。
你可以对用户进行任意组合,例如双方都是手动、双方都是自动,或者一人手动一人自动。
# 错误处理
目前,错误会导致 Phanatwork 协议向客户端发送终止消息。从设计上讲,某些错误在技术上是可恢复的,例如身份验证问题,但开发的重点放在了 PDU 实现和 DFA 状态验证上。
# DFA 验证
服务器在接收每条消息之前会检查协议状态。
强制执行的状态规则示例:
- 客户端不能在 `HELLO` 之前发送 `AUTH`。
- 客户端不能在成功通过身份验证之前发送 `JOIN`。
- 客户端不能在两个客户端都加入之前发送 `READY`。
- 客户端不能在游戏开始之前发送 `PLAY_ACTION`。
- 客户端不能发送过期的 turn id。
- 客户端不能为同一个回合发送重复的动作。
- 处于防守方时,客户端不能发送进攻动作。
- 处于进攻方时,客户端不能发送防守动作。
- 一旦主队和客队角色都已满员,第三个客户端将无法加入。
这遵循了规范文档中详述的 DFA。
# 测试
可以通过调用 ```python test_phanatwork_protocol.py``` 来运行自动化测试
测试利用了模拟节点(fake peers)来验证协议是否正在执行 DFA 规则,并确保能够针对各种格式错误的数据包类型正确地报错。每个测试的详细信息都在下方的输出中展示,预期输出如下所示:
```
PHANATWORK PROTOCOL TEST SUMMARY
============================================================
DFA passing cases (6/6 passed)
------------------------------------------------------------
[PASS] HELLO -> AUTH -> JOIN succeeds
[PASS] Two clients joining triggers ROSTER_UPDATE
[PASS] Both clients READY triggers initial GAME_UPDATE
[PASS] Valid actions resolve a turn and increment turn_id
[PASS] Injected one-out rules advance the half inning
[PASS] Transport disconnect notifies opponent with server CLOSE
DFA validation errors (9/9 passed)
------------------------------------------------------------
[PASS] AUTH before HELLO is rejected
[PASS] Wrong password is rejected
[PASS] JOIN before AUTH is rejected
[PASS] READY before both clients joined is rejected
[PASS] PLAY_ACTION before READY is rejected
[PASS] Wrong action for current role is rejected
[PASS] Duplicate action in same turn is rejected
[PASS] Stale turn id is rejected
[PASS] Client protocol CLOSE is rejected
Malformed PDU errors (4/4 passed)
------------------------------------------------------------
[PASS] Too-short PDU header returns MALFORMED_PDU
[PASS] Unsupported major version returns UNSUPPORTED_VERSION
[PASS] Oversized payload length returns PAYLOAD_TOO_LARGE
[PASS] Incomplete payload returns MALFORMED_PDU
Fuzz testing (2/2 passed)
------------------------------------------------------------
[PASS] Random byte parser fuzzing is controlled
[PASS] Mutated valid PDU parser fuzzing is controlled
============================================================
TOTAL: 21/21 passed
```
# 开发期间的设计更新
我在开发 Phanatwork 期间发现需要进行的一些设计更新如下:
- 我发现,用户在空闲状态下的 1 分钟超时时间太短了,因为我在调试期间经常超时。在设计文档中,这已被更新为 5 分钟。
- 我发现客户端在游戏开始时并不知晓所有的游戏信息,并且需要在两个客户端都发送了 `READY` 消息后,接收一条初始的 `GAME_UPDATE` 消息。这已被添加到 DFA 中,作为从 `WAIT_READY` 到 `WAIT_BOTH_ACTIONS` 的转换动作。
# 开发简化说明
由于部分内容被刻意简化了,该项目可以向几个不同的方向发展。具体如下:
- 身份验证方法极不安全,使用了硬编码的纯文本“数据库”。这可以相对容易地更新为更复杂的方法,但这并不是本项目的重点。
- 棒球游戏应用层的逻辑被过度简化了。目前已经为实现更高级的功能搭建了框架,例如基于玩家统计数据的比赛结果,以及牵制或盗垒等进一步的动作。
- 虽然服务器支持并发,允许同时连接多个客户端,但目前同一时间只能进行一场比赛。
- UI 完全基于 CLI,但由于 Phanatwork 独立于具体实现,开发更复杂的 GUI 的框架已经具备。
- 错误处理会终止客户端连接,但有些错误其实应该是可恢复的。
# 额外加分内容
- 服务器支持并发处理客户端连接,这对于包含两个客户端和一个服务器的 Phanatwork 架构来说是必需的。
- 在实现过程中发现了一些设计上的更新,例如需要更新 DFA 以支持初始的 GAME_UPDATE 消息以及增加超时时间。这些内容在前面的 README 中已有详细说明,并已在提交的设计文档中进行了更新。
- 我制作了一个演示视频:[https://www.youtube.com/watch?v=lClG7GtQpi0](https://www.youtube.com/watch?v=lClG7GtQpi0)
- 在开发过程中使用了 GitHub 进行版本控制。
- 创建了自动化测试脚本。
- 我还提交了 ospf-advanced-setup.pdf 实验。
# 最终说明
本项目的重点是协议设计,而不是棒球游戏模拟。主要的关注点包括:
- 使用 QUIC 创建客户端/服务器架构
- 允许主机名和端口号
- 有状态的 DFA 验证
- 二进制 PDU 序列化
- 自动化测试
标签:Python, QUIC, 二进制协议, 内核驱动, 客户端服务端, 无后门, 状态机, 网络协议, 自动机验证, 课程设计, 逆向工具