这里给出CS 144 Lab 5: down the stack (the network interface)的翻译。

实验资料:

实验5:底层(网络接口)

0. 合作政策

编程作业必须是你自己的工作:你必须编写你为编程作业提交的所有代码,但我们作为作业的一部分提供给你的代码除外。请不要复制和粘贴来自StackOverflow,GitHub或其他来源的代码。如果你的代码基于你在网上或其他地方找到的样例,请在提交的源代码中的注释中引用URL。

与他人合作:你不能把你的代码给别人看,也不能看别人的代码,更不能看往年的解决方案。你可以与其他学生讨论作业,但不要抄袭任何人的代码。如果你与其他学生讨论作业,请在你提交的源代码中的注释中列出他们的姓名。请参阅课程管理讲义了解更多详细信息,如果有任何不清楚的地方,请在Piazza上询问。

Piazza:请随时在Piazza上提问,但请不要发布任何源代码。

1. 概述

在本周的实验中,你将深入研究并实现一个网络接口:世界各地的互联网数据报和一跳一跳的链路层以太网帧之间的桥梁。该组件可以“隐藏”在早期实验的TCP/IP实现之下,但它也将用于不同的设置:当你在实验6中建立一个路由器时,它将在网络接口之间路由数据报。图1显示了网络接口如何适应这两种设置。

你对网络接口的实现将使用与你在实验0-4中使用的相同的Sponge库,并增加了类和测试。但是,应大众的要求,本实验的大部分(但不是全部)都可以在不依赖早期实验的TCP连接的情况下完成。

在过去的实验中,你写了一个TCP实现,可以成功地与使用TCP的任何其他计算机交换TCP段。这些网段实际上是如何传达给对等方的TCP实现的呢?正如我们所讨论的,有几种选择:

  • TCP-in-UDP-in-IP:TCP段可以在用户数据报的有效载荷中携带。在正常(用户空间)环境下工作时,这是最容易实现的。Linux提供了一个接口(“互联网数据报套接字”,UDPSocket),允许应用程序只提供用户数据报和目标地址的有效载荷,内核负责构造UDP报头、IP报头和以太网报头,然后将数据包发送到适当的下一跳。内核确保每个套接字具有本地和远程地址以及端口号的独占组合,并且由于内核是将这些地址和端口号写入UDP和IP头的内核,因此它可以保证不同应用程序之间的隔离。

  • TCP-in-IP:在通常情况下,TCP段几乎总是直接放在互联网数据报中,在IP和TCP报头之间没有UDP报头。这就是人们所说的”TCP/IP”。这在实现上要困难一些。Linux提供了一个称为TUN设备的接口,该接口允许应用程序提供整个Internet数据报,内核负责其余部分(编写以太网报头,并通过物理以太网卡实际发送,等等)。但是现在,应用程序必须自己构造完整的IP报头,而不仅仅是有效载荷。

    你已经做了这个。在实验4中,我们为你提供了一个表示Internet数据报的对象,它知道如何解析和序列化自身(tcp_helpers/ipv4_datagram.{hh,cc})以及在IP中封装TCP段的逻辑(现在可以在tcp_helpers/tcp_over_ip.cc中找到)。CS144TCPSocket使用这些工具将TCPConnection连接到TUN设备。

  • TCP-in-IP-in-Ethernet:在上述方法中,我们仍然依赖于Linux内核的部分网络栈。每次你的代码向TUN设备写入一个IP数据报时,Linux必须构建一个适当的链路层(以太网)帧,并将IP数据报作为其有效载荷。这意味着Linux必须根据下一跳的IP地址来计算出下一跳的以太网目标地址。如果它还不知道这个映射,Linux就会广播一个查询,问:”谁要求使用下面的IP地址?你的以太网地址是什么?”并等待回应。

    这些功能由网络接口执行:一个将出站IP数据报翻译成链路层(如以太网)帧的组件,反之亦然。(在实际系统中,网络接口通常有eth0eth1wlan0等名称。) 在本周的实验中,你将实现一个网络接口,并把它放在TCP/IP协议栈的最底层。你的代码将产生原始的以太网帧,这些帧将通过一个叫做TAP设备的接口交给Linux——类似于TUN设备,但更底层,因为它交换的是原始链路层帧而不是IP数据报。

大部分的工作是为每个下一跳的IP地址查找(和缓存)以太网地址。这方面的协议被称为地址解析协议(ARP)

我们已经为你提供了单元测试,使你的网络接口能够正常运行。然后,在本实验结束时,你将略微修改你的webget,以使用你的TCP实现,这样整个过程将生成原始以太网帧,并且仍然可以通过Internet与真正的Web服务器通信。在实验6中,你将在TCP的上下文之外使用同一个网络接口,作为IP路由器的一部分。

图1:网络接口连接互联网数据报和链路层帧。该组件作为主机TCP/IP堆栈的一部分(左侧)和IP路由器的一部分(右侧)都很有用。

2. 开始

  1. 请确保你已经提交了你在实验4中的所有解决方案。请不要修改libsponge目录顶层以外的任何文件,或者webget.cc。(请不要添加代码所依赖的额外文件。)否则,你可能会在合并实验5的启动代码时遇到麻烦。
  2. 在实验作业的存储库中,运行git fetch来检索实验作业的最新版本。
  3. 通过运行git merge origin/lab5-startercode,下载实验5的启动代码。
  4. build目录中,编译源代码:make(编译时可以运行make -j4以使用四个处理器)。
  5. build目录外,打开并开始编辑writeups/lab5.md文件。这是你实验报告的模板,将包含在你提交的内容中。

3. 地址解析协议

在开始编码之前,请阅读:

本实验的主要任务是实现NetworkInterface的三种主要方法(在network_interface.cc文件中),维护从IP地址到以太网地址的映射。映射是一个缓存,或“软状态”:NetworkInterface为了提高效率而保留它,但是如果它必须从头开始重新启动,映射将自然地重新生成,而不会引起问题。

  1. void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop)

    当调用者(如你的TCPConnection或路由器)希望将出站互联网(IP)数据报发送到下一个跃点时,将调用此方法。(请不要把数据报的最终目的地与下一跳混为一谈,后者在数据报自己的报头中是目标地址。在这个实验里,你只关心下一跳的地址。)该接口的工作是将此数据报转换为以太网帧并(最终)发送。

    • 如果目标以太网地址已知,请立即发送。创建以太网帧(type = EthernetHeader::TYPE_IPv4),将有效载荷设置为序列化数据报,并设置源地址和目标地址。
    • 如果目标以太网地址未知,广播下一跳以太网地址的ARP请求,并将IP数据报排队,以便在收到ARP回复后发送。

    例外:你不想让ARP请求充斥网络。如果网络接口在过去5秒内已经发送了一个关于相同IP地址的ARP请求,不要发送第二个,只需等待第一个请求的回复即可。同样,对数据报进行排队,直到了解到目标以太网地址。

  2. optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame)

    当以太网帧从网络到达时,调用此方法。代码应忽略任何不发送到网络接口的帧(也就是说,只接受以太网目的地是广播地址或存储在以太网地址成员变量_ethernet_address中的以太网地址)。

    • 如果入站帧是IPv4,将有效载荷解析为InternetDatagram,如果成功(意味着parse()方法返回ParseResult::NoError),则将生成的InternetDatagram返回给调用者。
    • 如果入站帧是ARP,将有效载荷解析为ARP消息,如果成功,记住发送方的IP地址和以太网地址之间的映射,持续30秒。(从请求和回复中学习映射。)此外,如果是ARP请求请求我们的IP地址,请发送适当的ARP回复。
  3. void NetworkInterface::tick(const size_t ms_since_last_tick)

    随着时间的推移,这将被调用。使任何已经过期的IP到以太网的映射过期。

你可以通过运行ctest -V -R "^arp"来测试你的实现。此测试不依赖于你的TCP实现。

4. webget回顾

还记得你在实验0中写的webget.cc吗(在TCPSocket中使用Linux提供的TCP实现)?还记得你在实验4中如何修改它,以便在CS144TCPSocket中使用你自己的TCP-in-IP实现吗?如上所述,这仍然依赖于Linux内核作为堆栈的一部分:在IP和链路层(以太网)之间转换的网络接口。

我们希望你在不更改任何其他内容的情况下将其切换为使用网络接口。你只需将CS144TCPSocket类型替换为FullStackSocket

这将使用TCP-in-IP-in-Ethernet堆栈,如图1(左侧)所示:你的webget.cc应用程序,在TCP的TCPConnection实现之上,在TCP-in-IP的tcp_helpers/tcp_over_ip.cc代码之上,在NetworkInterface之上。

重新编译并运行make check_lab5以确认你已经完成了完整的堆栈:你已经在自己完整的TCP实现和自己的网络接口实现之上编写了一个基本的网络抓取程序,并且它仍然成功地与真正的Web服务器通信。

如果遇到问题,请尝试手动运行该程序:./apps/webget cs144.keithw.org /hasher/xyzzy,并尝试使用wireshark捕获它发送和接收的内容。你可以通过运行sudo TCPdump -i tap10 -w /tmp/packets.tap来保存它正在发送和接收的分组。然后在wireshark中打开/tmp/packets.tap文件。

5. Q & A

  • 你期望有多少代码?
    总的来说,我们希望实现(在network_interface.cc中)总共需要大约100-150行代码。

  • 我如何申请提前提交的15%的额外学分?

    我们鼓励你在感恩节假期前完成实验(部分原因是为了给你留出足够的时间完成12月6日到期的实验6),但我们也希望为那些需要或想要额外时间的人提供灵活性。实验将于12月4日下午5点到期,但我们为提前提交的学生提供15%的奖励。要获取这个:

    1. 11月22日下午5点前提交。
    2. 11月22日下午5点前给工作人员名单(cs144-aut1920-staff@lists.stanford.edu)发邮件,说“我想在实验5上获得额外的学分。”
    3. 我们将在11月22日下午5点之前为你提交的最新材料进行评分。无论你的评分结果如何,我们都将乘以1.15。
    4. 如果你不想申请额外学分,请正常提交(包括使用任何延迟天数)。你不会因为拒绝加分选项而受到惩罚(例如,CS144不是 “按曲线计分”)。
  • NetworkInterface实际如何发送以太网帧?

    TCPSenderTCPConnection的工作原理类似。对于NetworkInterface,将任何出站帧push到_frames_out队列,对象的所有者将pop并发送该帧。

  • 我应该使用什么数据结构来记录下一跳IP地址和以太网地址之间的映射?

    由你决定!

  • 如何将地址对象形式的IP地址转换为原始32位整数,然后写入ARP消息?
    使用Address::ipv4_numeric()方法。

  • 如果NetworkInterface发送ARP请求但从未收到回复,我该怎么办?我应该在超时后重新发送吗?是否使用ICMP向原始发送方发送错误信号?

    在现实生活中,是的,这两件事都要做,但在这个实验里不用担心。(在现实生活中,如果接口无法获得对其ARP请求的回复,它最终会通过互联网将ICMP“主机不可访问”发送回原始发送方。)

  • 如果InternetDatagram排队等待了解下一跳的以太网地址,而该信息从未出现,我该怎么办?我应该在超时后删除数据报吗?

    同样,在实际情况中肯定是”是”,但在这个实验里不用担心这个问题。

  • 如何运行此实验的测试套件?

    make check_lab5(两个测试)。或者,你可以使用make check(160个测试)运行整个测试套件。

  • 如果这个PDF出来后还有更多的FAQ,我在哪里可以看到?

    请定期查看网站(https://cs144.github.io/lab_faq.html)和Piazza。

6. 提交

  1. 在你的提交中,请只对libsponge顶层的.hh.cc文件进行修改。在这些文件中,请随意添加必要的私有成员,但请不要改变任何类的公共接口。
  2. 请不要添加额外的文件,自动打分器不会查看这些文件,你的代码可能无法编译。
  3. 在提交任何作业之前,请按顺序运行这些。
    • (a) make format(使编码风格正常化)
    • (b) git status(检查是否有未提交的修改,如果有,请提交!)
    • (c) make(确保代码可以编译)
    • (d) make check_lab5(确保自动测试通过)
  4. writeups/lab5.md中写一份报告。这个文件应该是一个大约20到50行的文件,每行不超过80个字符,以使其更容易阅读。该报告应包含以下部分。
    • (a) 程序结构和设计。描述你的代码中所体现的高层次结构和设计选择。你不需要详细讨论你从启动代码中继承了什么。借此机会突出重要的设计方面,并在这些方面提供更多的细节,以便你的评分助教理解。我们强烈建议你通过使用小标题和提纲,使这篇报告尽可能可读。请不要简单地将你的程序翻译成一段英文。
    • (b) 实现方面的挑战。描述你认为最麻烦的代码部分,并解释原因。反思一下你是如何克服这些挑战的,以及是什么帮助你最终理解了给你带来麻烦的概念。你是如何试图确保你的代码维护你的假设、不变式和先决条件的,你在哪些方面发现这很容易或很困难?你是如何调试和测试你的代码的?
    • (c) 剩余的错误。尽量指出并解释代码中仍然存在的任何错误(或未处理的边缘情况)。
  5. 在你的报告中,请同时填写这项任务花了你多少时间以及任何其他评论。
  6. 当准备提交时,请按照https://cs144.github.io/submit。在提交之前,请确保你已经提交了所有你想要的东西。
  7. 如果有任何问题,请尽快在周二晚上的实验课上告诉课程组成员,或者在Piazza上发帖提问。祝你好运!