CS144-4
本文最后更新于 157 天前,其中的信息可能已经有所发展或是发生改变。

前言

cmake – – build build – – target check3如下就可以开始lab4了

做着一部分最好的就是了解一下报文的类型,和结构关系。我当时做的时候在里面绕了很多弯,比如说误以为ARP以太网包是带普通数据报的,当时想是ARP顺便发过去,后面发现不是,ARP以太网包只含ARP,普通数据以太网包只含普通数据报文(IPV4)。

其中的我整理的以太网头部对应的数据类型信息如下

实验开始

鉴于之前我思路不清晰导致一个lab就在调试方面浪费太多太多时间,我后面使用一些图的关系来表示当时的思路

其中会遇到的问题1:

如何去广播

包有两种那么他们的一个关系是如何的

ARP 包和 ipv 4 包,最大的不同其实也就是以太网包的东西不同,以太网有一个以太网头部和以太网主体部分,以太网头部存储的只有 mac 地址(发送和接收,Ip 的部分分析是由主体部分进行一个拆包实现的,对其包涵的东西进行一个拆包,但是主体仍然存在 mac 地址,这个 mac 地址才是真正需要去注意的,他们只是套着以太网帧的头套干着不同的事情

为什么以太网头和ip头都要有mac地址

传递数据就像是间谍换马甲一样,进入一个国家就穿上一个马甲,也就是 mac 地址只有关于这两个交互国家的,而其中的真正目的 mac 是在主体部分,最后传递到对方手上的时候,以太网帧帧只是从对方的网关的 mac 到对方设备的 mac,mac 地址仅对于当前的局域网或链路是有意义的,并且它们在数据包通过网络传输的过程中会一直变化,直到到达最终的目标设备。而跨越多个网络(国家)的长途传输过程中涉及到的比如 IP 地址等信息则藏于以太网帧的负载(Payload)之内。好处显而易见的实现了一个隐藏 mac 和 ip 的功能保证安全性,同时只关注下一跳实现起来也简单。

在实验过程中要注意实现recv_frame( const EthernetFrame& frame )的时候就算是可以接受的信息也不代表ARP就一定是要你发,也就是说确认是否属于可接收的信息,和检测是否需要你发送是并不包含的,例外就是ARP你可能作为中间进行传递,但并不是一定让你发送你的ARP。而检查是否是你的ARP需要mac地址和ip的双重检测

以下是我实现的代码,需要自己思考的小伙伴就可以停下了

network_interface.cc

#include "network_interface.hh"

#include "arp_message.hh"
#include "ethernet_frame.hh"
#include <iostream>

using namespace std;

// ethernet_address: Ethernet (what ARP calls "hardware") address of the interface
// ip_address: IP (what ARP calls "protocol") address of the interface
// ethernet_address: 以太网(ARP协议中称为“硬件”)接口的地址
// ip_address: IP(ARP协议中称为“协议”)接口的地址
NetworkInterface::NetworkInterface( const EthernetAddress& ethernet_address, const Address& ip_address )
  : ethernet_address_( ethernet_address ), ip_address_( ip_address )
{
  cerr << "DEBUG: Network interface has Ethernet address " << to_string( ethernet_address_ ) << " and IP address "
       << ip_address.ip() << "\n";
}

// dgram: the IPv4 datagram to be sent
// 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)
// dgram: 要发送的IPv4数据报
// next_hop: 要将数据报发送到的接口的IP地址(通常是路由器或默认网关,但如果与目的地在同一网络上直接连接,
// 也可能是另一台主机)

// Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) by using the
// Address::ipv4_numeric() method.
// 注意:Address类型可以通过使用Address::ipv4_numeric()方法转换为uint32_t(原始的32位IP地址)。
void NetworkInterface::send_datagram( const InternetDatagram& dgram, const Address& next_hop )
{
  EthernetHeader ethernet_header_;
  EthernetFrame ethernet_Frame_;
  //目的地已知
  auto mac_address= arp_map_.find(next_hop.ipv4_numeric());
  if(mac_address!=arp_map_.end()){
    // cout<<"目的地已知"<<endl;
    ethernet_header_.dst=mac_address->second.macAddress;
    ethernet_header_.src=ethernet_address_;
    ethernet_header_.type=ethernet_header_.TYPE_IPv4;
    ethernet_Frame_.header=ethernet_header_;
    ethernet_Frame_.payload=serialize(dgram);
  }
  //目的地未知,发送ARP帧来取得对应的ip地址与mac映射
  else {
  // cout<<"目的地未知"<<endl;
  //判断是否需要发送(是否找得到记录)
  if(ethernet_frame_ARPMessage_map_.find(next_hop.ipv4_numeric())!=
  ethernet_frame_ARPMessage_map_.end()){
  // cout<<"ARP请求缓冲中"<<endl;
    return;
  }
  // cout<<"执行ARP请求"<<endl;
  ARPMessage new_arp_msg;
  //设置状态码为请求
  new_arp_msg.opcode=ARPMessage::OPCODE_REQUEST;
  new_arp_msg.sender_ethernet_address=ethernet_address_;
  new_arp_msg.sender_ip_address=ip_address_.ipv4_numeric();
  //将发送地址设置成广播地址,但是此处出现了地址错误,说明广播地址是只在以太网帧当中的
  new_arp_msg.target_ip_address=next_hop.ipv4_numeric();

  //添加以太网头部信息
  ethernet_header_.dst=ETHERNET_BROADCAST;
  ethernet_header_.src=ethernet_address_;
  ethernet_header_.type=ethernet_header_.TYPE_ARP;

  //封装以太网帧
  ethernet_Frame_.header=ethernet_header_;
  ethernet_Frame_.payload= std::move( serialize( new_arp_msg ) );
  //放入arp网络优化表,起始行时间是5000ms
  ethernet_frame_ARPMessage_map_[next_hop.ipv4_numeric()]=5000;
  //那么这次是数据也要保存的,因为有可能不知道mac地址,但是仍然存在想要发送消息的愿望
  InternetDatagram_buffer[next_hop.ipv4_numeric()].push_back(dgram);
  }
  //存入到以太网的处理队列之中
  ethernet_frame_buffer_deque.push_back(ethernet_Frame_);
  (void)dgram;
  (void)next_hop;
}

// frame: the incoming Ethernet frame
// frame: 进来的以太网帧
optional<InternetDatagram> NetworkInterface::recv_frame( const EthernetFrame& frame ){
  //判断如果mac不是广播或者本地的就直接返回(不是给你的信息)
  if(frame.header.dst!=ethernet_address_&&frame.header.dst!=ETHERNET_BROADCAST){
    return nullopt;
  }
  // cout<<"----------------NetworkInterface::recv_frame-----------------"<<endl;
  
  //传入的是IPV4包
  InternetDatagram internet_datagram;
  if(frame.header.type==EthernetHeader::TYPE_IPv4){
    // cout<<"----------------EthernetHeader::TYPE_IPv4-----------------"<<endl;
    if(parse(internet_datagram,frame.payload)){
      return internet_datagram;
    }
    return nullopt;
  }
  //传入的是ARP帧
  else if(frame.header.type==EthernetHeader::TYPE_ARP){
    ARPMessage arp_msg;
    bool is_effective_arp=parse(arp_msg,frame.payload);
    //需要去判断一下是不是要你发的arp包,即使是发给你的包也有可能是广播的形式,所以也就是要做到mac和ip的双重验证
    if ( arp_msg.target_ip_address != ip_address_.ipv4_numeric() ){
      return nullopt;
    }
    if(is_effective_arp){
      //设置缓存
      DeviceInfo device_info{arp_msg.sender_ethernet_address,30000};
      //如果有值直接覆盖,如果没有创建(更新)
      arp_map_[arp_msg.sender_ip_address]={device_info};
      //应该回复什么?
      //如果是对方发的是ARP包,我们需要把之前未说的种种发送给对方
      if(arp_msg.opcode==ARPMessage::OPCODE_REPLY){
        //把之前没说过的话,都倾述出来
        //转换一下地址
        cout<<"正在发送未处理的数据包的循环前arp_msg.opcode:"<<arp_msg.opcode<<endl;
        Address send_ip_address=Address::from_ipv4_numeric(arp_msg.sender_ip_address);
        for(auto &it : InternetDatagram_buffer[arp_msg.sender_ip_address]){
            send_datagram(it,send_ip_address);
            cout<<"正在发送未处理的数据包的循环中,arp_msg.opcode:"<<arp_msg.opcode<<endl;
        }
        InternetDatagram_buffer[arp_msg.sender_ip_address].clear();
      }
      //如果对方发的是请求,要我们回复arp包
      else{
        //建立arp包
       ARPMessage new_arp_msg;
       //建立包装
       EthernetHeader ethernet_header_;
       EthernetFrame ethernet_Frame_;
       //设置状态码为响应
       new_arp_msg.opcode=ARPMessage::OPCODE_REPLY;
       new_arp_msg.sender_ethernet_address=ethernet_address_;
       new_arp_msg.sender_ip_address=ip_address_.ipv4_numeric();//这里只是处理了ipv4说明有可能出现ipv6需要过滤
       //交换源发送端mac和ip
       new_arp_msg.target_ethernet_address=arp_msg.sender_ethernet_address;
       new_arp_msg.target_ip_address=arp_msg.sender_ip_address;
       ethernet_Frame_.payload=serialize(new_arp_msg);
       //设置类型
       ethernet_header_.type=ethernet_header_.TYPE_ARP;
       //目标地址和源地址交换
       ethernet_header_.dst=frame.header.src;
       ethernet_header_.src=ethernet_address_;
       //替换以太网头
       ethernet_Frame_.header=ethernet_header_;
       //放入准备队列
       ethernet_frame_buffer_deque.push_back(ethernet_Frame_);     
      }
    }
    return nullopt;
  }
  (void)frame;
return nullopt;
}

// ms_since_last_tick: the number of milliseconds since the last call to this method
// ms_since_last_tick: 自上次调用此方法以来的毫秒数
void NetworkInterface::tick( const size_t ms_since_last_tick )
{

  //更新映射,遍历一遍全部更新就是了
  for (auto it = arp_map_.begin(); it != arp_map_.end(); ) {
    it->second.existenceTime=it->second.existenceTime>ms_since_last_tick?
    it->second.existenceTime-ms_since_last_tick:
    0;
    if (it->second.existenceTime==0) { 
        it = arp_map_.erase(it); 
    } else {
        ++it; 
    }
   }
  //更新ARP请求时间
  for (auto it =ethernet_frame_ARPMessage_map_.begin(); it != ethernet_frame_ARPMessage_map_.end(); ) {
    it->second=it->second>ms_since_last_tick?
    it->second-ms_since_last_tick:
    0;
    if (it->second==0) { 
        it = ethernet_frame_ARPMessage_map_.erase(it); 
    } else {
        ++it; 
    }
   }
  (void)ms_since_last_tick;
}

optional<EthernetFrame> NetworkInterface::maybe_send()
{
  /**
   * 按照我们实现的相应部分,这一部分的应该只返回一个消息即可
  */
 //没准备好就发空的
 if(ethernet_frame_buffer_deque.empty()){
  return nullopt;
 }
 //准备好了就发过去
  EthernetFrame frame=ethernet_frame_buffer_deque.front();
  ethernet_frame_buffer_deque.pop_front();
  return frame;
}

network_interface.hh

#pragma once

#include "address.hh"
#include "ethernet_frame.hh"
#include "ipv4_datagram.hh"

#include <iostream>
#include <list>
#include <optional>
#include <queue>
#include <unordered_map>
#include <utility>



// 定义一个结构体来存储MAC地址和存在时间
struct DeviceInfo {
    EthernetAddress macAddress;
    uint64_t existenceTime; // 存在时间,ms为单位
};

struct send_ARPMessages
{
  uint32_t ip_ipv4;
  uint64_t existenceTime;
};

// A "network interface" that connects IP (the internet layer, or network layer)
// with Ethernet (the network access layer, or link layer).

// This module is the lowest layer of a TCP/IP stack
// (connecting IP with the lower-layer network protocol,
// e.g. Ethernet). But the same module is also used repeatedly
// as part of a router: a router generally has many network
// interfaces, and the router's job is to route Internet datagrams
// between the different interfaces.

// The network interface translates datagrams (coming from the
// "customer," e.g. a TCP/IP stack or router) into Ethernet
// frames. To fill in the Ethernet destination address, it looks up
// the Ethernet address of the next IP hop of each datagram, making
// requests with the [Address Resolution Protocol](\ref rfc::rfc826).
// In the opposite direction, the network interface accepts Ethernet
// frames, checks if they are intended for it, and if so, processes
// the the payload depending on its type. If it's an IPv4 datagram,
// the network interface passes it up the stack. If it's an ARP
// request or reply, the network interface processes the frame
// and learns or replies as necessary.
// “网络接口”连接IP(互联网层或网络层)
// 与以太网(网络接入层或链路层)。

// 这个模块是TCP/IP协议栈的最底层
// (将IP与下层网络协议,例如以太网连接起来)。但是,相同的模块也会在路由器中重复使用:
// 一个路由器通常有多个网络接口,路由器的工作是在不同接口之间路由互联网数据报。

// 网络接口将数据报(来自“客户”,例如TCP/IP协议栈或路由器)转换为以太网帧。
// 为了填写以太网目的地址,它会查找每个数据报的下一个IP跳转的以太网地址,
// 使用[地址解析协议](\ref rfc::rfc826)进行请求。
// 在相反的方向上,网络接口接受以太网帧,检查它们是否是为它所设计的,如果是的话,
// 根据其类型处理负载。如果是IPv4数据报,
// 网络接口会将其向上传递。如果是ARP请求或回复,
// 网络接口会处理帧并学习或必要时回复。
// 定义一个结构体来存储MAC地址和存在时间
class NetworkInterface
{
private:
  // Ethernet (known as hardware, network-access, or link-layer) address of the interface
  // 以太网(又称硬件、网络接入层或链路层)接口的地址mac地址
  EthernetAddress ethernet_address_;
  // IP (known as Internet-layer or network-layer) address of the interface
  // IP(又称互联网层或网络层)接口的地址
  Address ip_address_;
  // uint64_t NI_cur_time_;//当前的接口时间
  // uint64_t NI_pre_time_;//之前的接口时间
  uint64_t max_arp_map_existenceTime_=30000;//30秒的最大时间映射
  std::unordered_map<uint32_t, DeviceInfo> arp_map_{}; // ARP映射表,键为IP地址字符串
  std::unordered_map<uint32_t,uint64_t> ethernet_frame_ARPMessage_map_{};//缓解网络堵塞,减少arp风暴
  std::deque<EthernetFrame> ethernet_frame_buffer_deque{};//以太网队列
  std::unordered_map<uint32_t,std::list<InternetDatagram>> InternetDatagram_buffer{};//缓存期待发送的消息

public:
  // Construct a network interface with given Ethernet (network-access-layer) and IP (internet-layer)
  // addresses
  // 使用给定的以太网(网络接入层)和IP(互联网层)地址构造一个网络接口
  NetworkInterface( const EthernetAddress& ethernet_address, const Address& ip_address );

  // Access queue of Ethernet frames awaiting transmission
  // 访问等待传输的以太网帧队列
  std::optional<EthernetFrame> maybe_send();

  // Sends an IPv4 datagram, encapsulated in an Ethernet frame (if it knows the Ethernet destination
  // address). Will need to use [ARP](\ref rfc::rfc826) to look up the Ethernet destination address
  // for the next hop.
  // ("Sending" is accomplished by making sure maybe_send() will release the frame when next called,
  // but please consider the frame sent as soon as it is generated.)
  // 发送一个IPv4数据报,封装在一个以太网帧中(如果它知道以太网目的地址)。
  // 将需要使用[ARP](\ref rfc::rfc826)来查找下一个跳转的以太网目的地址。
  // (“发送”是通过确保maybe_send()在下次调用时会释放帧来完成的,
  // 但请考虑一旦帧生成就认为它已经发送。)
  void send_datagram( const InternetDatagram& dgram, const Address& next_hop );

  // Receives an Ethernet frame and responds appropriately.
  // If type is IPv4, returns the datagram.
  // If type is ARP request, learn a mapping from the "sender" fields, and send an ARP reply.
  // If type is ARP reply, learn a mapping from the "sender" fields.
  // 接收一个以太网帧并作出适当响应。
  // 如果类型是IPv4,返回数据报。
  // 如果类型是ARP请求,从“发送者”字段学习映射,并发送一个ARP回复。
  // 如果类型是ARP回复,从“发送者”字段学习映射。
  std::optional<InternetDatagram> recv_frame( const EthernetFrame& frame );

  // Called periodically when time elapses
  // 定期调用,当时间流逝时
  void tick( size_t ms_since_last_tick );
};
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
Document