/* * Copyright (c) 2020, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include 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 : public GenericTraits { static constexpr bool is_trivial() { return true; } static unsigned hash(DHCPOption u) { return int_hash((u8)u); } }; struct ParsedDHCPv4Options { template Optional get(DHCPOption option_name) const requires(IsTriviallyCopyable) { 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 Vector get_many(DHCPOption option_name, size_t max_number) const { Vector 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(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 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 m_op; NetworkOrdered m_htype; NetworkOrdered m_hlen; NetworkOrdered m_hops; NetworkOrdered m_xid; NetworkOrdered m_secs; NetworkOrdered 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 }; };