Skip to content

Commit

Permalink
lab5: network interface
Browse files Browse the repository at this point in the history
  • Loading branch information
FlyingPig committed May 1, 2021
1 parent 947eaca commit 1a2e771
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 6 deletions.
2 changes: 1 addition & 1 deletion apps/webget.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ void get_URL(const string &host, const string &path) {
// Then you'll need to print out everything the server sends back,
// (not just one call to read() -- everything) until you reach
// the "eof" (end of file).
CS144TCPSocket socket;
FullStackSocket socket;
socket.connect(Address(host, "http"));
string message;
message += "GET " + path + " HTTP/1.1\r\n";
Expand Down
163 changes: 158 additions & 5 deletions libsponge/network_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,174 @@ NetworkInterface::NetworkInterface(const EthernetAddress &ethernet_address, cons
<< ip_address.ip() << "\n";
}

optional<EthernetAddress> NetworkInterface::get_EthernetAdress(const uint32_t ip_addr) {
optional<EthernetAddress> ret = nullopt;
map<uint32_t, EthernetAddressEntry>::iterator iter;
iter = _cache.find(ip_addr);
if (iter != _cache.end()) {
ret = iter->second.MAC_address;
}
return ret;
}

optional<NetworkInterface::WaitingList> NetworkInterface::get_WaitingList(const uint32_t ip_addr) {
optional<WaitingList> ret = nullopt;
map<uint32_t, NetworkInterface::WaitingList>::iterator iter;
iter = _queue_map.find(ip_addr);
if (iter != _queue_map.end()) {
ret = iter->second;
}
return ret;
}

//! \param[in] MAC_addr the destination Ethernet Address
//! \param[in] dgram the IPv4 datagram to be sent
void NetworkInterface::send_helper(const EthernetAddress MAC_addr, const InternetDatagram &dgram) {
EthernetFrame frame;
frame.header().type = EthernetHeader::TYPE_IPv4;
frame.header().src = _ethernet_address;
frame.header().dst = MAC_addr;
frame.payload() = dgram.serialize();
_frames_out.push(frame);
}

//! \param[in] ipaddr the IPv4 address waits for resolving
//! \param[in] dgram the IPv4 datagram queued to be sent
//! push the datagram into the waiting queue
//! resend ARP request if a new ARP request need to be sent ,i.e., the last request was sent over 5 seconds ago or there is no request sent before
void NetworkInterface::queue_helper(const uint32_t ip_addr, const InternetDatagram &dgram) {
optional<WaitingList> wait_list = get_WaitingList(ip_addr);
bool send_ARP = false;
if (wait_list.has_value()) {
wait_list.value().waiting_datagram.push(dgram);
send_ARP = wait_list.value().time_since_last_ARP_request_send >= NetworkInterface::MAX_RETX_WAITING_TIME;
} else {
WaitingList new_wait_list;
new_wait_list.waiting_datagram.push(dgram);
_queue_map[ip_addr] = new_wait_list;
send_ARP = true;
}
if (send_ARP) send_ARP_request(ip_addr);
}

void NetworkInterface::send_ARP_request(const uint32_t ip_addr) {
EthernetFrame frame;
frame.header().type = EthernetHeader::TYPE_ARP;
frame.header().src = _ethernet_address;
frame.header().dst = ETHERNET_BROADCAST;
ARPMessage arp;
arp.opcode = ARPMessage::OPCODE_REQUEST;
arp.sender_ethernet_address = _ethernet_address;
arp.sender_ip_address = _ip_address.ipv4_numeric();
//arp.target_ethernet_address = unknown address
arp.target_ip_address = ip_addr;
frame.payload() = BufferList(arp.serialize());
_frames_out.push(frame);
}

void NetworkInterface::send_ARP_reply(const uint32_t ip_addr, const EthernetAddress& MAC_addr) {
EthernetFrame frame;
frame.header().type = EthernetHeader::TYPE_ARP;
frame.header().src = _ethernet_address;
frame.header().dst = MAC_addr;
ARPMessage arp;
arp.opcode = ARPMessage::OPCODE_REPLY;
arp.sender_ethernet_address = _ethernet_address;
arp.sender_ip_address = _ip_address.ipv4_numeric();
arp.target_ethernet_address = MAC_addr;
arp.target_ip_address = ip_addr;
frame.payload() = BufferList(arp.serialize());
_frames_out.push(frame);
}
bool NetworkInterface::valid_frame(const EthernetFrame &frame) {
EthernetAddress dst = frame.header().dst;
return dst == _ethernet_address || dst == ETHERNET_BROADCAST;
}

void NetworkInterface::cache_mapping(uint32_t ip_addr, EthernetAddress MAC_addr) {
map<uint32_t, EthernetAddressEntry>::iterator iter;
iter = _cache.find(ip_addr);
if (iter != _cache.end()) {
// update the cache
iter->second.caching_time = 0;
iter->second.MAC_address = MAC_addr;
} else {
// add new entry
EthernetAddressEntry entry;
entry.caching_time = 0;
entry.MAC_address = MAC_addr;
_cache[ip_addr] = entry;
}
}

void NetworkInterface::clear_waitinglist(uint32_t ip_addr, EthernetAddress MAC_addr) {
map<uint32_t, WaitingList>::iterator iter;
iter = _queue_map.find(ip_addr);
if (iter != _queue_map.end()) {
while (!iter->second.waiting_datagram.empty()) {
InternetDatagram dgram = iter->second.waiting_datagram.front();
iter->second.waiting_datagram.pop();
send_helper(MAC_addr, dgram);
}
}
_queue_map.erase(ip_addr);
}

//! \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();

DUMMY_CODE(dgram, next_hop, next_hop_ip);
optional<EthernetAddress> MAC_addr = get_EthernetAdress(next_hop_ip);
if (MAC_addr.has_value()) {
send_helper(MAC_addr.value(), dgram);
} else {
queue_helper(next_hop_ip, dgram);
}
}

//! \param[in] frame the incoming Ethernet frame
optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) {
DUMMY_CODE(frame);
return {};
// filter frames whose dst is not this Network Interface
optional<InternetDatagram> ret = nullopt;
if (!valid_frame(frame)) return ret;
if (frame.header().type == EthernetHeader::TYPE_IPv4) {
InternetDatagram dgram;
if (dgram.parse(Buffer(frame.payload())) == ParseResult::NoError) {
ret = dgram;
}
} else {
ARPMessage arp;
if (arp.parse(Buffer(frame.payload())) == ParseResult::NoError) {
cache_mapping(arp.sender_ip_address, arp.sender_ethernet_address);
clear_waitinglist(arp.sender_ip_address, arp.sender_ethernet_address);
if (arp.opcode == ARPMessage::OPCODE_REQUEST && arp.target_ip_address == _ip_address.ipv4_numeric())
send_ARP_reply(arp.sender_ip_address, arp.sender_ethernet_address);
}
}
return ret;
}

//! \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) { DUMMY_CODE(ms_since_last_tick); }
void NetworkInterface::tick(const size_t ms_since_last_tick) {
map<uint32_t, NetworkInterface::WaitingList>::iterator iter1;
for (iter1 = _queue_map.begin(); iter1 != _queue_map.end(); iter1++) {
iter1->second.time_since_last_ARP_request_send += ms_since_last_tick;
}

map<uint32_t, EthernetAddressEntry>::iterator iter2;
queue<uint32_t> deleted_ips;
for (iter2 = _cache.begin(); iter2 != _cache.end(); iter2++) {
iter2->second.caching_time += ms_since_last_tick;
if (iter2->second.caching_time >= NetworkInterface::MAX_CACHE_TIME) {
deleted_ips.push(iter2->first);
}
}

while (!deleted_ips.empty()) {
uint32_t ip = deleted_ips.front();
deleted_ips.pop();
_cache.erase(ip);
}
}
38 changes: 38 additions & 0 deletions libsponge/network_interface.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "tcp_over_ip.hh"
#include "tun.hh"

#include <map>
#include <optional>
#include <queue>

Expand All @@ -31,6 +32,12 @@
//! and learns or replies as necessary.
class NetworkInterface {
private:
//! only resend ARP request for the same IPv4 address after 5000ms
static constexpr size_t MAX_RETX_WAITING_TIME = 5000;

//! cache the mapping for 30 seconds
static constexpr size_t MAX_CACHE_TIME = 30000;

//! Ethernet (known as hardware, network-access-layer, or link-layer) address of the interface
EthernetAddress _ethernet_address;

Expand All @@ -40,6 +47,37 @@ class NetworkInterface {
//! outbound queue of Ethernet frames that the NetworkInterface wants sent
std::queue<EthernetFrame> _frames_out{};

//! cache entry for ethernet address mapping
struct EthernetAddressEntry {
size_t caching_time;
EthernetAddress MAC_address;
};

//! mapping from ip_address to ethernet address
std::map<uint32_t, EthernetAddressEntry> _cache{};

//! to avoid flooding 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.
struct WaitingList {
size_t time_since_last_ARP_request_send = 0;
std::queue<InternetDatagram> waiting_datagram{};
};

//! mapping from the ip_address to the waiting queue
std::map<uint32_t, WaitingList> _queue_map{};

std::optional<EthernetAddress>get_EthernetAdress(const uint32_t ip_addr);
std::optional<WaitingList>get_WaitingList(const uint32_t ip_addr);
void send_helper(const EthernetAddress MAC_addr, const InternetDatagram &dgram);
void queue_helper(const uint32_t ip_addr, const InternetDatagram &dgram);
void send_ARP_request(const uint32_t ip_addr);
void send_ARP_reply(const uint32_t ip_addr, const EthernetAddress& MAC_addr);
bool valid_frame(const EthernetFrame &frame);
void cache_mapping(uint32_t ip_addr, EthernetAddress MAC_addr);
void clear_waitinglist(uint32_t ip_addr, EthernetAddress MAC_addr);

public:
//! \brief Construct a network interface with given Ethernet (network-access-layer) and IP (internet-layer) addresses
NetworkInterface(const EthernetAddress &ethernet_address, const Address &ip_address);
Expand Down

0 comments on commit 1a2e771

Please sign in to comment.