ladybird/Userland/Services/DHCPClient/DHCPv4.h
Andreas Kling 765ed9677b DHCPClient: Simplify DHCPv4PacketBuilder
Instead of creating a temporary ByteBuffer to hold the packet while
building it, we can just create the DHCPv4Packet struct directly.
2021-10-25 14:31:11 +02:00

280 lines
7.3 KiB
C++

/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Assertions.h>
#include <AK/Endian.h>
#include <AK/HashMap.h>
#include <AK/IPv4Address.h>
#include <AK/MACAddress.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Traits.h>
#include <AK/Types.h>
#include <string.h>
enum class DHCPv4Flags : u16 {
Broadcast = 1 << 15,
/* everything else is reserved and must be zero */
};
enum class DHCPv4Op : u8 {
BootRequest = 1,
BootReply = 2
};
enum class DHCPOption : u8 {
// BOOTP
Pad = 0,
SubnetMask,
TimeOffset,
Router,
TimeServer,
NameServer,
DomainNameServer,
LogServer,
CookieServer,
LPRServer,
ImpressServer,
ResourceLocationServer,
HostName,
BootFileSize,
MeritDumpFile,
DomainName,
SwapServer,
RootPath,
ExtensionsPath,
IPForwardingEnableDisable,
NonLocalSourceRoutingEnableDisable,
PolicyFilter,
MaximumDatagramReassemblySize,
DefaultIPTTL,
PathMTUAgingTimeout,
PathMTUPlateauTable,
InterfaceMTU,
AllSubnetsAreLocal,
BroadcastAddress,
PerformMaskDiscovery,
MaskSupplier,
PerformRouterDiscovery,
RouterSolicitationAddress,
StaticRoute,
TrailerEncapsulation,
ARPCacheTimeout,
EthernetEncapsulation,
TCPDefaultTTL,
TCPKeepaliveInterval,
TCPKeepaliveGarbage,
NetworkInformationServiceDomain,
NetworkInformationServers,
NetworkTimeProtocolServers,
VendorSpecificInformation,
NetBIOSOverTCPIPNameServer,
NetBIOSOverTCPIPDatagramDistributionServer,
NetBIOSOverTCPIPNodeType,
NetBIOSOverTCPIPScope,
XWindowSystemFontServer, // wow
XWindowSystemDisplayManager,
// DHCP
RequestedIPAddress = 50,
IPAddressLeaseTime,
OptionOverload,
DHCPMessageType,
ServerIdentifier,
ParameterRequestList,
Message,
MaximumDHCPMessageSize,
RenewalT1Time,
RenewalT2Time,
ClassIdentifier,
ClientIdentifier,
End = 255
};
enum class DHCPMessageType : u8 {
DHCPDiscover = 1,
DHCPOffer,
DHCPRequest,
DHCPDecline,
DHCPAck,
DHCPNak,
DHCPRelease,
};
template<>
struct AK::Traits<DHCPOption> : public GenericTraits<DHCPOption> {
static constexpr bool is_trivial() { return true; }
static unsigned hash(DHCPOption u) { return int_hash((u8)u); }
};
struct ParsedDHCPv4Options {
template<typename T>
Optional<const T> get(DHCPOption option_name) const requires(IsTriviallyCopyable<T>)
{
auto option = options.get(option_name);
if (!option.has_value()) {
return {};
}
auto& value = option.value();
if (value.length != sizeof(T))
return {};
T t;
__builtin_memcpy(&t, value.value, value.length);
return t;
}
template<typename T>
Vector<T> get_many(DHCPOption option_name, size_t max_number) const
{
Vector<T> values;
auto option = options.get(option_name);
if (!option.has_value()) {
return {};
}
auto& value = option.value();
if (value.length < sizeof(T))
return {};
for (size_t i = 0; i < max_number; ++i) {
auto offset = i * sizeof(T);
if (offset >= value.length)
break;
values.append(*(T*)((u8*)const_cast<void*>(value.value) + offset));
}
return values;
}
String to_string() const
{
StringBuilder builder;
builder.append("DHCP Options (");
builder.appendff("{}", options.size());
builder.append(" entries)\n");
for (auto& opt : options) {
builder.appendff("\toption {} ({} bytes):", (u8)opt.key, (u8)opt.value.length);
for (auto i = 0; i < opt.value.length; ++i)
builder.appendff(" {} ", ((const u8*)opt.value.value)[i]);
builder.append('\n');
}
return builder.build();
}
struct DHCPOptionValue {
u8 length;
const void* value;
};
HashMap<DHCPOption, DHCPOptionValue> options;
};
constexpr auto DHCPV4_OPTION_FIELD_MAX_LENGTH = 312;
class [[gnu::packed]] DHCPv4Packet {
public:
u8 op() const { return m_op; }
void set_op(DHCPv4Op op) { m_op = (u8)op; }
u8 htype() const { return m_htype; }
void set_htype(u8 htype) { m_htype = htype; }
u8 hlen() const { return m_hlen; }
void set_hlen(u8 hlen) { m_hlen = hlen; }
u8 hops() const { return m_hops; }
void set_hops(u8 hops) { m_hops = hops; }
u32 xid() const { return m_xid; }
void set_xid(u32 xid) { m_xid = xid; }
u16 secs() const { return m_secs; }
void set_secs(u16 secs) { m_secs = secs; }
u16 flags() const { return m_flags; }
void set_flags(DHCPv4Flags flags) { m_flags = (u16)flags; }
const IPv4Address& ciaddr() const { return m_ciaddr; }
const IPv4Address& yiaddr() const { return m_yiaddr; }
const IPv4Address& siaddr() const { return m_siaddr; }
const IPv4Address& giaddr() const { return m_giaddr; }
IPv4Address& ciaddr() { return m_ciaddr; }
IPv4Address& yiaddr() { return m_yiaddr; }
IPv4Address& siaddr() { return m_siaddr; }
IPv4Address& giaddr() { return m_giaddr; }
u8* options() { return m_options; }
ParsedDHCPv4Options parse_options() const;
const MACAddress& chaddr() const { return *(const MACAddress*)&m_chaddr[0]; }
void set_chaddr(const MACAddress& mac) { *(MACAddress*)&m_chaddr[0] = mac; }
StringView sname() const { return { (const char*)&m_sname[0] }; }
StringView file() const { return { (const char*)&m_file[0] }; }
private:
NetworkOrdered<u8> m_op;
NetworkOrdered<u8> m_htype;
NetworkOrdered<u8> m_hlen;
NetworkOrdered<u8> m_hops;
NetworkOrdered<u32> m_xid;
NetworkOrdered<u16> m_secs;
NetworkOrdered<u16> m_flags;
IPv4Address m_ciaddr;
IPv4Address m_yiaddr;
IPv4Address m_siaddr;
IPv4Address m_giaddr;
u8 m_chaddr[16]; // 10 bytes of padding at the end
u8 m_sname[64] { 0 };
u8 m_file[128] { 0 };
u8 m_options[DHCPV4_OPTION_FIELD_MAX_LENGTH] { 0 }; // variable, less than 312 bytes
};
class DHCPv4PacketBuilder {
public:
DHCPv4PacketBuilder()
{
auto* options = m_packet.options();
// set the magic DHCP cookie value
options[0] = 99;
options[1] = 130;
options[2] = 83;
options[3] = 99;
}
void add_option(DHCPOption option, u8 length, const void* data)
{
VERIFY(m_can_add);
// we need enough space to fit the option value, its length, and its data
VERIFY(next_option_offset + length + 2 < DHCPV4_OPTION_FIELD_MAX_LENGTH);
auto* options = m_packet.options();
options[next_option_offset++] = (u8)option;
memcpy(options + next_option_offset, &length, 1);
next_option_offset++;
if (data && length)
memcpy(options + next_option_offset, data, length);
next_option_offset += length;
}
void set_message_type(DHCPMessageType type) { add_option(DHCPOption::DHCPMessageType, 1, &type); }
DHCPv4Packet& peek() { return m_packet; }
DHCPv4Packet& build()
{
add_option(DHCPOption::End, 0, nullptr);
m_can_add = false;
return m_packet;
}
private:
DHCPv4Packet m_packet;
size_t next_option_offset { 4 };
bool m_can_add { true };
};