一、实验目的
完成一个网络接口实现。
二、实验内容
完成一个网络接口实现,其大部分工作是:为每个下一跳IP地址查找(和缓存)以太网地址。而这种协议被称为地址解析协议ARP。
三、实验过程
在minnow目录下输入git merge origin/check4-startercode获取Lab4
用文本编辑器打开./src/network_interface.hh
修改代码
用文本编辑器打开./src/network_interface.cc
修改代码
在build目录下输入make进行编译
输入make check4进行测试
测试成功,实验结束。
四、实验体会
1.在实现整个网络接口时,必须确保几点:
①ARP条目 TTL 为30s,时间到期后需要将其从 ARP Table 中删除。
若发送 IP 报文时,发现 ARP Table 中无目标 MAC 地址,则立即发送 ARP 请求报文,同时将当前 IP 报文暂时缓存,直至获取到目标 MAC 地址后再重新发送。
②不同目标 IP 的 ARP 请求报文之间的发送间隔,不能超过 5s。
③如果 ARP 请求报文在 5 秒内仍然无响应,则重新发送。
④当网络接口接收到一个以太网帧时,
必须丢弃目的 MAC 地址不为当前网络接口 MAC 地址
除了 ARP 协议需要比较自己的 IP 地址以外,不要在其他任何地方进行 IP 比较,因为网络接口位于链路层。
如果是发给自己的 ARP 请求,那么要忽略掉发送来的 ARPMessage::target_ethernet_address,因为发送者自己也不知道这个要填写什么,该字段无意义。
无论接收到的是 ARP 请求包或者 ARP 响应包,只要是明确发给自己的,那么这里面的 src_ip_addr 和 src_eth_addr 都可用于更新当前的 ARP 表。
2.如何理解NetworkInterface:
①一个将IP(互联网层或网络层)与以太网(网络访问层或链路层)连接的"网络接口",该模块是TCP/IP协议栈的最底层(连接IP与更底层的网络协议,如以太网),但同样的模块也作为路由器的一部分反复使用;
②路由器通常有许多网络接口,其工作是在不同的接口之间路由互联网数据报,网络接口将来自"客户端"(例如TCP/IP协议栈或路由器)的数据报转换为以太网帧。为了填写以太网的目标地址,它查找每个数据报的下一个IP跳的以太网地址,并使用地址解析协议ARP进行请求。在相反的方向,网络接口接受以太网帧,检查它们是否是针对它的,如果是,则根据其类型处理有效载荷。
如果是IPv4数据报,网络接口将其向上传递到协议栈。
如果是ARP请求或回复,网络接口将处理该帧,并根据需要进行学习或回复。
五、代码附录
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>
// 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.
class NetworkInterface
{
private:
// Ethernet (known as hardware, network-access, or link-layer) address of the interface
EthernetAddress ethernet_address_;
// IP (known as Internet-layer or network-layer) address of the interface
Address ip_address_;
std::queue<EthernetFrame> send_frame_{};
std::unordered_map<uint32_t, std::queue<InternetDatagram>> store_dgram_{};
//std::queue<std::pair<InternetDatagram, Address>> store_dgram_{};
//这里key值不能选Address,它没有拷贝构造函数,=重载
std::unordered_map<uint32_t, std::pair<EthernetAddress, size_t>> map_ip2mac_{};
std::unordered_map<uint32_t,size_t> map_arp_time_{};
public:
// Construct a network interface with given Ethernet (network-access-layer) and IP (internet-layer)
// addresses
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.)
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.
std::optional<InternetDatagram> recv_frame( const EthernetFrame& frame );
// Called periodically when time elapses
void tick( size_t ms_since_last_tick );
};
network_interface.cc
#include "network_interface.hh"
#include "arp_message.hh"
#include "ethernet_frame.hh"
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
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)
// Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) by using the
// Address::ipv4_numeric() method.
void NetworkInterface::send_datagram( const InternetDatagram& dgram, const Address& next_hop )
{
//以太网帧可以携带Internetdatagram,也可以携带arpmessage
//(void)dgram;
//(void)next_hop;
uint32_t target_ip = next_hop.ipv4_numeric();
//uint32_t source_address = dgram.header.src;
//uint32_t destination_address = dgram.header.dst;
//发送到next_hop, 而不是dgram的dst
auto it = map_ip2mac_.find(target_ip);
//nexthop的以太网地址已知,封装dgram发送
if(it!=map_ip2mac_.end())
{
EthernetFrame send_frame
{{it->second.first, ethernet_address_, EthernetHeader::TYPE_IPv4},serialize(dgram)};
//send_frame.header.dst = it->second.first;
send_frame_.push(send_frame);
return;
}
//nexthop的以太网地址未知,如果没发过arp就发request arp,把dgram缓存起来
if(map_arp_time_.find(target_ip)==map_arp_time_.end())
{
ARPMessage arp_send;
arp_send.opcode = ARPMessage::OPCODE_REQUEST;
arp_send.sender_ethernet_address = ethernet_address_;
arp_send.sender_ip_address = ip_address_.ipv4_numeric();
arp_send.target_ip_address = target_ip;
EthernetFrame send_frame{{ETHERNET_BROADCAST, ethernet_address_, EthernetHeader::TYPE_ARP}, serialize(arp_send)};
send_frame_.push(send_frame);
map_arp_time_.insert(make_pair(target_ip, 0));//发完arp之后加入到map里面记录下来
}
//缓存dgram
auto it_store_dgram = store_dgram_.find(target_ip);
if(it_store_dgram == store_dgram_.end())
{
std::queue<InternetDatagram> q_dgram;
store_dgram_.insert(make_pair(target_ip, q_dgram));
//store_dgram_[target_ip].push(dgram);
}
store_dgram_[target_ip].push(dgram);
return;
}
// frame: the incoming Ethernet frame
optional<InternetDatagram> NetworkInterface::recv_frame( const EthernetFrame& frame )
{
if(frame.header.dst!=ethernet_address_ && frame.header.dst!=ETHERNET_BROADCAST)
return{};//有点疑惑为什么会出现这种发错的情况,但是测试样例里面有一个这样的例子
//(void)frame;
//收到arp,两种情况,收到的是request或者reply
if(frame.header.type==EthernetHeader::TYPE_ARP)
{
ARPMessage rec_arp;
parse(rec_arp, frame.payload);
map_ip2mac_.insert(make_pair(rec_arp.sender_ip_address, make_pair(rec_arp.sender_ethernet_address, 0)));
//收到的是request,如果request的是自己的以太网地址,就发送一个reply的arpmessage,发送给request方
if(rec_arp.opcode==ARPMessage::OPCODE_REQUEST)
{
if(rec_arp.target_ip_address == ip_address_.ipv4_numeric())
{
ARPMessage arp_reply;
arp_reply.opcode = ARPMessage::OPCODE_REPLY;
arp_reply.sender_ethernet_address = ethernet_address_;
arp_reply.sender_ip_address = ip_address_.ipv4_numeric();
arp_reply.target_ethernet_address = rec_arp.sender_ethernet_address;
arp_reply.target_ip_address = rec_arp.sender_ip_address;
EthernetFrame reply_frame
{{rec_arp.sender_ethernet_address, ethernet_address_, EthernetHeader::TYPE_ARP},serialize(arp_reply)};
send_frame_.push(reply_frame);
return{};
}
else return{};
}
//收到arp之后,无论arp是reply还是request都检查一下有没有可以发送的dgram
auto it = store_dgram_.find(rec_arp.sender_ip_address);
if(it!=store_dgram_.end())
{
while(it->second.empty()==false)
{
InternetDatagram dgram = it->second.front();
it->second.pop();
EthernetFrame send_frame{{rec_arp.sender_ethernet_address, ethernet_address_,EthernetHeader::TYPE_IPv4},serialize(dgram)};
send_frame_.push(send_frame);
}
if(it->second.empty()) store_dgram_.erase(it);
}
}
//frame携带的是internetdatagram
else if(frame.header.type==EthernetHeader::TYPE_IPv4)
{
InternetDatagram dgram;
parse(dgram, frame.payload);
return dgram;
}
return {};
}
// ms_since_last_tick: the number of milliseconds since the last call to this method
void NetworkInterface::tick( const size_t ms_since_last_tick )
{
for(auto it = map_ip2mac_.begin();it!=map_ip2mac_.end();)
{
it->second.second += ms_since_last_tick;
if(it->second.second >= 30000)it=map_ip2mac_.erase(it);
else it++;
}
for(auto it = map_arp_time_.begin(); it!=map_arp_time_.end();)
{
it->second += ms_since_last_tick;
if(it->second>=5000) it=map_arp_time_.erase(it);
else it++;
}
//for(auto it = map)
//(void)ms_since_last_tick;
}
optional<EthernetFrame> NetworkInterface::maybe_send()
{
if(!send_frame_.empty())
{
auto frame = send_frame_.front();
send_frame_.pop();
return {frame};
}
else return{};
}