1. 实验目的

  1. 熟悉ARP数据包格式,掌握ARP映射表的建立与更新过程;

  2. 掌握ARP数据包的的发送和接收处理过程。

2. 实验任务

在完成协议栈之eth协议的基础上,编写ARP报文的接收、发送和请求报文函数,使其能够发送和接收ARP数据报文,并且能通过实验评测系统的测试。

3. 实验原理

3.1. ARP协议概要

在TCP/IP的网络构造和网络通信中无需事先知道MAC地址究竟是什么,只要确定了IP地址,就可向这个目标地址发送IP数据报了。然而,在数据链路层使用的是硬件地址(MAC)进行报文传输,IP地址不能被物理网络所识别,因此必须建立IP地址和MAC地址的映射关系,这一过程称为ARP(Address Resolution Protocol)地址解析协议。

ARP协议以目标IP地址为线索,用来定位下一个应该接收数据包的网络设备对应的MAC地址。如果目标主机不在同一个链路上,可以通过ARP查找下一跳网关的MAC地址。注意,ARP只适用于IPv4,不能用于IPv6。IPv6可以用ICMPv6替代ARP发送邻居探索消息。

3.2. ARP的工作机制

那么ARP又是如何知道MAC地址的呢?简单地说,ARP是借助ARP请求与ARP响应两种类型的包确定MAC地址的。此外,在每台使用ARP的主机中,都保留了一个专用的内存区(称为缓存),存放最近的IP地址与硬件地址的对应关系。一旦收到ARP应答,主机将获得的IP地址和硬件地址的对应关系存到缓存中。当发送报文时,首先去缓存中查找相应的项,如果找到相应项,便将报文直接发送出去;如果找不到,再利用ARP进行解析。ARP 缓存信息在一定时间内有效,过期不更新就会被删除。

3.2.1. 同一网段的ARP解析过程

如下图所示,主机A需要发报文给主机B,如果在缓存中找不到相应的记录,就必须先解析主机B的硬件地址。主机A首先在网段内通过广播发出ARP请求报文,由于广播的包可以被同一个链路上所有的主机或路由器接收,因此ARP的请求包也就会被这同一个链路上所有的主机和路由器进行解析。主机B收到后,判断报文的目的IP是本主机的IP地址,便将本主机的硬件地址写入应答报文,发送给主机A,主机A收到后将其存入缓存中,则解析成功,然后才将报文发往主机B。

../_images/%E5%90%8C%E4%B8%80%E7%BD%91%E6%AE%B5ARP.png

3.2.2. 不同网段的ARP解析过程

如下图所示,主机A要发报文给主机C,首先主机A分析目的地址不在同一个网段,需要将报文先发给其默认网关,再由默认网关转发。如果没有找到默认网关的硬件地址,便发送ARP请求报文,请求默认网关的硬件地址,默认网关收到之后,将自己的硬件地址写入应答报文,发送给主机A。然后,主机A到主机C的报文首先被送到默认网关。默认网关再根据报文的目的IP地址进行转发,以此类推,直至报文送到主机C中。主机C到主机A的报文以相反的顺序发送。

../_images/%E4%B8%8D%E5%90%8C%E7%BD%91%E6%AE%B5ARP.png

注意

请思考,IP地址和MAC地址为什么缺一不可?

3.3. ARP协议报文格式

ARP是一个独立的三层协议,所以ARP报文在向数据链路层传输时不需要经过IP协议的封装,而是直接生成自己的报文,其中包括ARP报头,到数据链路层后再对应的数据链路层(如以太网协议)进行封装。ARP报文分为ARP请求和应答报文两种,报文格式如下图所示。

../_images/ARP.png

ARP报文每个字段的含义如下表所示:

  • 硬件类型 :占2字节,表示ARP报文可以在哪种类型的网络上传输,值为1时表示为以太网地址。

  • 上层协议类型 :占2字节,表示硬件地址要映射的协议地址类型,映射IP地址时的值为0x0800。

  • MAC地址长度 : 占1字节,标识MAC地址长度,以字节为单位,此处为6。

  • IP协议地址长度 : 占1字节,标识IP地址长度,以字节为单位,此处为4。

  • 操作类型 : 占2字节,指定本次ARP报文类型。1标识ARP请求报文,2标识ARP应答报文。

  • 源MAC地址 : 占6字节,标识发送设备的硬件地址。

  • 源IP地址 : 占4字节,标识发送方设备的IP地址。

  • 目的MAC地址 : 占6字节,表示接收方设备的硬件地址,在请求报文中该字段值全为0,即00-00-00-00-00-00,表示任意地址,因为现在不知道这个MAC地址。

  • 目的IP地址 : 占4字节,表示接受方的IP地址。

3.4. ARP表

无论是主机,还是交换机或路由器都会有一个用来缓存同一网段设备IP地址和MAC地址的ARP映射表,用于数据帧的转发。设备通过ARP解析到目的MAC之后,将会在自己的ARP映射表中增加IP地址到MAC地址的映射表,以用于后续到同一目的地数据帧的转发。ARP表项分为动态ARP表项和静态ARP表项。在实验中,我们需要实现 动态ARP表项

动态ARP表项由ARP协议通过ARP报文自动生成和维护,可以被老化,可以被新的ARP报文更新,也可以被静态ARP表项所覆盖。当到达老化时间或接口关闭时会删除相应的动态ARP表项。

在本实验中,我们提供了支持超时时间的map键值对容器框架,以减轻同学们的部分工作量:)。在用于获取map中指定键的值map_set()函数中,调用map_entry_valid()函数判断该键值对是否有效,而判断的标准里就有是否超时判断。

 1   /**
 2   * @brief 内部函数,判断键值对是否有效
 3   *
 4   * @param map 要判断的map
 5   * @param entry 键值对指针
 6   * @return int 1为合法,0为不合法
 7   */
 8   int map_entry_valid(map_t *map, const void *entry)
 9   {
10      time_t entry_time = *(time_t *)((uint8_t *)entry + map->key_len + map->value_len);
11      return entry_time && (!map->timeout || entry_time + map->timeout >= time(NULL));
12   }
13
14   /**
15   * @brief 获取map中指定键的值
16   *
17   * @param map 要获取的map
18   * @param key 键指针
19   * @return void* 值指针,找不到为NULL
20   */
21   void *map_get(map_t *map, const void *key)
22   {
23      if (key == NULL)
24         return NULL;
25      for (size_t i = 0; i < map->max_size; i++)
26      {
27         uint8_t *entry = map_entry_get(map, i);
28         if (map_entry_valid(map, entry) && !memcmp(key, entry, map->key_len))
29               return entry + map->key_len;
30      }
31      return NULL;
32   }

4. 代码实现与检测

请同学们认真阅读本实验提供的代码框架,并补充完整 src/arp.c 文件中的arp_req()函数、arp_out()函数、arp_in()函数和arp_resp()函数。

4.1. ARP初始化

arp_init()函数实现ARP初始化功能,初始化流程如下:

1 :调用map_init()函数,初始化用于存储IP地址和MAC地址的ARP表arp_table,并设置超时时间为ARP_TIMEOUT_SEC。

2 :调用map_init()函数,初始化用于缓存来自IP层的数据包,并设置超时时间为ARP_MIN_INTERVAL。

3 :调用net_add_protocol()函数,增加key:NET_PROTOCOL_ARP和vaule:arp_in的键值对。

4 :在初始化阶段(系统启用网卡)时,要向网络上发送无回报ARP包(ARP announcemennt),即广播包,告诉所有人自己的IP地址和MAC地址。在实验代码中,调用arp_req()函数来发送一个无回报ARP包。

提示

无回报ARP包(ARP announcement) :用于昭示天下(LAN)本机要使用某个IP地址了,是一个Sender IP和Traget IP填充的都是本机IP地址的ARP request。

同学们需要自行实现arp_req()函数,功能如下:

Step1 :调用buf_init()对txbuf进行初始化。

Step2 :填写ARP报头。

Step3 :ARP操作类型为ARP_REQUEST,注意大小端转换。

Step4 :调用ethernet_out函数将ARP报文发送出去。注意:ARP announcement或ARP请求报文都是广播报文,其目标MAC地址应该是广播地址:FF-FF-FF-FF-FF-FF。

ARP announcement报文格式可参考如下:

../_images/ARP-1.png

4.2. ARP发送处理过程

同学们需要自行实现arp_out()函数,功能如下:

Step1 :调用map_get()函数,根据IP地址来查找ARP表(arp_table)。

Step2 :如果能找到该IP地址对应的MAC地址,则将数据包直接发送给以太网层,即调用ethernet_out函数直接发出去。

Step3 :如果没有找到对应的MAC地址,进一步判断arp_buf是否已经有包了,如果有,则说明正在等待该ip回应ARP请求,此时不能再发送arp请求;如果没有包,则调用map_set()函数将来自IP层的数据包缓存到arp_buf,然后,调用arp_req()函数,发一个请求目标IP地址对应的MAC地址的ARP request报文。

注意

扩展功能:由于map数据结构的限制,相同ip的arp_buf长度仍为1,同学们可以自行设计数据结构实现不丢包。

4.3. ARP接收处理过程

同学们需要自行实现arp_in()函数,功能如下:

Step1 :首先判断数据长度,如果数据长度小于ARP头部长度,则认为数据包不完整,丢弃不处理。

Step2 :接着,做报头检查,查看报文是否完整,检测内容包括:ARP报头的硬件类型、上层协议类型、MAC硬件地址长度、IP协议地址长度、操作类型,检测该报头是否符合协议规定。

Step3 :调用map_set()函数更新ARP表项。

Step4 :调用map_get()函数查看该接收报文的IP地址是否有对应的arp_buf缓存。

如果有,则说明ARP分组队列里面有待发送的数据包。也就是上一次调用arp_out()函数发送来自IP层的数据包时,由于没有找到对应的MAC地址进而先发送的ARP request报文,此时收到了该request的应答报文。然后,将缓存的数据包arp_buf再发送给以太网层,即调用ethernet_out()函数直接发出去,接着调用map_delete()函数将这个缓存的数据包删除掉。

如果该接收报文的IP地址没有对应的arp_buf缓存,还需要判断接收到的报文是否为ARP_REQUEST请求报文,并且该请求报文的target_ip是本机的IP,则认为是请求本主机MAC地址的ARP请求报文,则调用arp_resp()函数回应一个响应报文。

4.4. ARP响应包

同学们需要自行实现arp_resp(),功能如下:

Step1 :首先调用buf_init()来初始化txbuf。

Step2 :接着,填写ARP报头首部。

Step3 :调用ethernet_out()函数将填充好的ARP报文发送出去。

4.5. 实验自测

ARP自测与eth自测的步骤类似。

点击CMake工具栏,找到arp_test[arp_test.exe],右键,选择“生成”进行编译。

../_images/cmake7.png

接着,打开VSCode的终端,到build目录下,输入ctest -R arp_test进行自测。

ARP协议自测,处理正确的结果显示如下图:

../_images/cmake12.png

如果提示有错,请参照eth协议自测的排除方法来找bug。

备注

如果提示.pcap文件不一致,可以用wireshark软件打开查看.pcap文件。wireshark用法可参考 附录 A:Wireshark 入门

4.5.1. GDB调试

本实验支持使用GDB调试,请参考 Windows开发环境搭建 中的“Windows下编译和调试”

5. 实验提交

请参考实验二的提交方式。