TOC
Open TOC
Info
https://zhuanlan.zhihu.com/p/382380361
QQ Group 485077457
Lab Checkpoint 5: down the stack (the network interface)
Overview
TCP-in-UDP-in-IP
Linux provides an interface (a “datagram socket”, UDPSocket) that lets applications supply only the payload of a user datagram and the target address, and the kernel takes care of constructing the UDP header, IP header, and Ethernet header, then sending the packet to the appropriate next hop.
TCP-in-IP
Linux provides an interface, called a TUN device, that lets application supply an entire Internet datagram, and the kernel takes care of the rest (writing the Ethernet header, and actually sending via the physical Ethernet card, etc.).
分析一下 Socket 类
注意
一些适配器
注意 TCPOverIPv4Adapter
类
而 IPv4Datagram
类如下
将 TCPSegment 包装成 InternetDatagram,再交由 TUN 处理
TCP-in-IP-in-Ethernet
Each time your code writes an IP datagram to the TUN device, Linux has to construct an appropriate link-layer (Ethernet) frame with the IP datagram as its payload.
Your code will produce raw Ethernet frames, which will be handed over to Linux through an interface called a TAP device—similar to a TUN device, but more low-level, in that it exchanges raw link-layer frames instead of IP datagrams.
Most of the work will be in looking up (and caching) the Ethernet address for each next-hop IP address. The protocol for this is called the Address Resolution Protocol, or ARP.
The Address Resolution Protocol
这个实验的主要内容就是实现 ARP 协议
实现 NetworkInterface
类的三个方法
内部的数据结构实现如下
send_datagram
看 next_hop 的 IP 地址是否已经被缓存
若已被缓存,则将 InternetDatagram 包装成 EthernetFrame,并直接发送
注意在网络层的 EthernetFrame 的头部只有 ethernet address 信息,没有 IP 地址信息
若未被缓存,则查 _arp_requests
表,若在 5s 内未进行广播,则构造 ARPMessage 并包装成 EthernetFrame,然后广播
注意 ARPMessage 所包含的信息
只有 target_ethernet_address 为空
make_arp_helper 是从测试框架里面抄过来的,不过改了一下参数
另外,InternetDatagram 和 ARPMessage 都有类似的 parse 方法和 serialize 方法
BufferList 可以转换为 Buffer
也可以通过 Buffer 或 string 构造 BufferList
recv_frame
比较复杂的一个方法
根据接收到的 EthernetFrame 的类型进行操作
若为 IPv4,则判断 EthernetFrame 的目的地址是否与本机地址一致
- 若一致,则 parse 出 InternetDatagram 并返回
- 否则忽略
若为 ARP,则 parse 出 ARPMessage
首先判断 message 的目的 IP 地址是否与本机 IP 地址一致
若一致,则判断 ARPMessage 的类型,否则忽略
- 若为 REPLY
先 learn mapping
注意 mapping 的时间是 30s
需要使用
make_pair
进行emplace
,使用{}
构造编译失败
断言 mapping 是不存在的,但是 arp_requests 可能已经因为超时而 erase 了,所以不必断言
然后 resend,断言一定有 unsent 的数据包
- 若为 REQUEST
先 reply,注意 ARPMessage 所包含的信息
然后尝试 learn mapping
然后尝试 resend
tick
主要就是更新 _ip_to_ethernet
和 _arp_requests
里面的 timeout
由于仿照 https://zh.cppreference.com/w/cpp/container/map/erase 里面写会编译失败
没有匹配的 erase 方法,很奇怪
于是创建临时的 map,最后更新成员变量为临时的 map 即可
Test
framework
与 TCP 的实现无关
框架如下
- Action
- SendDatagram - send_datagram
- ReceiveFrame - recv_frame
- Tick - tick
- Expectation
- ExpectFrame - frames_out
webget revisited
将 CS144TCPSocket
替换为 FullStackSocket
如此一下,webget.cc
的实现依赖于
- on top of your
TCPConnection
implementation of TCP - on top of the TCP-in-IP code in
tcp_helpers/tcp_over_ip.cc
- on top of your
NetworkInterface
然后测试
第一次 check 可能会超时
想看看自己的头像,然而显示 301 Moved Permanently,使用 Linux 内核的 TCPSocket 也是这样
Lab Checkpoint 6: building an IP router
Router
模拟的路由器如下
具体参见 apps/network_simulator.cc
中 Network
类的构造函数
而 Router
类 add_route
打印的信息如下
Test
测试框架 apps/network_simulator.cc
模拟了上述路由器
在函数 network_simulator
中构造 Network
对象
每个用例通过 Host
类的 send_to
函数构造 InternetDatagram
并调用目的主机的 expect
方法
之后调用 Network
的 simulate
方法
在每一次中,路由器尝试转发所有的网络接口中产生的数据包
然后 Network
通过 simulate_physical_connections
模拟底层的帧交换
实际上就是多个网络接口之间接受彼此传送的以太网帧
Note
实现 Router 类的两个方法
add_route
填表
使用如下的数据结构
注意这里的比较函数,保证了前缀长的在前
route_one_datagram
查表
若数据包的 TTL ≤ 1
,则直接丢弃
然后按顺序查表
关键在于构造 mask
然后判断是否匹配
最后调用相应网络接口的 send_datagram
函数即可
- 若 next_hop 为空,则为 direct,next_hop 就是目的主机的 IP 地址
- 否则就是表中的 next_hop
别忘了将 TTL 减去一
AsyncNetworkInterface
上述的网络接口均是异步的网络接口
继承了上一个实验中实现的 NetworkInterface
其中添加了 recv_frame 函数,注意这不是虚函数,不是 override
并且提供了外界访问数据包的接口,即所谓异步的含义
不是通过
NetworkInterface::recv_frame
的返回值同步得到数据包
Format
在调试中尝试打印路由器匹配的信息
然而对于零无法显示出十六进制的前导 0x
修改 etc/cflags.cmake
支持 C++20
麻了,编译器尚未支持
https://godbolt.org/z/1EvnnrrcE
Final checkpoint: putting it all together
本质上是一个 bonus checkpoint
没有 partner,只能一人分饰两角了
Assertion
Debug mode 下删去一些 assertions
NetworkInterface::recv_frame
最后的 assert(0);
TCPConnection::segment_received
里面的 assert(inbound_stream().input_ended());
The Network
有点类似 Lab4 里面的 wireshark 测试
不过自己实现的协议栈多了一层
basic conversation
使用已经写好的 apps/lab7
,在 build 文件夹下键入
输出如下
首先打开 server
可以看到 server router 的三个网络接口
然后打开 client
可以看到 client router 的三个网络接口
server 回显
server 按下 <C-d>
client 回显
client 按下 <C-d>
server 回显
这一部分和 Lab4 中一致
当然也可以在最后添加参数 debug,进一步显示所有传送的以太网帧
操作与上述一致,不再具体分析
server
client
sending a file
首先生成一个随机文件
键入如下命令
注意使用
</dev/null
表示 client 无输入
对比文件差异
文件传输成功
当文件过大时,可能触发 assertions 或报错
不知道是网络问题还是实现的 robustness 不太行……
Driver
下面分析一下 apps/lab7.cc
这个 socket 地位上类似 TCPOverIPv4SpongeSocket
或 TCPOverIPv4OverEthernetSpongeSocket
main
函数会调用
回顾
即调用
下面分析 program_body
通过传入的参数构造 Address
然后根据是 client 还是 server 填充 router
local socket 类型为 TCPSocketLab7,也就是上面的类
remote socket 类型为 UDPSocket,包装了 Linux 内核提供的服务
对于 client 和 server 而言都是如此
下面创建线程,其中使用了 EventLoop 类,细节不表,主要添加如下四条规则
- Frames from host to router
- Frames from router to host
- Frames from router to Internet
- Frames from Internet to router
然后进入死循环
注意这里现实中的时间和 tick 函数模拟的时间完美的同步
wait_next_event
中使用了 poll 系统调用,已经麻了
之后主线程试图连接 client 和 server
bidirectional_stream_copy
的目的为 copy socket input/output to stdin/stdout until finished
也就是将 socket 和终端的标准输入输出通信