aboutsummaryrefslogtreecommitdiff
path: root/lib/dhcp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dhcp')
-rw-r--r--lib/dhcp/dhcp.cc36
-rw-r--r--lib/dhcp/dhcp.h116
-rw-r--r--lib/dhcp/lease_db.h111
-rw-r--r--lib/dhcp/types.h14
-rw-r--r--lib/dhcp/utils.h31
5 files changed, 308 insertions, 0 deletions
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<struct option_view> 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<dhcp_option>(*opt++);
+
+ if (tag == dhcp_option::END) {
+ break;
+ }
+
+ // Extract length of current option.
+ usize len = *opt++;
+
+ if (tag == search_tag) {
+ if (static_cast<usize>(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 <optional>
+
+// -- 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<struct option_view> get_option(const u8* opt, usize len, dhcp_option search_tag);
+
+template<typename T>
+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<typename T>
+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 <array>
+#include <optional>
+
+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<usize LEASES>
+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<usize> 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<usize> 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<lease, LEASES> 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 <cstddef>
+#include <cstdint>
+
+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 <type_traits>
+
+// Convert from an underlying enum type into an enum variant.
+template<typename E>
+constexpr E from_raw(std::underlying_type_t<E> u) {
+ static_assert(std::is_enum_v<E>);
+ return static_cast<E>(u);
+}
+
+// Convert from an enum variant into an underlying enum type.
+template<typename E>
+constexpr std::underlying_type_t<E> into_raw(E e) {
+ static_assert(std::is_enum_v<E>);
+ return static_cast<std::underlying_type_t<E>>(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