implement DHCP in IPStackSimulator

This commit is contained in:
Martin Michelsen
2023-10-13 00:10:58 -07:00
parent 01033287f2
commit 78698a0a89
6 changed files with 196 additions and 66 deletions
+156 -45
View File
@@ -67,8 +67,8 @@ IPStackSimulator::IPStackSimulator(
: base(base),
state(state),
pcap_text_log_file(state->ip_stack_debug ? fopen("IPStackSimulator-Log.txt", "wt") : nullptr) {
memset(this->host_mac_address_bytes, 0x90, 6);
memset(this->broadcast_mac_address_bytes, 0xFF, 6);
this->host_mac_address_bytes.clear(0x90);
this->broadcast_mac_address_bytes.clear(0xFF);
}
IPStackSimulator::~IPStackSimulator() {
@@ -115,7 +115,7 @@ uint32_t IPStackSimulator::connect_address_for_remote_address(uint32_t remote_ad
IPStackSimulator::IPClient::IPClient(struct bufferevent* bev)
: bev(bev, bufferevent_free),
ipv4_addr(0) {
memset(this->mac_addr, 0, 6);
this->mac_addr.clear(0);
}
static void flush_and_free_bufferevent(struct bufferevent* bev) {
@@ -233,16 +233,16 @@ void IPStackSimulator::on_client_error(struct bufferevent* bev,
void IPStackSimulator::on_client_frame(
shared_ptr<IPClient> c, const string& frame) {
if (ip_stack_simulator_log.info("Virtual network sent frame")) {
if (ip_stack_simulator_log.debug("Virtual network sent frame")) {
print_data(stderr, frame);
fputc('\n', stderr);
}
this->log_frame(frame);
FrameInfo fi(frame);
if (ip_stack_simulator_log.should_log(LogLevel::INFO)) {
if (ip_stack_simulator_log.should_log(LogLevel::DEBUG)) {
string fi_header = fi.header_str();
ip_stack_simulator_log.info("Frame header: %s", fi_header.c_str());
ip_stack_simulator_log.debug("Frame header: %s", fi_header.c_str());
}
if (fi.arp) {
@@ -255,10 +255,14 @@ void IPStackSimulator::on_client_frame(
"IPv4 header checksum is incorrect (%04hX expected, %04hX received)",
expected_ipv4_checksum, fi.ipv4->checksum.load()));
}
if (memcmp(fi.ether->src_mac, c->mac_addr, 6)) {
// Populate the client's addresses if needed
if (c->mac_addr.is_filled_with(0)) {
c->mac_addr = fi.ether->src_mac;
} else if ((fi.ether->src_mac != c->mac_addr) && (fi.ether->src_mac != this->broadcast_mac_address_bytes)) {
throw runtime_error("client sent IPv4 packet from different MAC address");
}
if (fi.ipv4->src_addr != c->ipv4_addr) {
if ((fi.ipv4->src_addr != c->ipv4_addr) && (fi.ipv4->src_addr != 0)) {
throw runtime_error("client sent IPv4 packet from different IPv4 address");
}
@@ -301,18 +305,14 @@ void IPStackSimulator::on_client_arp_frame(
throw runtime_error("ARP payload too small");
}
// Populate the client's addresses if needed
if (!memcmp(c->mac_addr, "\0\0\0\0\0\0", 6)) {
memcpy(c->mac_addr, fi.ether->src_mac, 6);
}
if (c->ipv4_addr == 0) {
c->ipv4_addr = *reinterpret_cast<const be_uint32_t*>(
reinterpret_cast<const uint8_t*>(fi.payload) + 6);
}
EthernetHeader r_ether;
memcpy(r_ether.dest_mac, fi.ether->src_mac, 6);
memcpy(r_ether.src_mac, this->host_mac_address_bytes, 6);
r_ether.dest_mac = fi.ether->src_mac;
r_ether.src_mac = this->host_mac_address_bytes;
r_ether.protocol = fi.ether->protocol;
ARPHeader r_arp;
@@ -336,7 +336,7 @@ void IPStackSimulator::on_client_arp_frame(
const char* payload_bytes = reinterpret_cast<const char*>(fi.payload);
uint8_t r_payload[20];
memcpy(&r_payload[0], this->host_mac_address_bytes, 6);
memcpy(&r_payload[0], this->host_mac_address_bytes.data(), 6);
memcpy(&r_payload[6], payload_bytes + 16, 4);
memcpy(&r_payload[10], payload_bytes, 10);
@@ -348,7 +348,7 @@ void IPStackSimulator::on_client_arp_frame(
evbuffer_add(out_buf, &r_arp, sizeof(r_arp));
evbuffer_add(out_buf, r_payload, sizeof(r_payload));
ip_stack_simulator_log.info("Sending ARP response");
ip_stack_simulator_log.debug("Sending ARP response");
if (this->pcap_text_log_file) {
StringWriter w;
@@ -361,17 +361,13 @@ void IPStackSimulator::on_client_arp_frame(
void IPStackSimulator::on_client_udp_frame(
shared_ptr<IPClient> c, const FrameInfo& fi) {
// We only implement the DNS server here
if (fi.udp->dest_port != 53) {
throw runtime_error("UDP packet is not DNS");
}
if (fi.payload_size < 0x0C) {
throw runtime_error("DNS payload too small");
}
// We only implement DHCP and newserv's DNS server here
// Every received UDP packet will elicit exactly one UDP response from
// newserv, so we prepare the response headers in advance
EthernetHeader r_ether;
memcpy(r_ether.dest_mac, fi.ether->src_mac, 6);
memcpy(r_ether.src_mac, this->host_mac_address_bytes, 6);
r_ether.dest_mac = fi.ether->src_mac;
r_ether.src_mac = this->host_mac_address_bytes;
r_ether.protocol = fi.ether->protocol;
IPv4Header r_ipv4;
@@ -392,10 +388,126 @@ void IPStackSimulator::on_client_udp_frame(
// r_udp.size filled in later
// r_udp.checksum filled in later
uint32_t resolved_address = this->connect_address_for_remote_address(c->ipv4_addr);
string r_data;
if (fi.udp->dest_port == 67) { // DHCP
StringReader r(fi.payload, fi.payload_size);
const auto& dhcp = r.get<DHCPHeader>();
if (dhcp.hardware_type != 1) {
throw runtime_error("unknown DHCP hardware type");
}
if (dhcp.hardware_address_length != 6) {
throw runtime_error("unknown DHCP hardware address length");
}
if (dhcp.magic != 0x63825363) {
throw runtime_error("incorrect DHCP magic cookie");
}
string r_data = DNSServer::response_for_query(
fi.payload, fi.payload_size, resolved_address);
unordered_map<uint8_t, string> option_data;
for (;;) {
uint8_t option = r.get_u8();
if (option == 0xFF) {
break;
}
uint8_t size = r.get_u8();
option_data.emplace(option, r.read(size));
}
if (dhcp.opcode == 1) { // Request
uint8_t command = 0;
try {
command = option_data.at(53).at(0);
} catch (const out_of_range&) {
throw runtime_error("client did not send a DHCP command option");
}
// Populate the client's addresses
c->mac_addr = dhcp.client_hardware_address.data();
c->ipv4_addr = 0x0A000105; // 10.0.1.5
// In this case, the client doesn't know its IPv4 address or ours yet,
// so we overwrite the existing fields with the appropriate addresses.
r_ipv4.src_addr = 0x0A000101; // 10.0.1.1
r_ipv4.dest_addr = c->ipv4_addr;
if ((command != 1) && (command != 3)) {
throw runtime_error("client sent unknown DHCP command option");
}
StringWriter w;
w.put(DHCPHeader{
.opcode = 2, // Response
.hardware_type = 1, // Ethernet
.hardware_address_length = 6, // Ethernet
.hops = 0,
.transaction_id = dhcp.transaction_id,
.seconds_elapsed = 0,
.flags = 0,
.client_ip_address = 0,
.your_ip_address = r_ipv4.dest_addr,
.server_ip_address = r_ipv4.src_addr,
.gateway_ip_address = 0,
.client_hardware_address = c->mac_addr,
.magic = 0x63825363,
});
// DHCP message type option
w.put_u8(53);
w.put_u8(1);
w.put_u8(static_cast<uint8_t>((command == 3) ? 5 : 2)); // Offer or ack
// DHCP server ID option
w.put_u8(54);
w.put_u8(4);
w.put_u32b(0x0A000101); // 10.0.1.1
// Lease time option
w.put_u8(51);
w.put_u8(4);
w.put_u32b(60 * 60 * 24 * 7); // 1 week
// Renewal time option
w.put_u8(58);
w.put_u8(4);
w.put_u32b(60 * 60 * 24 * 7); // 1 week
// Rebind time option
w.put_u8(59);
w.put_u8(4);
w.put_u32b(60 * 60 * 24 * 7); // 1 week
// Subnet mask option
w.put_u8(1);
w.put_u8(4);
w.put_u32b(0xFFFFFF00); // 255.255.255.0
// Broadcast IP option
w.put_u8(28);
w.put_u8(4);
w.put_u32b(c->ipv4_addr | 0x000000FF);
// DNS server option
w.put_u8(6);
w.put_u8(4);
w.put_u32b(0x0A000101); // 10.0.1.1
// Domain name option
w.put_u8(15);
w.put_u8(7);
w.write("newserv");
// Default gateway option
w.put_u8(3);
w.put_u8(4);
w.put_u32b(0x0A000101); // 10.0.1.1
// End option list
w.put_u8(0xFF);
r_data = std::move(w.str());
} else {
throw runtime_error("unknown DHCP command");
}
} else if (fi.udp->dest_port == 53) { // DNS
if (fi.payload_size < 0x0C) {
throw runtime_error("DNS payload too small");
}
uint32_t resolved_address = this->connect_address_for_remote_address(c->ipv4_addr);
r_data = DNSServer::response_for_query(fi.payload, fi.payload_size, resolved_address);
} else { // Not DHCP or DNS
throw runtime_error("UDP packet is not DHCP or DNS");
}
r_ipv4.size = sizeof(IPv4Header) + sizeof(UDPHeader) + r_data.size();
r_udp.size = sizeof(UDPHeader) + r_data.size();
@@ -405,9 +517,9 @@ void IPStackSimulator::on_client_udp_frame(
struct evbuffer* out_buf = bufferevent_get_output(c->bev.get());
if (ip_stack_simulator_log.should_log(LogLevel::INFO)) {
if (ip_stack_simulator_log.should_log(LogLevel::DEBUG)) {
string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port);
ip_stack_simulator_log.info("Sending DNS response to %s", remote_str.c_str());
ip_stack_simulator_log.debug("Sending UDP response to %s", remote_str.c_str());
print_data(stderr, r_data);
}
@@ -451,7 +563,7 @@ uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(const FrameInfo& fi) {
void IPStackSimulator::on_client_tcp_frame(
shared_ptr<IPClient> c, const FrameInfo& fi) {
ip_stack_simulator_log.info("Virtual network sent TCP frame (seq=%08" PRIX32 ", ack=%08" PRIX32 ")",
ip_stack_simulator_log.debug("Virtual network sent TCP frame (seq=%08" PRIX32 ", ack=%08" PRIX32 ")",
fi.tcp->seq_num.load(), fi.tcp->ack_num.load());
if (fi.tcp->flags & (TCPHeader::Flag::NS | TCPHeader::Flag::CWR | TCPHeader::Flag::ECE | TCPHeader::Flag::URG)) {
@@ -541,13 +653,13 @@ void IPStackSimulator::on_client_tcp_frame(
// TODO: We should check the syn/ack numbers here instead of just assuming
// they're correct
conn_str = this->str_for_tcp_connection(c, conn);
ip_stack_simulator_log.info("Client resent SYN for TCP connection %s",
ip_stack_simulator_log.debug("Client resent SYN for TCP connection %s",
conn_str.c_str());
}
// Send a SYN+ACK (send_tcp_frame always adds the ACK flag)
this->send_tcp_frame(c, conn, TCPHeader::Flag::SYN);
ip_stack_simulator_log.info("Sent SYN+ACK on %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ")",
ip_stack_simulator_log.debug("Sent SYN+ACK on %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ")",
conn_str.c_str(), conn.acked_server_seq, conn.next_client_seq);
} else {
@@ -562,7 +674,7 @@ void IPStackSimulator::on_client_tcp_frame(
bool conn_valid = true;
if (fi.tcp->flags & TCPHeader::Flag::ACK) {
ip_stack_simulator_log.info("Client sent ACK %08" PRIX32, fi.tcp->ack_num.load());
ip_stack_simulator_log.debug("Client sent ACK %08" PRIX32, fi.tcp->ack_num.load());
if (conn->awaiting_first_ack) {
if (fi.tcp->ack_num != conn->acked_server_seq + 1) {
throw runtime_error("first ack_num was not acked_server_seq + 1");
@@ -572,7 +684,7 @@ void IPStackSimulator::on_client_tcp_frame(
} else {
if (seq_num_greater(fi.tcp->ack_num, conn->acked_server_seq)) {
ip_stack_simulator_log.info("Advancing acked_server_seq from %08" PRIX32, conn->acked_server_seq);
ip_stack_simulator_log.debug("Advancing acked_server_seq from %08" PRIX32, conn->acked_server_seq);
uint32_t ack_delta = fi.tcp->ack_num - conn->acked_server_seq;
size_t pending_bytes = evbuffer_get_length(conn->pending_data.get());
if (pending_bytes < ack_delta) {
@@ -584,7 +696,7 @@ void IPStackSimulator::on_client_tcp_frame(
conn->resend_push_usecs = DEFAULT_RESEND_PUSH_USECS;
conn->next_push_max_frame_size = conn->max_frame_size;
ip_stack_simulator_log.info("Removed %08" PRIX32 " bytes from pending buffer and advanced acked_server_seq to %08" PRIX32,
ip_stack_simulator_log.debug("Removed %08" PRIX32 " bytes from pending buffer and advanced acked_server_seq to %08" PRIX32,
ack_delta, conn->acked_server_seq);
} else if (seq_num_less(fi.tcp->ack_num, conn->acked_server_seq)) {
@@ -662,10 +774,10 @@ void IPStackSimulator::on_client_tcp_frame(
bool was_logged;
if (payload_skip_bytes) {
was_logged = ip_stack_simulator_log.info("Client sent data on TCP connection %s, overlapping existing ack'ed data (0x%zX bytes ignored)",
was_logged = ip_stack_simulator_log.debug("Client sent data on TCP connection %s, overlapping existing ack'ed data (0x%zX bytes ignored)",
conn_str.c_str(), payload_skip_bytes);
} else {
was_logged = ip_stack_simulator_log.info("Client sent data on TCP connection %s",
was_logged = ip_stack_simulator_log.debug("Client sent data on TCP connection %s",
conn_str.c_str());
}
if (was_logged) {
@@ -688,7 +800,7 @@ void IPStackSimulator::on_client_tcp_frame(
// Send an ACK
this->send_tcp_frame(c, *conn);
ip_stack_simulator_log.info("Sent PSH ACK on %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ", bytes_received=0x%zX)",
ip_stack_simulator_log.debug("Sent PSH ACK on %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ", bytes_received=0x%zX)",
conn_str.c_str(), conn->acked_server_seq, conn->next_client_seq, conn->bytes_received);
}
@@ -758,7 +870,7 @@ void IPStackSimulator::send_pending_push_frame(
size_t bytes_to_send = min<size_t>(pending_bytes, conn.next_push_max_frame_size);
ip_stack_simulator_log.info("Sending PSH frame with seq_num %08" PRIX32 ", 0x%zX/0x%zX data bytes",
ip_stack_simulator_log.debug("Sending PSH frame with seq_num %08" PRIX32 ", 0x%zX/0x%zX data bytes",
conn.acked_server_seq, bytes_to_send, pending_bytes);
this->send_tcp_frame(c, conn, TCPHeader::Flag::PSH, conn.pending_data.get(),
@@ -790,8 +902,8 @@ void IPStackSimulator::send_tcp_frame(
}
EthernetHeader ether;
memcpy(ether.dest_mac, c->mac_addr, 6);
memcpy(ether.src_mac, this->host_mac_address_bytes, 6);
ether.dest_mac = c->mac_addr;
ether.src_mac = this->host_mac_address_bytes;
ether.protocol = 0x0800; // IPv4
IPv4Header ipv4;
@@ -870,7 +982,7 @@ void IPStackSimulator::dispatch_on_server_input(struct bufferevent*, void* ctx)
void IPStackSimulator::on_server_input(shared_ptr<IPClient> c, IPClient::TCPConnection& conn) {
struct evbuffer* buf = bufferevent_get_input(conn.server_bev.get());
ip_stack_simulator_log.info("Server input event: 0x%zX bytes to read",
ip_stack_simulator_log.debug("Server input event: 0x%zX bytes to read",
evbuffer_get_length(buf));
evbuffer_add_buffer(conn.pending_data.get(), buf);
@@ -904,8 +1016,7 @@ void IPStackSimulator::on_server_error(
// Delete the connection object (this also flushes and frees the server
// virtual connection bufferevent)
string conn_str = this->str_for_tcp_connection(c, conn);
ip_stack_simulator_log.info("Server closed TCP connection %s",
conn_str.c_str());
ip_stack_simulator_log.info("Server closed TCP connection %s", conn_str.c_str());
c->tcp_connections.erase(this->tcp_conn_key_for_connection(conn));
}
}