前言
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 );
};