计算机网络实验(二)

计算机网络实验目录

  • eth协议实现
  • ARP协议实现
  • RIP路由配置和协议分析
  • IP协议实现
  • ICMP协议实现
  • UDP协议实现
  • NAT组网
  • 邮件客户端的设计与实现

文章目录

  • 计算机网络实验目录
  • 前言
  • 基础知识
    • 一、ARP解析协议
    • 二、工作机制
      • 同一网段的ARP解析
      • 不同网段ARP解析
    • 三、ARP请求报文格式
    • 四、ARP表
      • 动态ARP表
      • 静态ARP表
  • 实验
  • 总结


前言

HITSZ 2022春计算机网络实验完成记录+复习总结
实验网址


基础知识

一、ARP解析协议

`ARP协议以目标IP地址为线索,用来定位下一个应该接收数据包的网络设备对应的MAC地址。如果目标主机不在同一个链路上,可以通过ARP查找下一跳网关的MAC地址。

二、工作机制

同一网段的ARP解析

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

不同网段ARP解析

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

三、ARP请求报文格式

ARP是一个独立的三层协议,所以ARP报文在向数据链路层传输时不需要经过IP协议的封装,而是直接生成自己的报文,其中包括ARP报头,到数据链路层后再对应的数据链路层(如以太网协议)进行封装ARP报文分为ARP请求和应答报文两种,报文格式如下图所示。
计算机网络实验(二)_第3张图片
其中,每个字段的含义如下表所示:

  • 硬件类型:表示ARP报文可以在那种类型的网络上传输,为1表示为以太网地址
  • 上层协议类型:表示硬件地址要映射的协议地址类型,映射IP地址时的值为0x0800
  • MAC地址长度:标识MAC地址长度,字节为单位
  • IP地址长度:标识IP地址长度,字节为单位
  • 操作类型:指定本次ARP报文类型,1标识ARP请求报文,2标识ARP应答报文
  • 源MAC地址
  • 源IP地址
  • 目的MAC地址:标识接收方设备的硬件地址,在请求报文中字段的值全为0,即00-00-00-00-00-00,标识不知道MAC地址

四、ARP表

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

动态ARP表

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

静态ARP表

不超时删除条目


实验

需要实现的函数:

  • arp_req:发送Announcement和arp请求报文的函数
  • arp_out:查找ARP表发送IP数据包,如果没有找到需要广播arp请求
  • arp_in:收到数据包的处理,有可能收到回复arp请求的数据包,也可能收到arp请求
  • arp_resp:发送arp响应

实验test流程:

  1. 初始化arp协议,将arp协议加入协议栈
  2. 广播一个announcement请求,相当于告诉网络我要用这个IP地址(192.168.163.103)
  3. 希望向192.168.163.10发送ip数据包,需要对应的mac地址
  4. 查询IP为192.168.163.10的host的mac地址,arp缓存中不存在,于是将待发送的数据包存入buff里面,等待reply
  5. 广播arp request
  6. 收到reply后,将 < i p , M a c > <ip,Mac>存入arp缓存表中,同时发现buff中有等待发送到192.168.163.10的数据包,于是调用ethernet_out发送数据

注意事项: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有点麻烦。。

你可能感兴趣的:(计算机网络,c语言)