当前位置: 首页 > news >正文

CS144 Lab5 实战记录:网络接口实现 ARP

文章目录

    • 1 实验背景与目标
    • 2 模块流程
    • 3 网络接口实现
      • 3.1 数据结构
      • 3.2 send_datagram:转发 IP 数据报
      • 3.3 recv_frame:接收 Ethernet 帧
      • 3.4 tick:时间驻留管理
    • 4 DEBUG记录
    • 5 仓库地址

1 实验背景与目标

在这一次的 CS144 实验中,我们需要实现的是协调 IP 分级和链路层之间的连接部分:网络接口 Network Interface,它能将 IP 数据报包装裱成 Ethernet 带有地址的链路层帧,并转发到 TAP 设备。

它是 TCP/IP 网络堆栈的最下层部分,也是后续 Lab6 实现路由器功能的重要基础。

2 模块流程

网络接口的运行流程基于 ARP 协议,通过 ARP 请求/响应 来查询 IP-网卡 MAC 地址是否可达,并记录在本地缓存中。

TCP 层 InternetDatagram NetworkInterface EthernetFrame 物理链路 "LINK" 初始化 以太网地址和IP地址 交互 TCP段 传送 InternetDatagram 查找 ARP 缓存 打包成 EthernetFrame src=本机MAC, dst=目标MAC 发送数据包 保存 datagram 到待发送队列 检查是否已有该IP的待发送请求 等待先前ARP请求响应 构造ARP请求 目标MAC=广播地址 发送ARP请求 alt [已有待处理的ARP请求] [无待处理请求] alt [找到目标MAC地址] [未找到目标MAC地址] 接收数据帧 传递接收的帧 更新ARP缓存 检查待发送队列 发送队列中该IP的数据报 loop [遍历待发送队列] 本机是被查询目标 构造ARP响应 发送ARP响应 传递接收的IP数据报 alt [接收ARP响应] [接收ARP请求且目标是本机] [接收IPv4数据包且目标是本机] 定期清理过期ARP 缓存和请求 TCP 层 InternetDatagram NetworkInterface EthernetFrame 物理链路 "LINK"

整个工作流程可分为以下几个关键部分:

  1. 发送数据报流程:当上层协议需要发送数据时,先查询ARP缓存。
    • 若找到目标MAC地址,直接封装成以太网帧发送
    • 若未找到,保存数据报并发送ARP请求
  2. 接收数据帧流程:接收以太网帧并检查目标MAC是否为本机或广播。处理ARP请求/响应,更新本地ARP缓存,处理IPv4数据包,解析后上传到网络层。
  3. 定时维护流程:定期检查ARP缓存条目过期情况,清理超时未响应的ARP请求和待发送数据。

3 网络接口实现

3.1 数据结构

网络接口的实现核心在于ARP(地址解析协议)的管理,它将逻辑IP地址映射到物理MAC地址。我们使用以下数据结构来支持这一功能。

  1. ARP缓存管理:使用unordered_map实现IP到MAC地址的高效查询(O(1)平均查找时间),每个条目包含MAC地址和存活时间计时器,遵循RFC建议的30秒TTL,缓存设计有助于减少网络请求,提高性能。
  2. 待处理数据报管理pending_datagrams_存储等待ARP回复的IP数据报列表。使用IP地址作为键,支持一次ARP回复后批量处理多个等待的数据报。
  3. ARP请求限流机制pending_datagram_timers_跟踪ARP请求的发送时间,防止短时间内重复发送ARP请求,减轻网络广播风暴,符合RFC826推荐的5秒重传间隔
// ARP entry validity period and retransmission interval (30s / 5s recommended by RFC826)
static constexpr size_t ARP_ENTRY_TTL_ms      = 30'000;  // 30 s
static constexpr size_t ARP_REQUEST_PERIOD_ms = 5'000;   // 5 s
using Timer = uint64_t;
using AddressNumber = uint32_t;struct ArpEntry
{EthernetAddress ethernet_address;Timer timer;
};// ARP cache: maps IP addresses to Ethernet addresses
std::unordered_map<AddressNumber, ArpEntry> arp_cache_ {};
// Pending datagrams: maps IP addresses to datagrams waiting for ARP replies
std::unordered_map<AddressNumber, std::vector<InternetDatagram>> pending_datagrams_ {};
// Pending datagram timers: maps IP addresses to timers for retransmitting ARP requests
std::unordered_map<AddressNumber, Timer> pending_datagram_timers_ {};

3.2 send_datagram:转发 IP 数据报

当上层协议需要发送IP数据报时,网络接口需要完成IP地址到MAC地址的转换,并将数据报封装到以太网帧中,实现流程如下图所示:

开始
send_datagram(dgram, next_hop)
检查ARP缓存
该IP是否有映射?
获取对应的MAC地址
构造以太网帧
通过transmit()发送
结束
将数据报加入
pending_datagrams队列
是否已有
该IP的ARP请求?
等待已发出请求的响应
结束
创建ARP请求计时器
构造ARP请求消息
发送广播ARP请求
结束
void NetworkInterface::send_datagram( const InternetDatagram& dgram, const Address& next_hop )
{// Convert the Address to a numeric representation for easier lookupconst AddressNumber next_hop_ip = next_hop.ipv4_numeric();// Check if the next hop's Ethernet address is already in the ARP cacheauto arp_it = arp_cache_.find( next_hop_ip );if ( arp_it != arp_cache_.end() ) {// If found, get the Ethernet address and transmit the datagram immediatelyconst EthernetAddress& next_hop_ethernet_address = arp_it->second.ethernet_address;transmit( { { next_hop_ethernet_address, ethernet_address_, EthernetHeader::TYPE_IPv4 }, serialize( dgram ) } );return;}// If not found, queue the datagram for later transmissionpending_datagrams_[next_hop_ip].emplace_back( dgram );// Check if an ARP request for this IP is already pendingif ( pending_datagram_timers_.find( next_hop_ip )!= pending_datagram_timers_.end() ) {// If yes, do nothing; an ARP request is already in flightreturn;}// If no ARP request is pending, send one// Start a timer to track the ARP requestpending_datagram_timers_.emplace( next_hop_ip, Timer {} );// Construct the ARP request messageconst ARPMessage arp_request = {.opcode = ARPMessage::OPCODE_REQUEST,.sender_ethernet_address = ethernet_address_,.sender_ip_address = ip_address_.ipv4_numeric(),.target_ethernet_address = {}, // Target Ethernet address is unknown, hence left empty.target_ip_address = next_hop_ip};// Encapsulate the ARP request in an Ethernet frame and transmit it (broadcast)transmit({ { ETHERNET_BROADCAST, ethernet_address_, EthernetHeader::TYPE_ARP }, serialize(arp_request) });}

3.3 recv_frame:接收 Ethernet 帧

当网络接口收到以太网帧时,需要根据帧类型进行不同处理:

IPv4
ARP
开始
recv_frame(frame)
帧目标地址
是本机或广播?
忽略帧
结束
检查帧类型
解析为IP数据报
解析成功?
忽略帧
结束
加入接收队列
结束
解析为ARP消息
解析成功?
忽略帧
结束
学习/更新ARP映射
(sender_ip -> sender_eth)
是否是
针对本机的ARP请求?
构造ARP响应
发送ARP响应给请求者
是否有待该IP地址
的待发送数据报?
结束
发送所有待处理的数据报
清理待发送队列和计时器
结束
void NetworkInterface::recv_frame( EthernetFrame frame )
{// Ignore frames not destined for this interface's Ethernet address or the broadcast addressif ( frame.header.dst != ethernet_address_ && frame.header.dst != ETHERNET_BROADCAST ) {return;}// Check the Ethernet frame typeif ( frame.header.type == EthernetHeader::TYPE_IPv4 ) {// If it's an IPv4 datagram, try to parse itInternetDatagram dgram;if ( parse( dgram, frame.payload ) ) {// If parsing is successful, push the datagram onto the received queuedatagrams_received_.push( move( dgram ) );}return; // Processing finished for IPv4 frames}if ( frame.header.type == EthernetHeader::TYPE_ARP ) {// If it's an ARP message, try to parse itARPMessage msg;if ( !parse( msg, frame.payload ) ) {// If parsing fails, ignore the framereturn;}// Learn the mapping from the sender's IP and Ethernet addressesconst AddressNumber sender_ip = msg.sender_ip_address;const EthernetAddress sender_eth = msg.sender_ethernet_address;// Add or update the entry in the ARP cache, resetting its timerarp_cache_[sender_ip] = { sender_eth, 0 };// Check if this is an ARP request specifically for our IP addressif ( msg.opcode == ARPMessage::OPCODE_REQUEST && msg.target_ip_address == ip_address_.ipv4_numeric() ) {// If yes, construct an ARP replyARPMessage arp_reply = {.opcode = ARPMessage::OPCODE_REPLY,.sender_ethernet_address = ethernet_address_, // Our Ethernet address.sender_ip_address = ip_address_.ipv4_numeric(), // Our IP address.target_ethernet_address = sender_eth, // The original sender's Ethernet address.target_ip_address = sender_ip // The original sender's IP address};// Encapsulate the ARP reply in an Ethernet frame and transmit it back to the sendertransmit({ { sender_eth, ethernet_address_, EthernetHeader::TYPE_ARP }, serialize(arp_reply) });}// Check if we have any pending datagrams waiting for this sender's Ethernet addressauto it = pending_datagrams_.find(sender_ip);if ( it != pending_datagrams_.end() ) {// If yes, iterate through the pending datagramsfor ( const auto& dgram : it->second ) {// Transmit each pending datagram using the now-known Ethernet addresstransmit({ { sender_eth, ethernet_address_, EthernetHeader::TYPE_IPv4 }, serialize(dgram) });}// Remove the entry from the pending datagrams mappending_datagrams_.erase(it);// Remove the corresponding timer for the ARP requestpending_datagram_timers_.erase(sender_ip);}}
}

3.4 tick:时间驻留管理

网络接口需要定期处理超时事件,包括清理过期的ARP缓存条目和丢弃超时未响应的ARP请求。每过一段时间,需要扫描缓存,删除过期项:

  • 删除缓存中 30s 未更新的 IP-MAC 映射
  • 删除 5s 内未回复的 ARP 请求,并且不再发送
void NetworkInterface::tick(const size_t ms) {for (auto it = arp_cache_.begin(); it != arp_cache_.end(); ) {it->second.timer += ms;if (it->second.timer >= ARP_ENTRY_TTL_ms)it = arp_cache_.erase(it);else++it;}for (auto it = pending_datagram_timers_.begin(); it != pending_datagram_timers_.end(); ) {it->second += ms;if (it->second >= ARP_REQUEST_PERIOD_ms) {pending_datagrams_.erase(it->first);it = pending_datagram_timers_.erase(it);} else {++it;}}
}

4 DEBUG记录

在测试过程中,没有通过检测项:Pending datagrams dropped when pending request expires。具体错误如下:

image-20250424155534486

这个测试检查当ARP请求超时后,网络接口是否正确丢弃相关的待发送数据报。这是因为 tick() 中忘记在删除超时的ARP请求计时器时同时删除对应的待发送数据报。实现时确保两者同步清理。

5 仓库地址

项目代码已上传至GitHub:https://github.com/HeZephyr/minnow

相关文章:

  • Spring Boot Controller 单元测试撰写
  • Git删除指定历史版本
  • 快速配置linux远程开发-go语言
  • Docker部署DeepSeek常见问题及解决方案
  • 实战交易策略 篇十九:君山居士熊市交易策略
  • 机器学习 Day14 XGboost(极端梯度提升树)算法
  • 得物业务参数配置中心架构综述
  • 大语言模型之提示词技巧
  • Tomcat:从零理解Java Web应用的“心脏”
  • 路由交换网络专题 | 第七章 | BGP练习 | 次优路径 | Route-Policy | BGP认证
  • Typecho 访客统计插件最新版-前后台统计图均可显示
  • 搭建私人网站
  • 香港国际视角下的资金路径识别与结构研判
  • 数理逻辑基础 | 命题逻辑 / 谓词逻辑 / 命题符号化
  • nodejs之Express-介绍、路由
  • Godot开发2D冒险游戏——第二节:主角光环整起来!
  • JDK 21 的新特性:探索 Java 的最新进化
  • ubantu中下载编译安装qt5.15.3
  • (51单片机)LCD展示动画(延时函数)(LLCD1602教程)
  • JVM(Java虚拟机)详解
  • 贵州赤水被指“整改复耕”存形式主义,当地部署耕地流出整改“回头看”
  • AI翻译技术已走向大规模商用,应用场景覆盖多个关键领域
  • 魔都眼·上海车展④|奔驰宝马保时捷……全球豪车扎堆首秀
  • 天问三号计划2028年前后发射实施,开放20千克质量资源
  • 外交部否认中美就关税问题进行磋商谈判
  • 乌代表团与美特使在伦敦举行会谈,双方同意继续对话