From f9928a1a08c57fe853888119a996c3acc98ee09d Mon Sep 17 00:00:00 2001 From: Johannes Stoelp Date: Fri, 14 Jan 2022 23:51:05 +0100 Subject: Initial version of nodemcuv2 dhcp server Able to offer IP address + DNS/Gateway ... Worked with devices at my hand. --- lib/dhcp/dhcp.cc | 36 ++++++++++++++++ lib/dhcp/dhcp.h | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/dhcp/lease_db.h | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/dhcp/types.h | 14 +++++++ lib/dhcp/utils.h | 31 ++++++++++++++ 5 files changed, 308 insertions(+) create mode 100644 lib/dhcp/dhcp.cc create mode 100644 lib/dhcp/dhcp.h create mode 100644 lib/dhcp/lease_db.h create mode 100644 lib/dhcp/types.h create mode 100644 lib/dhcp/utils.h (limited to 'lib') diff --git a/lib/dhcp/dhcp.cc b/lib/dhcp/dhcp.cc new file mode 100644 index 0000000..1e5f785 --- /dev/null +++ b/lib/dhcp/dhcp.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2022 Johannes Stoelp + +#include "dhcp.h" +#include "utils.h" + +std::optional get_option(const u8* opt, usize len, dhcp_option search_tag) { + const u8* end = opt + len; + + while (opt < end) { + // Get tag of current option. + dhcp_option tag = from_raw(*opt++); + + if (tag == dhcp_option::END) { + break; + } + + // Extract length of current option. + usize len = *opt++; + + if (tag == search_tag) { + if (static_cast(end - opt) < len) { + // If length is malformed. + return std::nullopt; + } else { + // Option found. + return {{opt, len}}; + } + } + + // Advance option iterator to beginning of next option. + opt = opt + len; + } + + // Option not found. + return std::nullopt; +} diff --git a/lib/dhcp/dhcp.h b/lib/dhcp/dhcp.h new file mode 100644 index 0000000..ce904f0 --- /dev/null +++ b/lib/dhcp/dhcp.h @@ -0,0 +1,116 @@ +// Copyright (c) 2022 Johannes Stoelp +// +// dhcp protocol: https://datatracker.ietf.org/doc/html/rfc2131 +// dhcp options : https://datatracker.ietf.org/doc/html/rfc2132 + +#ifndef DHCP_H +#define DHCP_H + +#include "types.h" + +#include + +// -- Global constants. + +constexpr u16 DHCP_SERVER_PORT = 67; +constexpr u16 DHCP_CLIENT_PORT = 68; + +constexpr usize DHCP_MESSAGE_LEN = 576; +constexpr usize DHCP_MESSAGE_MIN_LEN = 243; + +constexpr u32 DHCP_OPTION_COOKIE = 0x63538263; + +enum class dhcp_operation : u8 { + BOOTREQUEST = 1, + BOOTREPLY, +}; + +enum class dhcp_option : u8 { + PAD = 0, + END = 255, + + // Vendor Extensions. + SUBNET_MASK = 1, + ROUTER = 3, + DNS = 6, + + // IP Layer Parameters per Interface. + BROADCAST_ADDR = 28, + + // DHCP Extensions. + REQUESTED_IP = 50, + IP_ADDRESS_LEASE_TIME, + OPTION_OVERLOAD, + DHCP_MESSAGE_TYPE, + SERVER_IDENTIFIER, + PARAMETER_REQUEST_LIST, + MESSAGE, + MAX_DHCP_MESSAGE_SIZE, + RENEWAL_TIME_T1, + REBINDING_TIME_T2, + CLASS_ID, + CLIENT_ID, +}; + +// -- DHCP message. + +enum class dhcp_message_type : u8 { + DHCP_DISCOVER = 1, + DHCP_OFFER, + DHCP_REQUEST, + DHCP_DECLINE, + DHCP_ACK, + DHCP_NAK, + DHCP_RELEAE, +}; + +struct dhcp_message { + dhcp_operation op; + u8 htype; + u8 hlen; + u8 hops; + u32 xid; + u16 secs; + u16 flags; + u32 ciaddr; + u32 yiaddr; + u32 siaddr; + u32 giaddr; + u8 chaddr[16]; + u8 sname[64]; + u8 file[128]; + u32 cookie; + u8 options[312 - 4 /* cookie len */]; +} __attribute__((packed)); + +static_assert(DHCP_MESSAGE_MIN_LEN == offsetof(dhcp_message, options) + 3 /* DHCP_MESSAGE_TYPE size */, + "Declare min size as dhcp_message with the DHCP_MESSAGE_TYPE option."); + +// -- DHCP Utilities. + +struct option_view { + const u8* data; + usize len; +}; + +// Search for the option with tag 'search_tag' in the options 'opt' of length 'len'. +std::optional get_option(const u8* opt, usize len, dhcp_option search_tag); + +template +constexpr T get_opt_val(const u8* opt_data) { + T ret = 0; + for (size_t i = 0; i < sizeof(T); ++i) { + ret = (ret << 8) | *opt_data++; + } + return ret; +} + +template +u8* put_opt_val(u8* opt_data, T val) { + for (size_t i = sizeof(T); i; --i) { + *opt_data++ = (val >> ((i - 1) * 8)) & 0xff; + } + return opt_data; +} + +#endif diff --git a/lib/dhcp/lease_db.h b/lib/dhcp/lease_db.h new file mode 100644 index 0000000..86b959f --- /dev/null +++ b/lib/dhcp/lease_db.h @@ -0,0 +1,111 @@ +// Copyright (c) 2022 Johannes Stoelp + +#ifndef LEASE_DB_H +#define LEASE_DB_H + +#include "types.h" + +#include +#include + +struct lease { + u32 client_hash; + usize lease_end; +}; + +// Lease database, for managing client leases, which includes +// - allocation of new leases +// - lookup of existing leases +// - update of existing leases +// - flushing of expired leases +// +// The database supports 'LEASE' number of clients. +// +// The lease APIs return an idx which represents the allocated client lease and +// should be interpreted as offset to the start address of the dhcp address +// range. +// +// For example, the dhcp server starts to assing addresses starting with +// 10.0.0.100, and the lease database returns idx=4, this would represent the +// allocated client address 10.0.0.104. +template +class lease_db { + public: + constexpr lease_db() = default; + + lease_db(const lease_db&) = delete; + lease_db& operator=(const lease_db&) = delete; + + // Try to allocate a new client lease for the client represented by + // 'client_hash'. + // + // If a lease could be allocated return the corresponding idx. + // + // If a lease for the client already exist or all leases are allocated, + // return nullopt. + // + // 'lease_end' sets the expiration time of the lease (should be absolute time). + std::optional new_lease(u32 client_hash, usize lease_end) { + if (get_lease(client_hash)) { + return std::nullopt; + } + + for (usize l = 0; client_hash != 0 && l < LEASES; ++l) { + if (leases[l].client_hash == 0) { + leases[l].client_hash = client_hash; + leases[l].lease_end = lease_end; + return l; + } + } + return std::nullopt; + } + + // Try to get the lease for the client if it exists. + std::optional get_lease(u32 client_hash) const { + for (usize l = 0; client_hash != 0 && l < LEASES; ++l) { + if (leases[l].client_hash == client_hash) { + return l; + } + } + return std::nullopt; + } + + // Update expiration time for client if the client has an allocated lease. + // Similar to 'new_lease' the 'lease_end' should be an absolute time value. + bool update_lease(u32 client_hash, usize lease_end) { + for (usize l = 0; client_hash != 0 && l < LEASES; ++l) { + if (leases[l].client_hash == client_hash) { + leases[l].lease_end = lease_end; + return true; + } + } + return false; + } + + // Check for expired leases and free them accordingly. + // 'curr_time' should be the current time as absolute time value. + void flush_expired(usize curr_time) { + for (lease& l : leases) { + if (l.lease_end <= curr_time) { + l.client_hash = 0; + l.lease_end = 0; + } + } + } + + // Get the number of active leases. + usize active_leases() const { + usize cnt = 0; + for (const lease& l : leases) { + if (l.client_hash != 0) { + ++cnt; + } + } + return cnt; + } + + private: + std::array leases = {0, 0}; +}; + +#endif diff --git a/lib/dhcp/types.h b/lib/dhcp/types.h new file mode 100644 index 0000000..049de0f --- /dev/null +++ b/lib/dhcp/types.h @@ -0,0 +1,14 @@ +// Copyright (c) 2022 Johannes Stoelp + +#ifndef TYPES_H +#define TYPES_H + +#include +#include + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using usize = size_t; + +#endif diff --git a/lib/dhcp/utils.h b/lib/dhcp/utils.h new file mode 100644 index 0000000..9016007 --- /dev/null +++ b/lib/dhcp/utils.h @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Johannes Stoelp + +#ifndef UTILS_H +#define UTILS_H + +#include + +// Convert from an underlying enum type into an enum variant. +template +constexpr E from_raw(std::underlying_type_t u) { + static_assert(std::is_enum_v); + return static_cast(u); +} + +// Convert from an enum variant into an underlying enum type. +template +constexpr std::underlying_type_t into_raw(E e) { + static_assert(std::is_enum_v); + return static_cast>(e); +} + +// Simple cyclic rotation hash function. +constexpr u32 hash(const u8* data, usize len) { + u32 hash = 0xa5a55a5a /* seed */; + for (usize i = 0; i < len; ++i) { + hash += ((hash << 25) | (hash >> 7) /* rrot(7) */) ^ data[i]; + } + return hash; +} + +#endif -- cgit v1.2.3