CS144-Lab5-ARP

lab 地址 :lab5-doc
代碼實現:lab5-code
完整目錄:
0. ByteStream
1. StreamReassembler
2. TCPReceiver
3. TCPSender
4. TCPConnection
5. ARP
6. IP-Router

1. 目標

lab0 ~ lab4 已經實現了一個基本的 TCPConnection,支持可靠的字節流,在 lab4 中官方已經實現了網絡層協議數據的封裝 IPV4Datagram
因此接下來我們主要需要關注的就是從 網絡層 -> 數據鏈路層 中:

  • 發包時,IP 數據報 到 EthernetFrame 的轉換
  • 收到 EthernetFrame 的處理

image

image

在處理收發 Ethernet frames 時主要涉及到 ARP 協議,什麼是 ARP 協議?

地址解析協議,即ARP(Address Resolution Protocol),是根據IP地址獲取物理地址的一個TCP/IP協議主機發送信息時將包含目標IP地址的ARP請求廣播到局域網絡上的所有主機,並接收返回消息,以此確定目標的物理地址;收到返回消息後將該IP地址和物理地址存入本機ARP緩存中並保留一定時間,下次請求時直接查詢ARP緩存以節約資源。

我們發送 以太網幀 時,當不知道目標地址的 ip 地址時,需要通過局域網廣播 arp 協議去發現目標主機的地址,並且進行一段時間的緩存方便後續查詢。
對比上個 lab,新增了 network_interface 類,需要實現的核心接口如下:

// 發送數據報,轉成以太網幀
void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop)

// 接收以太網幀
optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame)       

void tick(const size_t ms_since_last_tick);

[!note]
這裏需要注意的是 ARP 協議也是網絡層協議,發送 ARP 協議時也是封裝在 EthernetFramepayload 中的。

2. 實現

接下來規整下我們的實現,其實主要根據 lab 中的描述實現對應接口即可。

2.1 send_datagram

接口的參數主要有 2 個 ,const InternetDatagram &dgramAddress& next_hop ,dgram 是要發送的數據包,而 next_hop 是目標地址,處理點主要如下:

  1. 地址緩存機制,主要用於檢查目標地址 next_hop 是否已經緩存(新增一個地址緩存映射)
    • 已緩存,將數據包轉換成 EthernetFrame ,push 到 frames_out 隊列中
    • 未緩存,廣播局域網 ARP 消息(其實就是 push 一個 EthernetFrame ,但是 payload 是 ARPMessage 結構序列化的 buffer)
  2. 由於 1 引出 2,緩存的有效時間爲 30s,如果 30s 內與目標主機無任何通信,則緩存失效,反之更新緩存保存時長
  3. 對相同地址發送 ARP 廣播查詢,需要間隔 5s
  4. 當前準備發出的請求,如果由於未知地址沒有發出,需要先緩存到隊列中(新增一個請求隊列)
  5. 由於 4 引出 5,這裏有可能一直沒有查詢到目標地址(超時,地址不可達),但是 lab 中不需要考慮(課程 test case 跳過這種情況)。當查詢到地址時,會收到一個 ARP Reply (recv_frames 接口中處理),重新檢查請求隊列
    代碼如下:
//! \param[in] dgram the IPv4 datagram to be sent
//! \param[in] 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) with the Address::ipv4_numeric() method.)
void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop) {
	// convert IP address of next hop to raw 32-bit representation (used in ARP header)
	const uint32_t next_hop_ip = next_hop.ipv4_numeric();
	// find and send ip datagram
	EthernetFrame frame;
	auto& address = _learn_address[next_hop_ip];
	if (address.is_valid) {
		_send_datagram(dgram, address.learn_address);
	}
	// send arp message request
	else {
		/*
		don’t want to flood the network with ARP requests. If the network interface already sent
		an ARP request about the same IP address in the last five seconds, don’t send a second
		request—just wait for a reply to the first one. Again, queue the datagram until you
		learn the destination Ethernet address.
		*/
		if (!address.can_query()) {
			address.datagrams.push(dgram);
			return;
		}
		// can not query again in 5s, reset query pass time
		address.query_pass_time -= LearnAddress::QUERY_VAILD_TIMEOUT;
		// setup header
		EthernetHeader& header = frame.header();
		header.dst = ETHERNET_BROADCAST;
		header.src = _ethernet_address;
		header.type = EthernetHeader::TYPE_ARP;
		
		// setup arp payload
		ARPMessage arp;
		arp.opcode = ARPMessage::OPCODE_REQUEST;
		arp.sender_ethernet_address = _ethernet_address;
		arp.sender_ip_address = _ip_address.ipv4_numeric();
		arp.target_ip_address = next_hop_ip;
		frame.payload() = arp.serialize();
		// cache datagram
		address.datagrams.push(dgram);
		_frames_out.push(frame);
	}
}

2.2 recv_frame

recv_frame 的接口參數只有一個 EthernetFrame& frame ,以太網幀,recv_frame 要做的事情比較簡單:

  1. 如果 frame 是 ipv4 數據包,解析成 InternetDatagram 類型
    • 如果數據包的目標 ip 不是自己的,返回 {},反之返回解析成功的數據包
  2. 如果 frame 是 arp 數據包
    • 解析成 ARPMessage ,並且保存發送端的 ip 地址和 mac 地址映射(如果已經保存則重置過期時間)
    • 如果 ARP 請求的是自身的 ip,發送一個 ARP Reply
    • 檢查可以發送的 請求隊列
//! \param[in] frame the incoming Ethernet frame
optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) {
	uint32_t local_ip = _ip_address.ipv4_numeric();
	if (frame.header().type == EthernetHeader::TYPE_IPv4) {
		InternetDatagram datagram;
		ParseResult result = datagram.parse(frame.payload());
		if (result != ParseResult::NoError || frame.header().dst != _ethernet_address) {
			return {};
		}
		return datagram;
	}
	
	if (frame.header().type == EthernetHeader::TYPE_ARP) {
		ARPMessage arp;
		ParseResult result = arp.parse(frame.payload());
		if (result != ParseResult::NoError) {
			return {};
		}
		/*
		If the inbound frame is ARP, parse the payload as an ARPMessage and, if successful,
		remember the mapping between the sender’s IP address and Ethernet address for 30 seconds.
		*/
		auto& address = _learn_address[arp.sender_ip_address];
		address.is_valid = true;
		address.learn_address = arp.sender_ethernet_address;
		address.learn_pass_time = 0;
		address.query_pass_time = LearnAddress::QUERY_VAILD_TIMEOUT;
	
		// received arp request, reply host ip and mac
		if (arp.opcode == ARPMessage::OPCODE_REQUEST && arp.target_ip_address == local_ip) {
			// setup header
			EthernetFrame reply_frame;
			EthernetHeader& header = reply_frame.header();
			header.dst = frame.header().src;
			header.src = _ethernet_address;
			header.type = EthernetHeader::TYPE_ARP;
			// setup arp payload
			ARPMessage reply_arp;
			reply_arp.opcode = ARPMessage::OPCODE_REPLY;
			reply_arp.sender_ethernet_address = _ethernet_address;
			reply_arp.sender_ip_address = _ip_address.ipv4_numeric();
			reply_arp.target_ip_address = arp.sender_ip_address;
			reply_arp.target_ethernet_address = arp.sender_ethernet_address;
			reply_frame.payload() = reply_arp.serialize();
			// send it
			_frames_out.push(reply_frame);
		}
		// received arp reply
		if (arp.opcode == ARPMessage::OPCODE_REPLY) {
			// re-sent datagrams
			while (!address.datagrams.empty()) {
				InternetDatagram& dgram = address.datagrams.front();
				_send_datagram(dgram, address.learn_address);
				address.datagrams.pop();
			}
		}
	}
	return {};
}

2.3 tick

tick 函數要處理時間相關的邏輯,根據前面引出的要處理的點,主要就是更新地址緩存保留時間,

//! \param[in] 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& address : _learn_address) {
		LearnAddress& learn_address = address.second;
		learn_address.tick(ms_since_last_tick);
		if (learn_address.learn_timeout()) {
			learn_address.reset();
		}
	}
}

3 測試

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章