HITSZ 2022春计算机网络实验完成记录+复习总结
实验网址
`ARP协议以目标IP地址为线索,用来定位下一个应该接收数据包的网络设备对应的MAC地址。如果目标主机不在同一个链路上,可以通过ARP查找下一跳网关的MAC地址。
如下图所示,主机A需要发报文给主机B,如果在缓存中找不到相应的记录,就必须先解析主机B的硬件地址。主机A首先在网段内通过广播发出ARP请求报文,由于广播的包可以被同一个链路上所有的主机或路由器接收,因此ARP的请求包也就会被这同一个链路上所有的主机和路由器进行解析。主机B收到后,判断报文的目的IP是本主机的IP地址,便将本主机的硬件地址写入应答报文,发送给主机A,主机A收到后将其存入缓存中,则解析成功,然后才将报文发往主机B。
如下图所示,主机A要发报文给主机C,首先主机A分析目的地址不在同一个网段,需要将报文先发给其默认网关,再由默认网关转发。如果没有找到默认网关的硬件地址,便发送ARP请求报文,请求默认网关的硬件地址,默认网关收到之后,将自己的硬件地址写入应答报文,发送给主机A。然后,主机A到主机C的报文首先被送到默认网关。默认网关再根据报文的目的IP地址进行转发,以此类推,直至报文送到主机C中。主机C到主机A的报文以相反的顺序发送。
ARP是一个独立的三层协议,所以ARP报文在向数据链路层传输时不需要经过IP协议的封装,而是直接生成自己的报文,其中包括ARP报头,到数据链路层后再对应的数据链路层(如以太网协议)进行封装ARP报文分为ARP请求和应答报文两种,报文格式如下图所示。
其中,每个字段的含义如下表所示:
无论是主机,还是交换机或路由器都会有一个用来缓存同一网段设备IP地址和MAC地址的ARP映射表,用于数据帧的转发。设备通过ARP解析到目的MAC之后,将会在自己的ARP映射表中增加IP地址到MAC地址的映射表,以用于后续到同一目的地数据帧的转发。ARP表项分为动态ARP表项和静态ARP表项。
动态ARP表项由ARP协议通过ARP报文自动生成和维护,可以被老化,可以被新的ARP报文更新,也可以被静态ARP表项所覆盖。当到达老化时间或接口关闭时会删除相应的动态ARP表项。
不超时删除条目
需要实现的函数:
实验test流程:
注意事项:A向B request时,A为sender,B为target;B向A reply时,B为sender,A为target;同时还需要注意,再知道mac地址后,调用ethernet_out发送时使用的上层协议就不再是ARP而是IP了
static const arp_pkt_t arp_init_pkt = {
.hw_type16 = swap16(ARP_HW_ETHER),
.pro_type16 = swap16(NET_PROTOCOL_IP),
.hw_len = NET_MAC_LEN,
.pro_len = NET_IP_LEN,
.sender_ip = NET_IF_IP,
.sender_mac = NET_IF_MAC,
.target_mac = {0}};
/**
* @brief arp地址转换表,的容器
*
*/
map_t arp_table;
/**
* @brief arp buffer,的容器
*
*/
map_t arp_buf;
/**
* @brief 发送一个arp请求
*
* @param target_ip 想要知道的目标的ip地址
*/
void arp_req(uint8_t *target_ip)
{
// TO-DO:调用arp_req()发送请求报文或者无回报报文。
// Step1 :调用buf_init()对txbuf进行初始化。
buf_init(&txbuf, 0);
// Step2 :填写ARP报头。
// 注意:ARP announcement或ARP请求报文都是广播报文,其目标MAC地址应该是广播地址:FF-FF-FF-FF-FF-FF。
buf_add_header(&txbuf, sizeof(arp_pkt_t));
arp_pkt_t *header = (arp_pkt_t *)txbuf.data;
memcpy(header, &arp_init_pkt, sizeof(arp_pkt_t));
memcpy(header->target_ip, target_ip, NET_IP_LEN * sizeof(uint8_t));
// request报文中的target_mac为0
// Step3 :ARP操作类型为ARP_REQUEST,注意大小端转换。
uint16_t opcode16 = swap16(ARP_REQUEST);
memcpy(&(header->opcode16), &opcode16, sizeof(uint16_t));
// Step4 :调用ethernet_out函数将ARP报文发送出去。
ethernet_out(&txbuf, ether_broadcast_mac, NET_PROTOCOL_ARP);
}
/**
* @brief 发送一个arp响应
*
* @param target_ip 目标ip地址
* @param target_mac 目标mac地址
*/
void arp_resp(uint8_t *target_ip, uint8_t *target_mac)
{
// TO-DO
// Step1 :首先调用buf_init()来初始化txbuf。
buf_init(&txbuf, 0);
// Step2 :接着,填写ARP报头首部。
buf_add_header(&txbuf, sizeof(arp_pkt_t));
arp_pkt_t *header = (arp_pkt_t *)txbuf.data;
memcpy(header, &arp_init_pkt, sizeof(arp_pkt_t));
memcpy(header->target_ip, target_ip, NET_IP_LEN * sizeof(uint8_t));
memcpy(header->target_mac, target_mac, NET_MAC_LEN * sizeof(uint8_t));
uint16_t opcode16 = swap16(ARP_REPLY);
memcpy(&(header->opcode16), &opcode16, sizeof(uint16_t));
// Step3 :调用ethernet_out()函数将填充好的ARP报文发送出去,广播
ethernet_out(&txbuf, target_mac, NET_PROTOCOL_ARP);
}
/**
* @brief 处理一个收到的数据包
*
* @param buf 要处理的数据包
* @param src_mac 源mac地址
*/
void arp_in(buf_t *buf, uint8_t *src_mac)
{
// TO-DO:有可能接受到request包,有可能接受到reply包
// Step1 :首先判断数据长度,如果数据长度小于ARP头部长度,则认为数据包不完整,丢弃不处理。
if(buf->len < sizeof(arp_pkt_t))
return;
// Step2 :接着,做报头检查,查看报文是否完整,检测内容包括:
// ARP报头的硬件类型、上层协议类型、MAC硬件地址长度、IP协议地址长度、操作类型,检测这些内存是否符合协议规定。
arp_pkt_t header;
memcpy(&header, buf->data, sizeof(header));
buf_remove_header(buf, sizeof(arp_pkt_t));
if(header.hw_type16 != swap16(ARP_HW_ETHER) || header.pro_type16 != swap16(NET_PROTOCOL_IP) ||
header.hw_len != NET_MAC_LEN || header.pro_len != NET_IP_LEN ||
(header.opcode16 != swap16(ARP_REQUEST) && header.opcode16 != swap16(ARP_REPLY)))
return;
// Step3 :调用map_set()函数更新ARP表项。
map_set(&arp_table, header.sender_ip, header.sender_mac);
// Step4 :调用map_get()函数查看该接收报文的IP地址是否有对应的arp_buf缓存。
buf_t *ip_buf = map_get(&arp_buf, header.sender_ip);
if(ip_buf)
{//响应报文
ethernet_out(ip_buf, header.sender_mac, NET_PROTOCOL_IP);//注意此时上层协议是IP
map_delete(&arp_buf, header.sender_ip);
}
else
{
if(header.opcode16 == swap16(ARP_REQUEST)
&& !memcmp(header.target_ip, net_if_ip, NET_IP_LEN * sizeof(uint8_t)))//请求报文
arp_resp(header.sender_ip, header.sender_mac);
}
// 如果有,则说明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()函数回应一个响应报文。
}
/**
* @brief 处理一个要发送的数据包
*
* @param buf 要处理的数据包
* @param ip 目标ip地址
* @param protocol 上层协议
*/
void arp_out(buf_t *buf, uint8_t *ip)
{
// Step1 :调用map_get()函数,根据IP地址来查找ARP表(arp_table)。
uint8_t *mac = map_get(&arp_table, ip);
// Step2 :如果能找到该IP地址对应的MAC地址,则将数据包直接发送给以太网层,即调用ethernet_out函数直接发出去。
if(mac)
ethernet_out(buf, mac, NET_PROTOCOL_IP);//注意此时上层协议是IP
else
{
// Step3 :如果没有找到对应的MAC地址,进一步判断arp_buf是否已经有包了,如果有,则说明正在等待该ip回应ARP请求,
// 此时不能再发送arp请求;如果没有包,则调用map_set()函数将来自IP层的数据包缓存到arp_buf,然后,调用arp_req()函数,
// 发一个请求目标IP地址对应的MAC地址的ARP request报文。
uint8_t *buff_address = map_get(&arp_buf, ip);
if(!buff_address)
{
map_set(&arp_buf, ip, buf);
arp_req(ip);
}
}
}
/**
* @brief 初始化arp协议
*
*/
void arp_init()
{
map_init(&arp_table, NET_IP_LEN, NET_MAC_LEN, 0, ARP_TIMEOUT_SEC, NULL);
map_init(&arp_buf, NET_IP_LEN, sizeof(buf_t), 0, ARP_MIN_INTERVAL, buf_copy);
net_add_protocol(NET_PROTOCOL_ARP, arp_in);
arp_req(net_if_ip);
}
debug有点麻烦。。