323 lines
15 KiB
C++
323 lines
15 KiB
C++
#include "arp_message.hh"
|
|
#include "ethernet_header.hh"
|
|
#include "ipv4_datagram.hh"
|
|
#include "network_interface_test_harness.hh"
|
|
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
|
|
using namespace std;
|
|
|
|
EthernetAddress random_private_ethernet_address() {
|
|
EthernetAddress addr;
|
|
for (auto &byte : addr) {
|
|
byte = random_device()(); // use a random local Ethernet address
|
|
}
|
|
addr.at(0) |= 0x02; // "10" in last two binary digits marks a private Ethernet address
|
|
addr.at(0) &= 0xfe;
|
|
|
|
return addr;
|
|
}
|
|
|
|
InternetDatagram make_datagram(const string &src_ip, const string &dst_ip) {
|
|
InternetDatagram dgram;
|
|
dgram.header().src = Address(src_ip, 0).ipv4_numeric();
|
|
dgram.header().dst = Address(dst_ip, 0).ipv4_numeric();
|
|
dgram.payload() = string("hello");
|
|
dgram.header().len = dgram.header().hlen * 4 + dgram.payload().size();
|
|
return dgram;
|
|
}
|
|
|
|
ARPMessage make_arp(const uint16_t opcode,
|
|
const EthernetAddress sender_ethernet_address,
|
|
const string sender_ip_address,
|
|
const EthernetAddress target_ethernet_address,
|
|
const string target_ip_address) {
|
|
ARPMessage arp;
|
|
arp.opcode = opcode;
|
|
arp.sender_ethernet_address = sender_ethernet_address;
|
|
arp.sender_ip_address = Address(sender_ip_address, 0).ipv4_numeric();
|
|
arp.target_ethernet_address = target_ethernet_address;
|
|
arp.target_ip_address = Address(target_ip_address, 0).ipv4_numeric();
|
|
return arp;
|
|
}
|
|
|
|
EthernetFrame make_frame(const EthernetAddress &src,
|
|
const EthernetAddress &dst,
|
|
const uint16_t type,
|
|
const BufferList payload) {
|
|
EthernetFrame frame;
|
|
frame.header().src = src;
|
|
frame.header().dst = dst;
|
|
frame.header().type = type;
|
|
frame.payload() = payload.concatenate();
|
|
return frame;
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
{
|
|
const EthernetAddress local_eth = random_private_ethernet_address();
|
|
NetworkInterfaceTestHarness test{"typical ARP workflow", local_eth, Address("4.3.2.1", 0)};
|
|
|
|
const auto datagram = make_datagram("5.6.7.8", "13.12.11.10");
|
|
test.execute(SendDatagram{datagram, Address("192.168.0.1", 0)});
|
|
|
|
// outgoing datagram should result in an ARP request
|
|
test.execute(ExpectFrame{
|
|
make_frame(local_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, local_eth, "4.3.2.1", {}, "192.168.0.1").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
const EthernetAddress target_eth = random_private_ethernet_address();
|
|
|
|
test.execute(Tick{800});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
// ARP reply should result in the queued datagram getting sent
|
|
test.execute(ReceiveFrame{
|
|
make_frame(
|
|
target_eth,
|
|
local_eth,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REPLY, target_eth, "192.168.0.1", local_eth, "4.3.2.1").serialize()),
|
|
{}});
|
|
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, target_eth, EthernetHeader::TYPE_IPv4, datagram.serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
// any IP reply directed for our Ethernet address should be passed up the stack
|
|
const auto reply_datagram = make_datagram("13.12.11.10", "5.6.7.8");
|
|
test.execute(
|
|
ReceiveFrame(make_frame(target_eth, local_eth, EthernetHeader::TYPE_IPv4, reply_datagram.serialize()),
|
|
reply_datagram));
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
// incoming frames to another Ethernet address (not ours) should be ignored
|
|
const EthernetAddress another_eth = {1, 1, 1, 1, 1, 1};
|
|
test.execute(ReceiveFrame(
|
|
make_frame(target_eth, another_eth, EthernetHeader::TYPE_IPv4, reply_datagram.serialize()), {}));
|
|
}
|
|
|
|
{
|
|
const EthernetAddress local_eth = random_private_ethernet_address();
|
|
const EthernetAddress remote_eth = random_private_ethernet_address();
|
|
NetworkInterfaceTestHarness test{"reply to ARP request", local_eth, Address("5.5.5.5", 0)};
|
|
test.execute(ReceiveFrame{
|
|
make_frame(remote_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, remote_eth, "10.0.1.1", {}, "7.7.7.7").serialize()),
|
|
{}});
|
|
test.execute(ExpectNoFrame{});
|
|
test.execute(ReceiveFrame{
|
|
make_frame(remote_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, remote_eth, "10.0.1.1", {}, "5.5.5.5").serialize()),
|
|
{}});
|
|
test.execute(ExpectFrame{make_frame(
|
|
local_eth,
|
|
remote_eth,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REPLY, local_eth, "5.5.5.5", remote_eth, "10.0.1.1").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
}
|
|
|
|
{
|
|
const EthernetAddress local_eth = random_private_ethernet_address();
|
|
const EthernetAddress remote_eth = random_private_ethernet_address();
|
|
NetworkInterfaceTestHarness test{"learn from ARP request", local_eth, Address("5.5.5.5", 0)};
|
|
test.execute(ReceiveFrame{
|
|
make_frame(remote_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, remote_eth, "10.0.1.1", {}, "5.5.5.5").serialize()),
|
|
{}});
|
|
test.execute(ExpectFrame{make_frame(
|
|
local_eth,
|
|
remote_eth,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REPLY, local_eth, "5.5.5.5", remote_eth, "10.0.1.1").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
const auto datagram = make_datagram("5.6.7.8", "13.12.11.10");
|
|
test.execute(SendDatagram{datagram, Address("10.0.1.1", 0)});
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, remote_eth, EthernetHeader::TYPE_IPv4, datagram.serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
}
|
|
|
|
{
|
|
const EthernetAddress local_eth = random_private_ethernet_address();
|
|
NetworkInterfaceTestHarness test{"pending mappings last five seconds", local_eth, Address("1.2.3.4", 0)};
|
|
|
|
test.execute(SendDatagram{make_datagram("5.6.7.8", "13.12.11.10"), Address("10.0.0.1", 0)});
|
|
test.execute(ExpectFrame{
|
|
make_frame(local_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, local_eth, "1.2.3.4", {}, "10.0.0.1").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
test.execute(Tick{4990});
|
|
test.execute(SendDatagram{make_datagram("17.17.17.17", "18.18.18.18"), Address("10.0.0.1", 0)});
|
|
test.execute(ExpectNoFrame{});
|
|
test.execute(Tick{20});
|
|
// pending mapping should now expire
|
|
test.execute(SendDatagram{make_datagram("42.41.40.39", "13.12.11.10"), Address("10.0.0.1", 0)});
|
|
test.execute(ExpectFrame{
|
|
make_frame(local_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, local_eth, "1.2.3.4", {}, "10.0.0.1").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
}
|
|
|
|
{
|
|
const EthernetAddress local_eth = random_private_ethernet_address();
|
|
NetworkInterfaceTestHarness test{"active mappings last 30 seconds", local_eth, Address("4.3.2.1", 0)};
|
|
|
|
const auto datagram = make_datagram("5.6.7.8", "13.12.11.10");
|
|
const auto datagram2 = make_datagram("5.6.7.8", "13.12.11.11");
|
|
const auto datagram3 = make_datagram("5.6.7.8", "13.12.11.12");
|
|
const auto datagram4 = make_datagram("5.6.7.8", "13.12.11.13");
|
|
|
|
test.execute(SendDatagram{datagram, Address("192.168.0.1", 0)});
|
|
test.execute(ExpectFrame{
|
|
make_frame(local_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, local_eth, "4.3.2.1", {}, "192.168.0.1").serialize())});
|
|
const EthernetAddress target_eth = random_private_ethernet_address();
|
|
test.execute(ReceiveFrame{
|
|
make_frame(
|
|
target_eth,
|
|
local_eth,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REPLY, target_eth, "192.168.0.1", local_eth, "4.3.2.1").serialize()),
|
|
{}});
|
|
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, target_eth, EthernetHeader::TYPE_IPv4, datagram.serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
// try after 10 seconds -- no ARP should be generated
|
|
test.execute(Tick{10000});
|
|
test.execute(SendDatagram{datagram2, Address("192.168.0.1", 0)});
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, target_eth, EthernetHeader::TYPE_IPv4, datagram2.serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
// another 10 seconds -- no ARP should be generated
|
|
test.execute(Tick{10000});
|
|
test.execute(SendDatagram{datagram3, Address("192.168.0.1", 0)});
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, target_eth, EthernetHeader::TYPE_IPv4, datagram3.serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
// after another 11 seconds, need to ARP again
|
|
test.execute(Tick{11000});
|
|
test.execute(SendDatagram{datagram4, Address("192.168.0.1", 0)});
|
|
test.execute(ExpectFrame{
|
|
make_frame(local_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, local_eth, "4.3.2.1", {}, "192.168.0.1").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
// ARP reply again
|
|
const EthernetAddress new_target_eth = random_private_ethernet_address();
|
|
test.execute(ReceiveFrame{
|
|
make_frame(new_target_eth,
|
|
local_eth,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REPLY, new_target_eth, "192.168.0.1", local_eth, "4.3.2.1")
|
|
.serialize()),
|
|
{}});
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, new_target_eth, EthernetHeader::TYPE_IPv4, datagram4.serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
}
|
|
|
|
{
|
|
const EthernetAddress local_eth = random_private_ethernet_address();
|
|
const EthernetAddress remote_eth1 = random_private_ethernet_address();
|
|
const EthernetAddress remote_eth2 = random_private_ethernet_address();
|
|
NetworkInterfaceTestHarness test{
|
|
"different ARP mappings are independent", local_eth, Address("10.0.0.1", 0)};
|
|
|
|
// first ARP mapping
|
|
test.execute(ReceiveFrame{
|
|
make_frame(remote_eth1,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, remote_eth1, "10.0.0.5", {}, "10.0.0.1").serialize()),
|
|
{}});
|
|
test.execute(ExpectFrame{make_frame(
|
|
local_eth,
|
|
remote_eth1,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REPLY, local_eth, "10.0.0.1", remote_eth1, "10.0.0.5").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
test.execute(Tick{15000});
|
|
|
|
// second ARP mapping
|
|
test.execute(ReceiveFrame{
|
|
make_frame(remote_eth2,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, remote_eth2, "10.0.0.19", {}, "10.0.0.1").serialize()),
|
|
{}});
|
|
test.execute(ExpectFrame{make_frame(
|
|
local_eth,
|
|
remote_eth2,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REPLY, local_eth, "10.0.0.1", remote_eth2, "10.0.0.19").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
test.execute(Tick{10000});
|
|
|
|
// outgoing datagram to first destination
|
|
const auto datagram = make_datagram("5.6.7.8", "13.12.11.10");
|
|
test.execute(SendDatagram{datagram, Address("10.0.0.5", 0)});
|
|
|
|
// outgoing datagram to second destination
|
|
const auto datagram2 = make_datagram("100.99.98.97", "4.10.4.10");
|
|
test.execute(SendDatagram{datagram2, Address("10.0.0.19", 0)});
|
|
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, remote_eth1, EthernetHeader::TYPE_IPv4, datagram.serialize())});
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, remote_eth2, EthernetHeader::TYPE_IPv4, datagram2.serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
test.execute(Tick{5010});
|
|
|
|
// outgoing datagram to second destination (mapping still alive)
|
|
const auto datagram3 = make_datagram("150.140.130.120", "144.144.144.144");
|
|
test.execute(SendDatagram{datagram3, Address("10.0.0.19", 0)});
|
|
test.execute(
|
|
ExpectFrame{make_frame(local_eth, remote_eth2, EthernetHeader::TYPE_IPv4, datagram3.serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
|
|
// outgoing datagram to second destination (mapping has expired)
|
|
const auto datagram4 = make_datagram("244.244.244.244", "3.3.3.3");
|
|
test.execute(SendDatagram{datagram4, Address("10.0.0.5", 0)});
|
|
test.execute(ExpectFrame{
|
|
make_frame(local_eth,
|
|
ETHERNET_BROADCAST,
|
|
EthernetHeader::TYPE_ARP,
|
|
make_arp(ARPMessage::OPCODE_REQUEST, local_eth, "10.0.0.1", {}, "10.0.0.5").serialize())});
|
|
test.execute(ExpectNoFrame{});
|
|
}
|
|
} catch (const exception &e) {
|
|
cerr << e.what() << endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|