CS144-Lab5-ARP
lab 地址 :lab5-doc
代码实现:lab5-code
完整目录:
0. ByteStream
1. StreamReassembler
2. TCPReceiver
3. TCPSender
4. TCPConnection
5. ARP
6. IP-Router
1. 目标
lab0 ~ lab4 已经实现了一个基本的 TCPConnection,支持可靠的字节流,在 lab4 中官方已经实现了网络层协议数据的封装 IPV4Datagram
。
因此接下来我们主要需要关注的就是从 网络层 -> 数据链路层 中:
- 发包时,IP 数据报 到
EthernetFrame
的转换 - 收到
EthernetFrame
的处理
在处理收发 Ethernet frames
时主要涉及到 ARP 协议,什么是 ARP 协议?
地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
我们发送 以太网帧 时,当不知道目标地址的 ip 地址时,需要通过局域网广播 arp 协议去发现目标主机的地址,并且进行一段时间的缓存方便后续查询。
对比上个 lab,新增了 network_interface
类,需要实现的核心接口如下:
// 发送数据报,转成以太网帧 void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop) // 接收以太网帧 optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) void tick(const size_t ms_since_last_tick);
[!note]
这里需要注意的是 ARP 协议也是网络层协议,发送 ARP 协议时也是封装在EthernetFrame
的payload
中的。
2. 实现
接下来规整下我们的实现,其实主要根据 lab 中的描述实现对应接口即可。
2.1 send_datagram
接口的参数主要有 2 个 ,const InternetDatagram &dgram
和 Address& next_hop
,dgram 是要发送的数据包,而 next_hop 是目标地址,处理点主要如下:
- 地址缓存机制,主要用于检查目标地址 next_hop 是否已经缓存(新增一个地址缓存映射)
- 已缓存,将数据包转换成
EthernetFrame
,push 到frames_out
队列中 - 未缓存,广播局域网 ARP 消息(其实就是 push 一个
EthernetFrame
,但是 payload 是ARPMessage
结构序列化的 buffer)
- 已缓存,将数据包转换成
- 由于 1 引出 2,缓存的有效时间为 30s,如果 30s 内与目标主机无任何通信,则缓存失效,反之更新缓存保存时长
- 对相同地址发送 ARP 广播查询,需要间隔 5s
- 当前准备发出的请求,如果由于未知地址没有发出,需要先缓存到队列中(新增一个请求队列)
- 由于 4 引出 5,这里有可能一直没有查询到目标地址(超时,地址不可达),但是 lab 中不需要考虑(课程 test case 跳过这种情况)。当查询到地址时,会收到一个 ARP Reply (
recv_frames
接口中处理),重新检查请求队列
代码如下:
//! \param[in] dgram the IPv4 datagram to be sent //! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but may also be another host if directly connected to the same network as the destination) //! (Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) with the Address::ipv4_numeric() method.) void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop) { // convert IP address of next hop to raw 32-bit representation (used in ARP header) const uint32_t next_hop_ip = next_hop.ipv4_numeric(); // find and send ip datagram EthernetFrame frame; auto& address = _learn_address[next_hop_ip]; if (address.is_valid) { _send_datagram(dgram, address.learn_address); } // send arp message request else { /* don’t want to flood the network with ARP requests. If the network interface already sent an ARP request about the same IP address in the last five seconds, don’t send a second request—just wait for a reply to the first one. Again, queue the datagram until you learn the destination Ethernet address. */ if (!address.can_query()) { address.datagrams.push(dgram); return; } // can not query again in 5s, reset query pass time address.query_pass_time -= LearnAddress::QUERY_VAILD_TIMEOUT; // setup header EthernetHeader& header = frame.header(); header.dst = ETHERNET_BROADCAST; header.src = _ethernet_address; header.type = EthernetHeader::TYPE_ARP; // setup arp payload ARPMessage arp; arp.opcode = ARPMessage::OPCODE_REQUEST; arp.sender_ethernet_address = _ethernet_address; arp.sender_ip_address = _ip_address.ipv4_numeric(); arp.target_ip_address = next_hop_ip; frame.payload() = arp.serialize(); // cache datagram address.datagrams.push(dgram); _frames_out.push(frame); } }
2.2 recv_frame
recv_frame
的接口参数只有一个 EthernetFrame& frame
,以太网帧,recv_frame
要做的事情比较简单:
- 如果 frame 是 ipv4 数据包,解析成
InternetDatagram
类型- 如果数据包的目标 ip 不是自己的,返回 {},反之返回解析成功的数据包
- 如果 frame 是 arp 数据包
- 解析成
ARPMessage
,并且保存发送端的 ip 地址和 mac 地址映射(如果已经保存则重置过期时间) - 如果 ARP 请求的是自身的 ip,发送一个 ARP Reply
- 检查可以发送的 请求队列
- 解析成
//! \param[in] frame the incoming Ethernet frame optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) { uint32_t local_ip = _ip_address.ipv4_numeric(); if (frame.header().type == EthernetHeader::TYPE_IPv4) { InternetDatagram datagram; ParseResult result = datagram.parse(frame.payload()); if (result != ParseResult::NoError || frame.header().dst != _ethernet_address) { return {}; } return datagram; } if (frame.header().type == EthernetHeader::TYPE_ARP) { ARPMessage arp; ParseResult result = arp.parse(frame.payload()); if (result != ParseResult::NoError) { return {}; } /* If the inbound frame is ARP, parse the payload as an ARPMessage and, if successful, remember the mapping between the sender’s IP address and Ethernet address for 30 seconds. */ auto& address = _learn_address[arp.sender_ip_address]; address.is_valid = true; address.learn_address = arp.sender_ethernet_address; address.learn_pass_time = 0; address.query_pass_time = LearnAddress::QUERY_VAILD_TIMEOUT; // received arp request, reply host ip and mac if (arp.opcode == ARPMessage::OPCODE_REQUEST && arp.target_ip_address == local_ip) { // setup header EthernetFrame reply_frame; EthernetHeader& header = reply_frame.header(); header.dst = frame.header().src; header.src = _ethernet_address; header.type = EthernetHeader::TYPE_ARP; // setup arp payload ARPMessage reply_arp; reply_arp.opcode = ARPMessage::OPCODE_REPLY; reply_arp.sender_ethernet_address = _ethernet_address; reply_arp.sender_ip_address = _ip_address.ipv4_numeric(); reply_arp.target_ip_address = arp.sender_ip_address; reply_arp.target_ethernet_address = arp.sender_ethernet_address; reply_frame.payload() = reply_arp.serialize(); // send it _frames_out.push(reply_frame); } // received arp reply if (arp.opcode == ARPMessage::OPCODE_REPLY) { // re-sent datagrams while (!address.datagrams.empty()) { InternetDatagram& dgram = address.datagrams.front(); _send_datagram(dgram, address.learn_address); address.datagrams.pop(); } } } return {}; }
2.3 tick
tick 函数要处理时间相关的逻辑,根据前面引出的要处理的点,主要就是更新地址缓存保留时间,
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method void NetworkInterface::tick(const size_t ms_since_last_tick) { for (auto& address : _learn_address) { LearnAddress& learn_address = address.second; learn_address.tick(ms_since_last_tick); if (learn_address.learn_timeout()) { learn_address.reset(); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本