CS144 Lab 5
Lab Checkpoint 5: down the stack (the network interface)
注意,这篇笔记My implementation的后半部分全程代码剧透!
Overview
In this lab, you'll go down the stack and implement the network interface: the bridge between Internet datagrams that travel the world, and link-layer Ethernet frames that travel one hop.
The figure below shows that the network interface we are going to implement is part of a host's TCP/IP protocol stack and also a part of an IP router.
What's the responsibility of the network interface?
When an IP datagram is handed to the kernel, the kernel needs to construct an appropriate link-layer (Ethernet) frame with the IP datagram as its payload. This means the kernel needs to know its (IP address, Ethernet address) mapping. These functions are performed by the network interface.
Most of the work you will do is to implement the Address Resolution Protocol(ARP), which looks up (and cache) the Ethernet address for each next-hop IP address.
In lab6, you will use this network interface as part of an IP router.
My implementation
前言:coding前一定要认真读spec,将逻辑细节清楚后再写code。这个lab的代码量会相对较大,一定要多写helper functions!否则函数内部实现就会很臃肿。spec将函数逻辑讲解得很清晰,而且不像lab2和lab3那样有那么多corner case,所以难度适中。如果test没有通过,那一定是你在函数的逻辑细节问题上疏漏了。
这个lab需要我们实现网络层(IP)和链路层(Ethernet)之间的网络接口(Network Interface),负责:
- 将从host/router上层(IP)收到的network datagram包装成Ethernet frame发送给上层IP协议指定的下一个host(next hop)。可能需要发送ARP request并将datagram暂放在waiting area中等待发送(详见ARP协议)
- 接收外来的Ethernet frame,解析出payload,并根据其类型决定下一步操作:
- 如果是IPv4 datagram, 则将其存入buffer中供上层协议读取
- 如果是ARP message,则记录下sender's IP-to-Ethernet mapping,如果是ARP request for自己的Ethernet地址,那就回复相应的ARP reply
如spec所说,这个Network Interface的关键在于实现ARP协议:
这个Network Interface需要一个结构来缓存所有next hop的IP-to-Ethernet mapping。当需要发送一个network datagram的时候,如果next hop的Ethernet地址未知,就需要broadcast一个ARP request,并将这个datagram放入一个waiting area等待接收到ARP reply后再发送出去
当Network Interface接收到一个ARP message的时候,需要将sender's IP-to-Ethernet mapping缓存下来。这时候需要检查waiting area中是否有正在等待这个mapping的datagram,如果有的话现在就可以发送这个datagram了。如果是一个ARP request,并且请求的还是自己的Ethernet地址,那么还需要将相应的ARP reply发送给sender
需要注意的点:
next hop的IP-to-Ethernet mapping是会随着设备的接入和离开随时变化的,因此每个缓存的IP-to-Ethernet mapping每隔一段时间(lab规定为30秒)会过期,需要清除。
在发送一个ARP request之后,5秒内(lab规定的)不应再发送针对相同IP地址的ARP request,但是仍需要将datagram放入waiting area中。
datagram不会永远等待在waiting area中,如果被放入waiting area后的5秒内(lab规定的)还没接收到等待的Ethernet地址,那么我们的Network Interface就会drop掉这个datagram。
接下来进入我的实现细节(后面将是全程代码剧透!):
首先要确定用什么数据结构来表示:
- 所有缓存的IP-to-Ethernet mappings
- 所有在waiting area中的datagrams
考虑到清除IP-to-Ethernet mappings操作是清除那些早于当前时间30秒的所有mappings,因此我决定使用可以根据mapping插入的时间顺序(时间戳)来排序所有mappings的数据结构,以提高清除操作的速度。我选择使用一个multimap,将mapping插入的时间(时间戳)作为key,IP-to-Ethernet mapping pair作为value。用multimap而不是map是因为不同mapping pair的时间戳可能相同。
1 |
|
同样的道理,在waiting area中等待过期的datagrams也需要定期清除(每次调用tick
时),因此我也用一个multimap存储queued datagram。注意queued datagram需要包含datagram本身,以及其需要去往的next hop的ip地址。
注意不要将datagram_queued_
与datagram_received_
搞混淆了,前者是需要等待ARP request收到回应后才能发送出去的pending(queued) datagrams,而datagram_received_是Network Interface从外部接收并解析出的IPv4 datagrams,暂存在Network Interface中供上层协议读取的。
1 |
|
send_datagram
method还需要一个结构来存储针对每一个ip地址发送的ARF request的上一次发送时间,以此来检查是否可以发送当前的ARP request,这里我选择使用unordered_map
,ip地址作为key,时间戳作为value。因为访问该结构的时候是通过ip地址寻找的,无法进行顺序查找,因此不需要排序,用hash table存储访问效率较高。
1 |
|
确定好所有的数据结构后,就可以开始尝试实现Network Interface提供的3个methods,实现的过程中尽量将具体的逻辑细节封装在helper functions中:
tick
tick
是最容易实现的,首先更新当前时间,然后就是根据更新后的时间将过期的IP-to-Ethernet mappings和queued datagram清除。这里可以分别写两个helper function来实现:
1 |
|
1 |
|
这两个helper function虽然是处理不同的数据结构,但是逻辑很相似,都是从multimap的开头开始顺序清除,直到遇到第一个没有过期的元素后停止。
1 |
|
send_datagram
分两种情况:
- 已知next hop的Ethernet address(缓存在Network Interface中了)
从IP-to-Ethernet中获取需要的mapping,创建并设置Ethernet frame,frame header中的dst field设为获取的mapping中的Ethernet address。将frame的payload设为serialized后的datagram。设置完成后传输该Ethernet frame
根据next hop的ip地址获取mapping缓存,并将获知的Ethernet地址写入frame header的过程可以写一个helper function:
1 |
|
在send_datagram
中:
1 |
|
- 未知next hop的Ethernet address(不在缓存中)
需要发送一个请求next hop的ip地址的ARP request(如果5秒内没发送过请求该ip地址的ARP request),并将datagram放入waiting area中。
我用一个helper function来创建ARP message:
1 |
|
注意,send_datagram
(可能)需要的ARP request message和recv_frame
(可能)需要的ARP reply message都是用上面这个函数创建。创建request和reply message使用的参数模板都是一样的,不同的是:
opcode
不同request message的
target_ethernet_address
是{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
,而reply message的是要回复对象的Ethernet地址request message的
target_ip_address
是请求的ip地址,reply message的是要回复对象的ip地址request message的frame header中的
dst
是Ethernet Broadcast地址,而reply message的就是要回复对象的Ethernet地址
接下来,我用一个helper function来检查是否可以发送ARP request,再用一个来更新ARP request发送时间:
1 |
|
在send_datagram
中:
1 |
|
recv_datagram
首先判断接收到的Ethernet frame是不是发送给自己的,如果不是的话直接ignore:
1 |
|
后续处理该frame也分两种情况:
- 如果接收到的是IPv4类型的frame,将payload解析为network datagram,如果解析成功就将其放入
datagram_received_
buffer中供上层协议读取
1 |
|
-
如果接收到的是ARP类型的frame,将payload解析为ARP message,如果解析成功就将sender的IP-to-Ethernet mapping写入缓存中。并且,如果是ARP request,那么还需要回复相应的ARP reply。
最后,我们需要遍历
datagram_queued_
检查刚接收到的IP-to-Ethernet mapping是否可以让我们发送等待在waiting area中的pending(queued) datagrams。
写入IP-to-Ethernet mapping缓存的过程可以用一个helper function:
1 |
|
创建并设置ARP reply使用之前写的create_arp_message
function。
在recv_datagram
中:
1 |
|
Finish!
All tests in check5 pass!