Note
为什么没有给完整的 proj ?
因为我在WSL里好像网络是有问题,而且我不知道怎么配置。所以我就是额外在本地复制了WSL里写好的代码,然后创建文件的。
参考课程网站:
- Project 1: https://sp25.cs168.io/proj1/
- Project 2: https://sp25.cs168.io/proj2/
- Project 3: https://sp25.cs168.io/proj3/
这份文档按我自己的实际学习经验来写的。
这三次作业虽然内容不一样,但做法其实很统一。
共同建议(其实应该是必须这么做):
- 如果是 Windows,在
WSL里做。 - 优先用
python3,不要默认写python - 严格在官网指定目录下运行命令
- 只改官方要求你改的那个文件
- 每做完一个阶段就测,不要一次写完再找 bug
为什么统一用 python3:
因为我用python会报错,但是python3不会。
官方要求改动的核心文件分别是:
proj1:traceroute.pyproj2:dv_router.pyproj3:student_socket.py
因为我不知道Gradescope的邀请码或者是否开放,我就是在本地测试的。 特别的:
proj2和proj3主要依靠本地单元测试、模拟器和命令行调试proj1因为官方本地测试支持弱一些,所以主要依靠手动验证 traceroute 行为
proj1 的目标是实现一个 traceroute。
你要做的事情本质上是:
- 发 UDP probe
- 收 ICMP reply
- 解析 IPv4 / ICMP / UDP 头
- 根据 TTL 一跳一跳地恢复路径
- 在
1A里先做基本版 - 在
1B里处理各种异常情况
官网对 proj1 的运行环境要求可以直接整理成下面这些点:
- 操作系统:项目测试环境是 Linux 和 Mac
- Python 版本:官方测试版本是
Python 3.11 - 工作目录:所有命令都应该在
cs168-sp25-proj1-traceroute目录下运行 - 可修改文件:只改
traceroute.py - 运行方式:需要直接在本机网络环境里发探测包、收 ICMP 响应
这部分一定要先确认清楚,因为 proj1 和后两个项目不太一样,它不是纯脚本模拟,而是真的要和本机网络栈打交道。
检查环境的命令是:
python3 --version
sudo python3 traceroute.py cmu.edu这里的 sudo 很重要,因为 traceroute 通常要用原始套接字,普通权限可能不够。
如果这里都跑不通,就先不要急着写逻辑,先把运行环境修好。
Project 1 最好按这个顺序推进:
- 先看
Traceroute Guide - 先手动跑一次 traceroute,理解返回包长什么样
- 实现
IPv4、ICMP、UDP三个 header parser - 完成
1A的基本 traceroute - 确认能在简单场景下输出正确路径
- 再进入
1B的异常处理
Project 1 的难点不是代码量,而是「你是否真的理解收到的 ICMP 包在说什么」。
你必须搞清楚:
- 当前收到的是
ICMP Time Exceeded还是Destination Unreachable - 这个 ICMP 包是不是你的 probe 引起的
- 里面嵌套的原始 IP/UDP 头是不是和你这次 traceroute 对得上
- 这个 response 属于当前 TTL,还是上一次 probe 的延迟包
1A 核心是三件事:
- 正确解析包头
- 每个 TTL 发
PROBE_ATTEMPT_COUNT个 probe - 收到目标主机响应后结束 traceroute
你要特别注意返回值格式:
- 返回的是
list[list[str]] - 第
i个子列表表示 TTL 为i+1时发现的路由器 - 同一个 TTL 下不要重复记录相同 IP
- 如果这个 TTL 没探测到任何路由器,就放空列表
- 一旦探测到目标 IP,最后一个列表应该是
[ip]
1B 不是让你重写 traceroute,而是在已有代码上补「抗脏数据能力」。
你要重点处理:
- 无效 ICMP type
- 无效 ICMP code
- 目的主机不存在
- 某些路由器完全不回包
- packet drop
- duplicate response
- duplicate probe
- delayed duplicate
- 来自别的 traceroute 的旧 response
这是最容易挂 1B 的原因。
不是所有 ICMP 都是你这次 traceroute 的合法响应。你必须检查:
- type / code 对不对
- 内层原始包是不是你发的
- 目标 IP 和端口是不是匹配
官网 1B 明确有 duplicate tests。
如果你不对响应去重,就会出现:
- 某个 TTL 多出重复 IP
- 或者上一层 TTL 的残留包跑到下一层去
官网专门提醒了这一点。
如果你每个 TTL 都强行把接收队列完全 drain 掉,虽然逻辑上可能还能跑,但会特别慢,还容易导致简单测试超时。
正确思路是:
- 当前 TTL 收到足够的有效响应后就可以前进
- 但如果发生重复包,也要考虑适当清理残留,别把脏包留到下一轮
不是每一跳都会回你。
所以你不能假设:
- 每个 TTL 一定有响应
- 最终一定能到达目标主机
proj2 的目标是实现一个 distance-vector router。
核心内容包括:
- 静态路由
- 数据转发
- 发送路由通告
- 接收路由通告并更新表项
- 超时删除
- poison / split horizon / poison reverse
- incremental updates
- link up / link down triggered updates
官方说明里给出的 setup 要点:
- 操作系统:这个项目本质上是本地 Python 模拟,不像
proj1那样依赖 raw socket - 测试版本是
Python 3.8 - 命令要在
cs168-sp25-proj2-routing/simulator目录下运行 - 只改
dv_router.py
这里其实可以把官方运行环境理解成三件事:
- 进入
simulator目录 - 跑
dv_unit_tests.py - 跑
simulator.py看网络可视化和表项变化
如果不在这个目录下运行,测试脚本和拓扑模块的相对路径很容易直接错掉。
常用命令:
python3 --version
python3 dv_unit_tests.py 1
python3 dv_unit_tests.py 5
python3 dv_unit_tests.py 10
python3 simulator.py --start --default-switch-type=dv_router topos.simpleProject 2 很适合严格按 stage 做。
推荐顺序:
- Stage 1:
add_static_route - Stage 2:
handle_data_packet - Stage 3:
send_routes - Stage 4:
handle_route_advertisement - Stage 5:
expire_routes - Stage 6/7/8/9: split horizon、poison reverse、poison expired 等增强逻辑
- Stage 10A: incremental triggered updates
- Stage 10B: link up / link down triggered updates
你要始终抓住 Bellman-Ford 更新逻辑:
- 邻居告诉我它到某个目标的代价
- 我把它的代价加上到这个邻居的链路代价
- 如果这条路更好,我就更新自己的 forwarding table
- 如果这个邻居本来就是我当前 next hop,那它的后续更新我也要接受
同时你要记住:
- forwarding table 的 key 是目的 host
- value 里要记录
port、latency、expire_time - 项目里找的是到 host 的路,不是到 router 的路
很多人 Stage 1 能过,Stage 2 直接挂,因为只会建表,不会按表转发。
handle_data_packet 里至少要检查:
- 目的地址是否在表中
- latency 是否小于
INFINITY - 符合条件才发送
收到广告时,不是「只有更优路径才更新」这么简单。
如果当前 route 就是从这个邻居学来的,那这个邻居后续发来的更新你通常也要跟着改,不然表会陈旧。
这会导致:
- 实际链路已经断了
- 但路由器还在往旧端口发包
官网专门用断链测试来卡这个点。
POISON_EXPIRED、POISON_ON_LINK_DOWN、POISON_REVERSE 这些名字很像,但触发条件完全不一样。
不要混:
- expired route 要不要 poison
- link down 时要不要 poison
- 发给原 next hop 时是否 poison reverse
官网明确提醒 Stage 10 会明显更长。
如果你前面把 send_routes 和 handle_route_advertisement 写得很乱,Stage 10A 的 incremental update 基本会重构一遍。
所以前面就建议你:
- 尽量抽 helper method
- 给每个 port 维护 advertisement history
Stage 10B 会要求:
- link up 时只给新邻居同步
send_routes(single_port=...)只发给一个 port
如果你还按「无脑广播到所有 port」写,就会挂。
这个项目的本地测试条件其实是最完整的。
我比较推荐的做法是:
- 先跑当前 stage 对应的
dv_unit_tests.py - 当前 stage 过了以后,再开 simulator
- 一边看可视化,一边在终端打印 routing table
这样你既能看见测试结果,也能看见协议行为。
常见调试手段:
python3 simulator.py --start --default-switch-type=dv_router topos.simple
python3 simulator.py --start --default-switch-type=dv_router topos.candy然后在终端里直接看:
print(s1.table)
print(s2.table)这个项目不要只跑单元测试,也要看可视化和路由表变化。
proj3 的目标是补完一个简化版 TCP socket。
你要实现的核心能力包括:
- 三次握手
- 按序接收
- 乱序接收
- 发送数据
- 处理 ACK
- 处理窗口
- 连接关闭
- 超时重传
- RTT / RTO 更新
官方说明里给出的 setup 要点:
- 操作系统:项目测试环境是 Linux 和 Mac
- 测试版本是
Python 3.7 - 所有 Python 命令建议在
cs168-sp25-proj3-transport/ext/cs168p2目录下运行 - 只改
student_socket.py
这里需要额外注意两个官方运行环境细节:
- 第一次通常要先给
../../pox.py执行权限 - 所有测试命令都应该从
ext/cs168p2这个目录发起
官方 setup 示例里有:
chmod +x ../../pox.py
python ../../pox.py config=tests/sanity_test.cfg我自己会统一改成:
chmod +x ../../pox.py
python3 ../../pox.py config=tests/sanity_test.cfg先把这条 sanity test 跑通,再开始写 stage,效率会高很多。
这个项目很不适合「代码先写一半,再回头补环境」。
正确顺序应该是:
- 先进入官方要求的工作目录
- 先跑 sanity test
- 确认
pox.py、autograder.py、tests/都能正常工作 - 再开始按 stage 写
student_socket.py
Project 3 最适合严格按 Stage 走。
推荐顺序:
- Stage 1: 三次握手
- Stage 2: 按序收包
- Stage 3: 乱序收包
- Stage 4: 发包与 ACK
- Stage 5: advertised window
- 跑通
all 5后提交3A - Stage 6: passive close
- Stage 7: active close
- Stage 8: retransmission
- Stage 9: RTT / RTO
常用命令可以写成:
python3 autograder.py s1
python3 autograder.py s2
python3 autograder.py s5
python3 autograder.py all 5
python3 autograder.py all单独调失败测试时,官网推荐的做法也很重要:
- 从失败输出里复制那条
../../pox.py ...命令 - 去掉最后的
--filename=... - 手动运行它看更直观的输出
官网专门给了 modular arithmetic 运算符。
涉及这些字段时,不要随手用普通大小比较:
seqacksnd.nxtsnd.unarcv.nxt
特别是 wraparound 测试里,普通比较很容易炸。
payload 长度不是唯一要占 sequence space 的东西。
SYN占 1FIN占 1
这个错会连锁影响:
- 下一次发送序号
- ACK 判定
- 状态迁移
Stage 3 不是「谁先到就先交付给应用层」。
正确思路是:
- 乱序包先进 queue
- 只有等它变成当前
rcv.nxt时才能真正处理 - 每处理完一个包,再继续看后面是否已经连续
很多测试挂掉不是因为主逻辑完全错,而是 ACK 时机错。
你要特别盯住:
- 收到按序 payload 后有没有 ACK
- 收到乱序包后有没有请求缺失数据
- 收到 FIN 后有没有 ACK
不是「有数据就发」,而是「窗口允许多少就发多少」。
maybe_send() 至少要同时考虑:
- 发送窗口剩余空间
self.tx_data剩余数据- 单个 segment 不超过
mss
Stage 9 里这个点特别容易出错。
如果一个包已经重传过,再拿它当正常样本更新 RTT,RTO 就会越来越不靠谱。
这个项目和 proj2 一样,也非常适合本地测试。
我更建议这样调:
- 先只跑当前 stage 的测试
- 如果失败,就把失败用例单独拎出来
- 手动执行那条
pox.py config=...命令 - 再回到
student_socket.py对应函数里查
不要一上来就直接跑 all,因为错误会很多,但不一定容易定位。