跳转至

建议的实验思路

推荐的实验流程是:

  1. 在 TanLabs 上创建仓库,克隆到本地,认真阅读文档
  2. 运行 Example 下面的程序,保证自己环境正确配置了
  3. 进行 Homework 的编写,完成四个编程作业
  4. 把上一步实现的几个函数和 HAL 配合使用,实现一个真实可用的路由器

建议采用的一些调试工具和方法:

  • Wireshark:无论是抓包还是查看评测用到的所有数据的格式,都是非常有用的,一定要学会
  • 编写测试的输入输出,这个仓库的 Datagen 目录下有一个用 Rust 编写的 PCAP 测试样例生成程序,你可以修改它以得到更适合你的代码的测试样例,利用 Wireshark 确认你构造的样例确实是合法的,但请不要放到作业仓库中影响评测结果
  • 运行一些成熟的软件,然后抓包看它们的输出是怎样的,特别是调试 RIP 协议的时候,可以自己用 BIRD(BIRD Internet Routing Daemon)跑 RIP 协议然后抓包,有条件的同学也可以自己找一台企业级的路由器进行配置(选计算机网络专题训练体验一下),当你的程序写好了也可以让你的路由器和它进行互通测试。当大家都和标准实现兼容的时候,大家之间兼容的机会就更高了。

关于第四步,一个可能的大概的框架如下:

int main() {
    // 0a. 初始化 HAL,打开调试信息
    HAL_Init(1, addrs);
    // 0b. 创建若干条 /24 直连路由
    for (int i = 0; i < N_IFACE_ON_BOARD;i++) {
        RoutingTableEntry entry = {
            .addr = addrs[i] & 0x00FFFFFF, // 大端
            .len = 24,
            .if_index = i,
            .nexthop = 0 // 表示直连路由
        };
        update(true, entry);
    }

    uint64_t last_time = 0;
    while (1) {
        // 获取当前时间,处理定时任务
        uint64_t time = HAL_GetTicks();
        if (time > last_time + 5 * 1000) {
            // 每 5s 做什么
            // 例如:超时?发 RIP Request/Response?
            last_time = time;
        }

        // 轮询
        int mask = (1 << N_IFACE_ON_BOARD) - 1;
        macaddr_t src_mac;
        macaddr_t dst_mac;
        int if_index;
        int res = HAL_ReceiveIPPacket(mask, packet, sizeof(packet), src_mac,
                                    dst_mac, 1000, &if_index); // 超时为 1s
        if (res > 0) {
            // 1. 检查是否是合法的 IP 包,可以用你编写的 validateIPChecksum 函数,还需要一些额外的检查
            // 2. 检查目的地址,如果是路由器自己的 IP(或者是 RIP 的组播地址),进入 3a;否则进入 3b
            // 3a.1 检查是否是合法的 RIP 包,可以用你编写的 disassemble 函数检查并从中提取出数据
            // 3a.2 如果是 Response 包,就调用你编写的 query 和 update 函数进行查询和更新,
            //      注意此时的 RoutingTableEntry 可能要添加新的字段(如metric、timestamp),
            //      如果有路由更新的情况,可能需要构造出 RipPacket 结构体,调用你编写的 assemble 函数,
            //      再把 IP 和 UDP 头补充在前面,通过 HAL_SendIPPacket 把它发到别的网口上
            // 3a.3 如果是 Request 包,就遍历本地的路由表,构造出一个 RipPacket 结构体,
            //      然后调用你编写的 assemble 函数,另外再把 IP 和 UDP 头补充在前面,
            //      通过 HAL_SendIPPacket 发回询问的网口
            // 3b.1 此时目的 IP 地址不是路由器本身,则调用你编写的 query 函数查询,
            //      如果查到目的地址,如果是直连路由(nexthop = 0 表示直连路由), nexthop 改为目的 IP 地址,
            //      用 HAL_ArpGetMacAddress 获取 nexthop 的 MAC 地址,如果找到了,
            //      就调用你编写的 forward 函数进行 TTL 和 Checksum 的更新,
            //      通过 HAL_SendIPPacket 发到指定的网口,
            //      在 TTL 减到 0 的时候要求构造一个 ICMP Time Exceeded 返回给发送者;
            //      如果没查到目的地址的路由,要求返回一个 ICMP Destination Network Unreachable;
            //      如果没查到下一跳的 MAC 地址,HAL 会自动发出 ARP 请求,在对方回复后,下次转发时就知道了。
        } else if (res == 0) {
            // 一段时间没有收到包,属于正常情况,忽略即可
        } else {
            fprintf(stderr, "Error: %d\n", res);
            break;
        }
    }
    return 0;
}

你需要基于 Homework/router 下的代码,把里面的代码实现完全。代码中在发送 RIP 包的时候,会涉及到 IP 头的构造,由于不需要用各种高级特性,可以这么设定:V=4,IHL=5,TOS(DSCP/ECN)=0,ID=0,FLAGS/OFF=0,TTL=1,其余按照要求实现即可。对于构造的 ICMP 包,可以设置 TTL=64 。

关于如何启动并配置一个比较标准的 RIP 实现,请参考 这个页面

关于如何在一台计算机上进行真实测试,请参考 这个页面 的第二部分。


最后更新: 2020年9月12日
作者: Harry Chen (82.14%), Jiajie Chen (17.86%)