summaryrefslogtreecommitdiffstats
path: root/src/VBox/NetworkServices/Dhcpd
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/NetworkServices/Dhcpd
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/NetworkServices/Dhcpd')
-rw-r--r--src/VBox/NetworkServices/Dhcpd/ClientId.cpp126
-rw-r--r--src/VBox/NetworkServices/Dhcpd/ClientId.h93
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Config.cpp1395
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Config.h401
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DHCPD.cpp420
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DHCPD.h88
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Db.cpp1060
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Db.h221
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp447
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpMessage.h161
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp515
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpOptions.h832
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpdInternal.h100
-rw-r--r--src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp209
-rw-r--r--src/VBox/NetworkServices/Dhcpd/IPv4Pool.h154
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Makefile.kmk108
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Timestamp.cpp45
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Timestamp.h122
-rw-r--r--src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp533
-rw-r--r--src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp35
-rw-r--r--src/VBox/NetworkServices/Dhcpd/lwipopts.h191
21 files changed, 7256 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/Dhcpd/ClientId.cpp b/src/VBox/NetworkServices/Dhcpd/ClientId.cpp
new file mode 100644
index 00000000..a0846b7b
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/ClientId.cpp
@@ -0,0 +1,126 @@
+/* $Id: ClientId.cpp $ */
+/** @file
+ * DHCP server - client identifier
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <algorithm>
+#include "ClientId.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Indiciates wherther ClientId::rtStrFormat was already registered. */
+bool ClientId::g_fFormatRegistered = false;
+
+
+/**
+ * Registers the ClientId format type callback ("%R[id]").
+ */
+void ClientId::registerFormat() RT_NOEXCEPT
+{
+ if (!g_fFormatRegistered)
+ {
+ int rc = RTStrFormatTypeRegister("id", rtStrFormat, NULL);
+ AssertRC(rc);
+ g_fFormatRegistered = RT_SUCCESS(rc);
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRFORMATTYPE, Formats ClientId via "%R[id]". }
+ */
+DECLCALLBACK(size_t)
+ClientId::rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char *pszType, void const *pvValue,
+ int cchWidth, int cchPrecision, unsigned fFlags,
+ void *pvUser)
+{
+ RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser);
+ Assert(strcmp(pszType, "id") == 0);
+
+ const ClientId *pThis = static_cast<const ClientId *>(pvValue);
+ if (pThis == NULL)
+ return pfnOutput(pvArgOutput, RT_STR_TUPLE("<NULL>"));
+
+ size_t cb = 0;
+ if (pThis->m_id.present())
+ {
+ cb += pfnOutput(pvArgOutput, RT_STR_TUPLE("["));
+
+ const OptClientId::value_t &idopt = pThis->m_id.value();
+ for (size_t i = 0; i < idopt.size(); ++i)
+ cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%s%02x", (i == 0 ? "" : ":"), idopt[i]);
+
+ cb += pfnOutput(pvArgOutput, RT_STR_TUPLE("] ("));
+ }
+
+ cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%RTmac", &pThis->m_mac);
+
+ if (pThis->m_id.present())
+ cb += pfnOutput(pvArgOutput, RT_STR_TUPLE(")"));
+
+ return cb;
+}
+
+
+bool operator==(const ClientId &l, const ClientId &r) RT_NOEXCEPT
+{
+ if (l.m_id.present())
+ {
+ if (r.m_id.present())
+ return l.m_id.value() == r.m_id.value();
+ }
+ else
+ {
+ if (!r.m_id.present())
+ return l.m_mac == r.m_mac;
+ }
+
+ return false;
+}
+
+
+bool operator<(const ClientId &l, const ClientId &r) RT_NOEXCEPT
+{
+ if (l.m_id.present())
+ {
+ if (r.m_id.present())
+ return l.m_id.value() < r.m_id.value();
+ return false; /* the one with id comes last */
+ }
+ else
+ {
+ if (r.m_id.present())
+ return true; /* the one with id comes last */
+ return l.m_mac < r.m_mac;
+ }
+}
+
diff --git a/src/VBox/NetworkServices/Dhcpd/ClientId.h b/src/VBox/NetworkServices/Dhcpd/ClientId.h
new file mode 100644
index 00000000..3d5bcd16
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/ClientId.h
@@ -0,0 +1,93 @@
+/* $Id: ClientId.h $ */
+/** @file
+ * DHCP server - client identifier
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_ClientId_h
+#define VBOX_INCLUDED_SRC_Dhcpd_ClientId_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DhcpdInternal.h"
+#include <iprt/net.h>
+#include "DhcpOptions.h"
+
+/**
+ * A client is identified by either the Client ID option it sends or its chaddr,
+ * i.e. MAC address.
+ */
+class ClientId
+{
+ /** The mac address of the client. */
+ RTMAC m_mac;
+ /** The client ID. */
+ OptClientId m_id;
+
+public:
+ ClientId()
+ : m_mac(), m_id()
+ {}
+ /** @throws std::bad_alloc */
+ ClientId(const RTMAC &a_mac, const OptClientId &a_id)
+ : m_mac(a_mac), m_id(a_id)
+ {}
+ /** @throws std::bad_alloc */
+ ClientId(const ClientId &a_rThat)
+ : m_mac(a_rThat.m_mac), m_id(a_rThat.m_id)
+ {}
+ /** @throws std::bad_alloc */
+ ClientId &operator=(const ClientId &a_rThat)
+ {
+ m_mac = a_rThat.m_mac;
+ m_id = a_rThat.m_id;
+ return *this;
+ }
+
+ const RTMAC &mac() const RT_NOEXCEPT { return m_mac; }
+ const OptClientId &id() const RT_NOEXCEPT { return m_id; }
+
+ /** @name String formatting of %R[id].
+ * @{ */
+ static void registerFormat() RT_NOEXCEPT;
+private:
+ static DECLCALLBACK(size_t) rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, const char *pszType,
+ void const *pvValue, int cchWidth, int cchPrecision, unsigned fFlags, void *pvUser);
+ static bool g_fFormatRegistered;
+ /** @} */
+
+ friend bool operator==(const ClientId &l, const ClientId &r) RT_NOEXCEPT;
+ friend bool operator<(const ClientId &l, const ClientId &r) RT_NOEXCEPT;
+};
+
+bool operator==(const ClientId &l, const ClientId &r) RT_NOEXCEPT;
+bool operator<(const ClientId &l, const ClientId &r) RT_NOEXCEPT;
+
+inline bool operator!=(const ClientId &l, const ClientId &r) RT_NOEXCEPT
+{
+ return !(l == r);
+}
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_ClientId_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/Config.cpp b/src/VBox/NetworkServices/Dhcpd/Config.cpp
new file mode 100644
index 00000000..bbfc9356
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/Config.cpp
@@ -0,0 +1,1395 @@
+/* $Id: Config.cpp $ */
+/** @file
+ * DHCP server - server configuration
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "DhcpdInternal.h"
+
+#include <iprt/ctype.h>
+#include <iprt/net.h> /* NB: must come before getopt.h */
+#include <iprt/getopt.h>
+#include <iprt/path.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/cpp/path.h>
+
+#include <VBox/com/utils.h> /* For log initialization. */
+
+#include "Config.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/*static*/ bool Config::g_fInitializedLog = false;
+/*static*/ uint32_t GroupConfig::s_uGroupNo = 0;
+
+
+/**
+ * Configuration file exception.
+ */
+class ConfigFileError
+ : public RTCError
+{
+public:
+#if 0 /* This just confuses the compiler. */
+ ConfigFileError(const char *a_pszMessage)
+ : RTCError(a_pszMessage)
+ {}
+#endif
+
+ explicit ConfigFileError(xml::Node const *pNode, const char *a_pszMsgFmt, ...)
+ : RTCError((char *)NULL)
+ {
+
+ i_buildPath(pNode);
+ m_strMsg.append(": ");
+
+ va_list va;
+ va_start(va, a_pszMsgFmt);
+ m_strMsg.appendPrintfV(a_pszMsgFmt, va);
+ va_end(va);
+ }
+
+
+ ConfigFileError(const char *a_pszMsgFmt, ...)
+ : RTCError((char *)NULL)
+ {
+ va_list va;
+ va_start(va, a_pszMsgFmt);
+ m_strMsg.printfV(a_pszMsgFmt, va);
+ va_end(va);
+ }
+
+ ConfigFileError(const RTCString &a_rstrMessage)
+ : RTCError(a_rstrMessage)
+ {}
+
+private:
+ void i_buildPath(xml::Node const *pNode)
+ {
+ if (pNode)
+ {
+ i_buildPath(pNode->getParent());
+ m_strMsg.append('/');
+ m_strMsg.append(pNode->getName());
+ if (pNode->isElement() && pNode->getParent())
+ {
+ xml::ElementNode const *pElm = (xml::ElementNode const *)pNode;
+ for (xml::Node const *pAttrib = pElm->getFirstAttribute(); pAttrib != NULL;
+ pAttrib = pAttrib->getNextSibiling())
+ if (pAttrib->isAttribute())
+ {
+ m_strMsg.append("[@");
+ m_strMsg.append(pAttrib->getName());
+ m_strMsg.append('=');
+ m_strMsg.append(pAttrib->getValue());
+ m_strMsg.append(']');
+ }
+ }
+ }
+ }
+
+};
+
+
+/**
+ * Private default constructor, external users use factor methods.
+ */
+Config::Config()
+ : m_strHome()
+ , m_strNetwork()
+ , m_strTrunk()
+ , m_enmTrunkType(kIntNetTrunkType_Invalid)
+ , m_MacAddress()
+ , m_IPv4Address()
+ , m_IPv4Netmask()
+ , m_IPv4PoolFirst()
+ , m_IPv4PoolLast()
+ , m_GlobalConfig()
+ , m_GroupConfigs()
+ , m_HostConfigs()
+{
+}
+
+
+/**
+ * Initializes the object.
+ *
+ * @returns IPRT status code.
+ */
+int Config::i_init() RT_NOEXCEPT
+{
+ return i_homeInit();
+}
+
+
+/**
+ * Initializes the m_strHome member with the path to ~/.VirtualBox or equivalent.
+ *
+ * @returns IPRT status code.
+ * @todo Too many init functions?
+ */
+int Config::i_homeInit() RT_NOEXCEPT
+{
+ char szHome[RTPATH_MAX];
+ int rc = com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome), false);
+ if (RT_SUCCESS(rc))
+ rc = m_strHome.assignNoThrow(szHome);
+ else
+ DHCP_LOG_MSG_ERROR(("unable to locate the VirtualBox home directory: %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Internal worker for the public factory methods that creates an instance and
+ * calls i_init() on it.
+ *
+ * @returns Config instance on success, NULL on failure.
+ */
+/*static*/ Config *Config::i_createInstanceAndCallInit() RT_NOEXCEPT
+{
+ Config *pConfig;
+ try
+ {
+ pConfig = new Config();
+ }
+ catch (std::bad_alloc &)
+ {
+ return NULL;
+ }
+
+ int rc = pConfig->i_init();
+ if (RT_SUCCESS(rc))
+ return pConfig;
+ delete pConfig;
+ return NULL;
+}
+
+
+/**
+ * Worker for i_complete() that initializes the release log of the process.
+ *
+ * Requires network name to be known as the log file name depends on
+ * it. Alternatively, consider passing the log file name via the
+ * command line?
+ *
+ * @note This is only used when no --log parameter was given.
+ */
+int Config::i_logInit() RT_NOEXCEPT
+{
+ if (g_fInitializedLog)
+ return VINF_SUCCESS;
+
+ if (m_strHome.isEmpty() || m_strNetwork.isEmpty())
+ return VERR_PATH_ZERO_LENGTH;
+
+ /* default log file name */
+ char szLogFile[RTPATH_MAX];
+ ssize_t cch = RTStrPrintf2(szLogFile, sizeof(szLogFile),
+ "%s%c%s-Dhcpd.log",
+ m_strHome.c_str(), RTPATH_DELIMITER, m_strNetwork.c_str());
+ if (cch > 0)
+ {
+ RTPathPurgeFilename(RTPathFilename(szLogFile), RTPATH_STR_F_STYLE_HOST);
+ return i_logInitWithFilename(szLogFile);
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+/**
+ * Worker for i_logInit and for handling --log on the command line.
+ *
+ * @returns IPRT status code.
+ * @param pszFilename The log filename.
+ */
+/*static*/ int Config::i_logInitWithFilename(const char *pszFilename) RT_NOEXCEPT
+{
+ AssertReturn(!g_fInitializedLog, VERR_WRONG_ORDER);
+
+ int rc = com::VBoxLogRelCreate("DHCP Server",
+ pszFilename,
+ RTLOGFLAGS_PREFIX_TIME_PROG,
+ "all net_dhcpd.e.l.f.l3.l4.l5.l6",
+ "VBOXDHCP_RELEASE_LOG",
+ RTLOGDEST_FILE
+#ifdef DEBUG
+ | RTLOGDEST_STDERR
+#endif
+ ,
+ 32768 /* cMaxEntriesPerGroup */,
+ 5 /* cHistory */,
+ RT_SEC_1DAY /* uHistoryFileTime */,
+ _32M /* uHistoryFileSize */,
+ NULL /* pErrInfo */);
+ if (RT_SUCCESS(rc))
+ g_fInitializedLog = true;
+ else
+ RTMsgError("Log initialization failed: %Rrc, log file '%s'", rc, pszFilename);
+ return rc;
+
+}
+
+
+/**
+ * Post process and validate the configuration after it has been loaded.
+ */
+int Config::i_complete() RT_NOEXCEPT
+{
+ if (m_strNetwork.isEmpty())
+ {
+ LogRel(("network name is not specified\n"));
+ return false;
+ }
+
+ i_logInit();
+
+ /** @todo the MAC address is always generated, no XML config option for it ... */
+ bool fMACGenerated = false;
+ if ( m_MacAddress.au16[0] == 0
+ && m_MacAddress.au16[1] == 0
+ && m_MacAddress.au16[2] == 0)
+ {
+ RTUUID Uuid;
+ int rc = RTUuidCreate(&Uuid);
+ AssertRCReturn(rc, rc);
+
+ m_MacAddress.au8[0] = 0x08;
+ m_MacAddress.au8[1] = 0x00;
+ m_MacAddress.au8[2] = 0x27;
+ m_MacAddress.au8[3] = Uuid.Gen.au8Node[3];
+ m_MacAddress.au8[4] = Uuid.Gen.au8Node[4];
+ m_MacAddress.au8[5] = Uuid.Gen.au8Node[5];
+
+ LogRel(("MAC address is not specified: will use generated MAC %RTmac\n", &m_MacAddress));
+ fMACGenerated = true;
+ }
+
+ /* unicast MAC address */
+ if (m_MacAddress.au8[0] & 0x01)
+ {
+ LogRel(("MAC address is not unicast: %RTmac\n", &m_MacAddress));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ if (!fMACGenerated)
+ LogRel(("MAC address %RTmac\n", &m_MacAddress));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses the command line and loads the configuration.
+ *
+ * @returns The configuration, NULL if we ran into some fatal problem.
+ * @param argc The argc from main().
+ * @param argv The argv from main().
+ */
+Config *Config::create(int argc, char **argv) RT_NOEXCEPT
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--comment", '#', RTGETOPT_REQ_STRING },
+ { "--config", 'c', RTGETOPT_REQ_STRING },
+ { "--log", 'l', RTGETOPT_REQ_STRING },
+ { "--log-destinations", 'd', RTGETOPT_REQ_STRING },
+ { "--log-flags", 'f', RTGETOPT_REQ_STRING },
+ { "--log-group-settings", 'g', RTGETOPT_REQ_STRING },
+ { "--relaxed", 'r', RTGETOPT_REQ_NOTHING },
+ { "--strict", 's', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE State;
+ int rc = RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
+ AssertRCReturn(rc, NULL);
+
+ const char *pszLogFile = NULL;
+ const char *pszLogGroupSettings = NULL;
+ const char *pszLogDestinations = NULL;
+ const char *pszLogFlags = NULL;
+ const char *pszConfig = NULL;
+ const char *pszComment = NULL;
+ bool fStrict = true;
+
+ for (;;)
+ {
+ RTGETOPTUNION ValueUnion;
+ rc = RTGetOpt(&State, &ValueUnion);
+ if (rc == 0) /* done */
+ break;
+
+ switch (rc)
+ {
+ case 'c': /* --config */
+ pszConfig = ValueUnion.psz;
+ break;
+
+ case 'l':
+ pszLogFile = ValueUnion.psz;
+ break;
+
+ case 'd':
+ pszLogDestinations = ValueUnion.psz;
+ break;
+
+ case 'f':
+ pszLogFlags = ValueUnion.psz;
+ break;
+
+ case 'g':
+ pszLogGroupSettings = ValueUnion.psz;
+ break;
+
+ case 'r':
+ fStrict = false;
+ break;
+
+ case 's':
+ fStrict = true;
+ break;
+
+ case '#': /* --comment */
+ /* The sole purpose of this option is to allow identification of DHCP
+ * server instances in the process list. We ignore the required string
+ * argument of this option. */
+ pszComment = ValueUnion.psz;
+ break;
+
+ default:
+ RTGetOptPrintError(rc, &ValueUnion);
+ return NULL;
+ }
+ }
+
+ if (!pszConfig)
+ {
+ RTMsgError("No configuration file specified (--config file)!\n");
+ return NULL;
+ }
+
+ /*
+ * Init the log if a log file was specified.
+ */
+ if (pszLogFile)
+ {
+ rc = i_logInitWithFilename(pszLogFile);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to initialize log file '%s': %Rrc", pszLogFile, rc);
+
+ if (pszLogDestinations)
+ RTLogDestinations(RTLogRelGetDefaultInstance(), pszLogDestinations);
+ if (pszLogFlags)
+ RTLogFlags(RTLogRelGetDefaultInstance(), pszLogFlags);
+ if (pszLogGroupSettings)
+ RTLogGroupSettings(RTLogRelGetDefaultInstance(), pszLogGroupSettings);
+
+ LogRel(("--config: %s\n", pszComment));
+ if (pszComment)
+ LogRel(("--comment: %s\n", pszComment));
+ }
+
+ /*
+ * Read the config file.
+ */
+ RTMsgInfo("reading config from '%s'...\n", pszConfig);
+ std::unique_ptr<Config> ptrConfig;
+ ptrConfig.reset(Config::i_read(pszConfig, fStrict));
+ if (ptrConfig.get() != NULL)
+ {
+ rc = ptrConfig->i_complete();
+ if (RT_SUCCESS(rc))
+ return ptrConfig.release();
+ }
+ return NULL;
+}
+
+
+/**
+ *
+ * @note The release log is not operational when this method is called.
+ */
+Config *Config::i_read(const char *pszFileName, bool fStrict) RT_NOEXCEPT
+{
+ if (pszFileName == NULL || pszFileName[0] == '\0')
+ {
+ DHCP_LOG_MSG_ERROR(("Config::i_read: Empty configuration filename\n"));
+ return NULL;
+ }
+
+ xml::Document doc;
+ try
+ {
+ xml::XmlFileParser parser;
+ parser.read(pszFileName, doc);
+ }
+ catch (const xml::EIPRTFailure &e)
+ {
+ DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
+ return NULL;
+ }
+ catch (const RTCError &e)
+ {
+ DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
+ return NULL;
+ }
+ catch (...)
+ {
+ DHCP_LOG_MSG_ERROR(("Config::i_read: Unknown exception while reading and parsing '%s'\n", pszFileName));
+ return NULL;
+ }
+
+ std::unique_ptr<Config> config(i_createInstanceAndCallInit());
+ AssertReturn(config.get() != NULL, NULL);
+
+ try
+ {
+ config->i_parseConfig(doc.getRootElement(), fStrict);
+ }
+ catch (const RTCError &e)
+ {
+ DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
+ return NULL;
+ }
+ catch (std::bad_alloc &)
+ {
+ DHCP_LOG_MSG_ERROR(("Config::i_read: std::bad_alloc\n"));
+ return NULL;
+ }
+ catch (...)
+ {
+ DHCP_LOG_MSG_ERROR(("Config::i_read: Unexpected exception\n"));
+ return NULL;
+ }
+
+ return config.release();
+}
+
+
+/**
+ * Helper for retrieving a IPv4 attribute.
+ *
+ * @param pElm The element to get the attribute from.
+ * @param pszAttrName The name of the attribute
+ * @param pAddr Where to return the address.
+ * @throws ConfigFileError
+ */
+static void getIPv4AddrAttribute(const xml::ElementNode *pElm, const char *pszAttrName, PRTNETADDRIPV4 pAddr)
+{
+ const char *pszAttrValue;
+ if (pElm->getAttributeValue(pszAttrName, &pszAttrValue))
+ {
+ int rc = RTNetStrToIPv4Addr(pszAttrValue, pAddr);
+ if (RT_SUCCESS(rc))
+ return;
+ throw ConfigFileError(pElm, "Attribute %s is not a valid IPv4 address: '%s' -> %Rrc", pszAttrName, pszAttrValue, rc);
+ }
+ throw ConfigFileError(pElm, "Required %s attribute missing", pszAttrName);
+}
+
+
+/**
+ * Helper for retrieving a MAC address attribute.
+ *
+ * @param pElm The element to get the attribute from.
+ * @param pszAttrName The name of the attribute
+ * @param pMacAddr Where to return the MAC address.
+ * @throws ConfigFileError
+ */
+static void getMacAddressAttribute(const xml::ElementNode *pElm, const char *pszAttrName, PRTMAC pMacAddr)
+{
+ const char *pszAttrValue;
+ if (pElm->getAttributeValue(pszAttrName, &pszAttrValue))
+ {
+ int rc = RTNetStrToMacAddr(pszAttrValue, pMacAddr);
+ if (RT_SUCCESS(rc) && rc != VWRN_TRAILING_CHARS)
+ return;
+ throw ConfigFileError(pElm, "attribute %s is not a valid MAC address: '%s' -> %Rrc", pszAttrName, pszAttrValue, rc);
+ }
+ throw ConfigFileError(pElm, "Required %s attribute missing", pszAttrName);
+}
+
+
+/**
+ * Internal worker for i_read() that parses the root element and everything
+ * below it.
+ *
+ * @param pElmRoot The root element.
+ * @param fStrict Set if we're in strict mode, clear if we just
+ * want to get on with it if we can.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void Config::i_parseConfig(const xml::ElementNode *pElmRoot, bool fStrict)
+{
+ /*
+ * Check the root element and call i_parseServer to do real work.
+ */
+ if (pElmRoot == NULL)
+ throw ConfigFileError("Empty config file");
+
+ /** @todo XXX: NAMESPACE API IS COMPLETELY BROKEN, SO IGNORE IT FOR NOW */
+
+ if (!pElmRoot->nameEquals("DHCPServer"))
+ throw ConfigFileError("Unexpected root element '%s'", pElmRoot->getName());
+
+ i_parseServer(pElmRoot, fStrict);
+
+#if 0 /** @todo convert to LogRel2 stuff */
+ // XXX: debug
+ for (optmap_t::const_iterator it = m_GlobalOptions.begin(); it != m_GlobalOptions.end(); ++it) {
+ std::shared_ptr<DhcpOption> opt(it->second);
+
+ octets_t data;
+ opt->encode(data);
+
+ bool space = false;
+ for (octets_t::const_iterator itData = data.begin(); itData != data.end(); ++itData) {
+ uint8_t c = *itData;
+ if (space)
+ std::cout << " ";
+ else
+ space = true;
+ std::cout << (int)c;
+ }
+ std::cout << std::endl;
+ }
+#endif
+}
+
+
+/**
+ * Internal worker for parsing the elements under /DHCPServer/.
+ *
+ * @param pElmServer The DHCPServer element.
+ * @param fStrict Set if we're in strict mode, clear if we just
+ * want to get on with it if we can.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void Config::i_parseServer(const xml::ElementNode *pElmServer, bool fStrict)
+{
+ /*
+ * <DHCPServer> attributes
+ */
+ if (!pElmServer->getAttributeValue("networkName", m_strNetwork))
+ throw ConfigFileError("DHCPServer/@networkName missing");
+ if (m_strNetwork.isEmpty())
+ throw ConfigFileError("DHCPServer/@networkName is empty");
+
+ const char *pszTrunkType;
+ if (!pElmServer->getAttributeValue("trunkType", &pszTrunkType))
+ throw ConfigFileError("DHCPServer/@trunkType missing");
+ if (strcmp(pszTrunkType, "none") == 0)
+ m_enmTrunkType = kIntNetTrunkType_None;
+ else if (strcmp(pszTrunkType, "whatever") == 0)
+ m_enmTrunkType = kIntNetTrunkType_WhateverNone;
+ else if (strcmp(pszTrunkType, "netflt") == 0)
+ m_enmTrunkType = kIntNetTrunkType_NetFlt;
+ else if (strcmp(pszTrunkType, "netadp") == 0)
+ m_enmTrunkType = kIntNetTrunkType_NetAdp;
+ else
+ throw ConfigFileError("Invalid DHCPServer/@trunkType value: %s", pszTrunkType);
+
+ if ( m_enmTrunkType == kIntNetTrunkType_NetFlt
+ || m_enmTrunkType == kIntNetTrunkType_NetAdp)
+ {
+ if (!pElmServer->getAttributeValue("trunkName", &m_strTrunk))
+ throw ConfigFileError("DHCPServer/@trunkName missing");
+ }
+ else
+ m_strTrunk = "";
+
+ m_strLeasesFilename = pElmServer->findAttributeValue("leasesFilename"); /* optional */
+ if (m_strLeasesFilename.isEmpty())
+ {
+ int rc = m_strLeasesFilename.assignNoThrow(getHome());
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppendCxx(m_strLeasesFilename, m_strNetwork);
+ if (RT_SUCCESS(rc))
+ rc = m_strLeasesFilename.appendNoThrow("-Dhcpd.leases");
+ if (RT_FAILURE(rc))
+ throw ConfigFileError("Unexpected error constructing default m_strLeasesFilename value: %Rrc", rc);
+ RTPathPurgeFilename(RTPathFilename(m_strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST);
+ m_strLeasesFilename.jolt();
+ }
+
+ /*
+ * Addresses and mask.
+ */
+ ::getIPv4AddrAttribute(pElmServer, "IPAddress", &m_IPv4Address);
+ ::getIPv4AddrAttribute(pElmServer, "networkMask", &m_IPv4Netmask);
+ ::getIPv4AddrAttribute(pElmServer, "lowerIP", &m_IPv4PoolFirst);
+ ::getIPv4AddrAttribute(pElmServer, "upperIP", &m_IPv4PoolLast);
+
+ /* unicast IP address */
+ if ((m_IPv4Address.au8[0] & 0xe0) == 0xe0)
+ throw ConfigFileError("DHCP server IP address is not unicast: %RTnaipv4", m_IPv4Address.u);
+
+ /* valid netmask */
+ int cPrefixBits;
+ int rc = RTNetMaskToPrefixIPv4(&m_IPv4Netmask, &cPrefixBits);
+ if (RT_FAILURE(rc) || cPrefixBits == 0)
+ throw ConfigFileError("IP mask is not valid: %RTnaipv4", m_IPv4Netmask.u);
+
+ /* first IP is from the same network */
+ if ((m_IPv4PoolFirst.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u))
+ throw ConfigFileError("first pool address is outside the network %RTnaipv4/%d: %RTnaipv4",
+ (m_IPv4Address.u & m_IPv4Netmask.u), cPrefixBits, m_IPv4PoolFirst.u);
+
+ /* last IP is from the same network */
+ if ((m_IPv4PoolLast.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u))
+ throw ConfigFileError("last pool address is outside the network %RTnaipv4/%d: %RTnaipv4\n",
+ (m_IPv4Address.u & m_IPv4Netmask.u), cPrefixBits, m_IPv4PoolLast.u);
+
+ /* the pool is valid */
+ if (RT_N2H_U32(m_IPv4PoolLast.u) < RT_N2H_U32(m_IPv4PoolFirst.u))
+ throw ConfigFileError("pool range is invalid: %RTnaipv4 - %RTnaipv4", m_IPv4PoolFirst.u, m_IPv4PoolLast.u);
+ LogRel(("IP address: %RTnaipv4/%d\n", m_IPv4Address.u, cPrefixBits));
+ LogRel(("Address pool: %RTnaipv4 - %RTnaipv4\n", m_IPv4PoolFirst.u, m_IPv4PoolLast.u));
+
+ /*
+ * <DHCPServer> children
+ */
+ xml::NodesLoop it(*pElmServer);
+ const xml::ElementNode *pElmChild;
+ while ((pElmChild = it.forAllNodes()) != NULL)
+ {
+ /* Global options: */
+ if (pElmChild->nameEquals("Options"))
+ m_GlobalConfig.initFromXml(pElmChild, fStrict, this);
+ /* Group w/ options: */
+ else if (pElmChild->nameEquals("Group"))
+ {
+ std::unique_ptr<GroupConfig> ptrGroup(new GroupConfig());
+ ptrGroup->initFromXml(pElmChild, fStrict, this);
+ if (m_GroupConfigs.find(ptrGroup->getGroupName()) == m_GroupConfigs.end())
+ {
+ m_GroupConfigs[ptrGroup->getGroupName()] = ptrGroup.get();
+ ptrGroup.release();
+ }
+ else if (!fStrict)
+ LogRelFunc(("Ignoring duplicate group name: %s", ptrGroup->getGroupName().c_str()));
+ else
+ throw ConfigFileError("Duplicate group name: %s", ptrGroup->getGroupName().c_str());
+ }
+ /*
+ * MAC address and per VM NIC configurations:
+ */
+ else if (pElmChild->nameEquals("Config"))
+ {
+ std::unique_ptr<HostConfig> ptrHost(new HostConfig());
+ ptrHost->initFromXml(pElmChild, fStrict, this);
+ if (m_HostConfigs.find(ptrHost->getMACAddress()) == m_HostConfigs.end())
+ {
+ m_HostConfigs[ptrHost->getMACAddress()] = ptrHost.get();
+ ptrHost.release();
+ }
+ else if (!fStrict)
+ LogRelFunc(("Ignorining duplicate MAC address (Config): %RTmac", &ptrHost->getMACAddress()));
+ else
+ throw ConfigFileError("Duplicate MAC address (Config): %RTmac", &ptrHost->getMACAddress());
+ }
+ else if (!fStrict)
+ LogRel(("Ignoring unexpected DHCPServer child: %s\n", pElmChild->getName()));
+ else
+ throw ConfigFileError("Unexpected DHCPServer child <%s>'", pElmChild->getName());
+ }
+}
+
+
+/**
+ * Internal worker for parsing \<Option\> elements found under
+ * /DHCPServer/Options/, /DHCPServer/Group/ and /DHCPServer/Config/.
+ *
+ * @param pElmOption An \<Option\> element.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void ConfigLevelBase::i_parseOption(const xml::ElementNode *pElmOption)
+{
+ /* The 'name' attribute: */
+ const char *pszName;
+ if (!pElmOption->getAttributeValue("name", &pszName))
+ throw ConfigFileError(pElmOption, "missing option name");
+
+ uint8_t u8Opt;
+ int rc = RTStrToUInt8Full(pszName, 10, &u8Opt);
+ if (rc != VINF_SUCCESS) /* no warnings either */
+ throw ConfigFileError(pElmOption, "Bad option name '%s': %Rrc", pszName, rc);
+
+ /* The opional 'encoding' attribute: */
+ uint32_t u32Enc = 0; /* XXX: DHCPOptionEncoding_Normal */
+ const char *pszEncoding;
+ if (pElmOption->getAttributeValue("encoding", &pszEncoding))
+ {
+ rc = RTStrToUInt32Full(pszEncoding, 10, &u32Enc);
+ if (rc != VINF_SUCCESS) /* no warnings either */
+ throw ConfigFileError(pElmOption, "Bad option encoding '%s': %Rrc", pszEncoding, rc);
+
+ switch (u32Enc)
+ {
+ case 0: /* XXX: DHCPOptionEncoding_Normal */
+ case 1: /* XXX: DHCPOptionEncoding_Hex */
+ break;
+ default:
+ throw ConfigFileError(pElmOption, "Unknown encoding '%s'", pszEncoding);
+ }
+ }
+
+ /* The 'value' attribute. May be omitted for OptNoValue options like rapid commit. */
+ const char *pszValue;
+ if (!pElmOption->getAttributeValue("value", &pszValue))
+ pszValue = "";
+
+ /** @todo XXX: TODO: encoding, handle hex */
+ DhcpOption *opt = DhcpOption::parse(u8Opt, u32Enc, pszValue);
+ if (opt == NULL)
+ throw ConfigFileError(pElmOption, "Bad option '%s' (encoding %u): '%s' ", pszName, u32Enc, pszValue ? pszValue : "");
+
+ /* Add it to the map: */
+ m_Options << opt;
+}
+
+
+/**
+ * Internal worker for parsing \<ForcedOption\> and \<SupressedOption\> elements
+ * found under /DHCPServer/Options/, /DHCPServer/Group/ and /DHCPServer/Config/.
+ *
+ * @param pElmOption The element.
+ * @param fForced Whether it's a ForcedOption (true) or
+ * SuppressedOption element.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void ConfigLevelBase::i_parseForcedOrSuppressedOption(const xml::ElementNode *pElmOption, bool fForced)
+{
+ /* Only a name attribute: */
+ const char *pszName;
+ if (!pElmOption->getAttributeValue("name", &pszName))
+ throw ConfigFileError(pElmOption, "missing option name");
+
+ uint8_t u8Opt;
+ int rc = RTStrToUInt8Full(pszName, 10, &u8Opt);
+ if (rc != VINF_SUCCESS) /* no warnings either */
+ throw ConfigFileError(pElmOption, "Bad option name '%s': %Rrc", pszName, rc);
+
+ if (fForced)
+ m_vecForcedOptions.push_back(u8Opt);
+ else
+ m_vecSuppressedOptions.push_back(u8Opt);
+}
+
+
+/**
+ * Final children parser, handling only \<Option\> and barfing at anything else.
+ *
+ * @param pElmChild The child element to handle.
+ * @param fStrict Set if we're in strict mode, clear if we just
+ * want to get on with it if we can. That said,
+ * the caller will catch ConfigFileError exceptions
+ * and ignore them if @a fStrict is @c false.
+ * @param pConfig The configuration object.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void ConfigLevelBase::i_parseChild(const xml::ElementNode *pElmChild, bool fStrict, Config const *pConfig)
+{
+ /*
+ * Options.
+ */
+ if (pElmChild->nameEquals("Option"))
+ {
+ i_parseOption(pElmChild);
+ return;
+ }
+
+ /*
+ * Forced and supressed options.
+ */
+ bool const fForced = pElmChild->nameEquals("ForcedOption");
+ if (fForced || pElmChild->nameEquals("SuppressedOption"))
+ {
+ i_parseForcedOrSuppressedOption(pElmChild, fForced);
+ return;
+ }
+
+ /*
+ * What's this?
+ */
+ throw ConfigFileError(pElmChild->getParent(), "Unexpected child '%s'", pElmChild->getName());
+ RT_NOREF(fStrict, pConfig);
+}
+
+
+/**
+ * Base class initialization taking a /DHCPServer/Options, /DHCPServer/Group or
+ * /DHCPServer/Config element as input and handling common attributes as well as
+ * any \<Option\> children.
+ *
+ * @param pElmConfig The configuration element to parse.
+ * @param fStrict Set if we're in strict mode, clear if we just
+ * want to get on with it if we can.
+ * @param pConfig The configuration object.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void ConfigLevelBase::initFromXml(const xml::ElementNode *pElmConfig, bool fStrict, Config const *pConfig)
+{
+ /*
+ * Common attributes:
+ */
+ if (!pElmConfig->getAttributeValue("secMinLeaseTime", &m_secMinLeaseTime))
+ m_secMinLeaseTime = 0;
+ if (!pElmConfig->getAttributeValue("secDefaultLeaseTime", &m_secDefaultLeaseTime))
+ m_secDefaultLeaseTime = 0;
+ if (!pElmConfig->getAttributeValue("secMaxLeaseTime", &m_secMaxLeaseTime))
+ m_secMaxLeaseTime = 0;
+
+ /* Swap min and max if max is smaller: */
+ if (m_secMaxLeaseTime < m_secMinLeaseTime && m_secMinLeaseTime && m_secMaxLeaseTime)
+ {
+ LogRel(("Swapping min/max lease times: %u <-> %u\n", m_secMinLeaseTime, m_secMaxLeaseTime));
+ uint32_t uTmp = m_secMaxLeaseTime;
+ m_secMaxLeaseTime = m_secMinLeaseTime;
+ m_secMinLeaseTime = uTmp;
+ }
+
+ /*
+ * Parse children.
+ */
+ xml::NodesLoop it(*pElmConfig);
+ const xml::ElementNode *pElmChild;
+ while ((pElmChild = it.forAllNodes()) != NULL)
+ {
+ try
+ {
+ i_parseChild(pElmChild, fStrict, pConfig);
+ }
+ catch (ConfigFileError &rXcpt)
+ {
+ if (fStrict)
+ throw rXcpt;
+ LogRelFunc(("Ignoring: %s\n", rXcpt.what()));
+ }
+ }
+}
+
+
+/**
+ * Internal worker for parsing the elements under /DHCPServer/Options/.
+ *
+ * @param pElmOptions The \<Options\> element.
+ * @param fStrict Set if we're in strict mode, clear if we just
+ * want to get on with it if we can.
+ * @param pConfig The configuration object.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void GlobalConfig::initFromXml(const xml::ElementNode *pElmOptions, bool fStrict, Config const *pConfig)
+{
+ ConfigLevelBase::initFromXml(pElmOptions, fStrict, pConfig);
+
+ /*
+ * Resolve defaults here in the global config so we don't have to do this
+ * in Db::allocateBinding() for every lease request.
+ */
+ if (m_secMaxLeaseTime == 0 && m_secDefaultLeaseTime == 0 && m_secMinLeaseTime == 0)
+ {
+ m_secMinLeaseTime = 300; /* 5 min */
+ m_secDefaultLeaseTime = 600; /* 10 min */
+ m_secMaxLeaseTime = 12 * RT_SEC_1HOUR; /* 12 hours */
+ }
+ else
+ {
+ if (m_secDefaultLeaseTime == 0)
+ {
+ if (m_secMaxLeaseTime != 0)
+ m_secDefaultLeaseTime = RT_MIN(RT_MAX(m_secMinLeaseTime, 600), m_secMaxLeaseTime);
+ else
+ {
+ m_secDefaultLeaseTime = RT_MAX(m_secMinLeaseTime, 600);
+ m_secMaxLeaseTime = RT_MAX(m_secDefaultLeaseTime, 12 * RT_SEC_1HOUR);
+ }
+ }
+ if (m_secMaxLeaseTime == 0)
+ m_secMaxLeaseTime = RT_MAX(RT_MAX(m_secMinLeaseTime, m_secDefaultLeaseTime), 12 * RT_SEC_1HOUR);
+ if (m_secMinLeaseTime == 0)
+ m_secMinLeaseTime = RT_MIN(300, m_secDefaultLeaseTime);
+ }
+
+}
+
+
+/**
+ * Overrides base class to handle the condition elements under \<Group\>.
+ *
+ * @param pElmChild The child element.
+ * @param fStrict Set if we're in strict mode, clear if we just
+ * want to get on with it if we can.
+ * @param pConfig The configuration object.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void GroupConfig::i_parseChild(const xml::ElementNode *pElmChild, bool fStrict, Config const *pConfig)
+{
+ /*
+ * Match the condition
+ */
+ std::unique_ptr<GroupCondition> ptrCondition;
+ if (pElmChild->nameEquals("ConditionMAC"))
+ ptrCondition.reset(new GroupConditionMAC());
+ else if (pElmChild->nameEquals("ConditionMACWildcard"))
+ ptrCondition.reset(new GroupConditionMACWildcard());
+ else if (pElmChild->nameEquals("ConditionVendorClassID"))
+ ptrCondition.reset(new GroupConditionVendorClassID());
+ else if (pElmChild->nameEquals("ConditionVendorClassIDWildcard"))
+ ptrCondition.reset(new GroupConditionVendorClassIDWildcard());
+ else if (pElmChild->nameEquals("ConditionUserClassID"))
+ ptrCondition.reset(new GroupConditionUserClassID());
+ else if (pElmChild->nameEquals("ConditionUserClassIDWildcard"))
+ ptrCondition.reset(new GroupConditionUserClassIDWildcard());
+ else
+ {
+ /*
+ * Not a condition, pass it on to the base class.
+ */
+ ConfigLevelBase::i_parseChild(pElmChild, fStrict, pConfig);
+ return;
+ }
+
+ /*
+ * Get the attributes and initialize the condition.
+ */
+ bool fInclusive;
+ if (!pElmChild->getAttributeValue("inclusive", fInclusive))
+ fInclusive = true;
+ const char *pszValue = pElmChild->findAttributeValue("value");
+ if (pszValue && *pszValue)
+ {
+ int rc = ptrCondition->initCondition(pszValue, fInclusive);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add it to the appropriate vector.
+ */
+ if (fInclusive)
+ m_Inclusive.push_back(ptrCondition.release());
+ else
+ m_Exclusive.push_back(ptrCondition.release());
+ }
+ else
+ {
+ ConfigFileError Xcpt(pElmChild, "initCondition failed with %Rrc for '%s' and %RTbool", rc, pszValue, fInclusive);
+ if (!fStrict)
+ LogRelFunc(("%s, ignoring condition\n", Xcpt.what()));
+ else
+ throw ConfigFileError(Xcpt);
+ }
+ }
+ else
+ {
+ ConfigFileError Xcpt(pElmChild, "condition value is empty or missing (inclusive=%RTbool)", fInclusive);
+ if (fStrict)
+ throw Xcpt;
+ LogRelFunc(("%s, ignoring condition\n", Xcpt.what()));
+ }
+}
+
+
+/**
+ * Internal worker for parsing the elements under /DHCPServer/Group/.
+ *
+ * @param pElmGroup The \<Group\> element.
+ * @param fStrict Set if we're in strict mode, clear if we just
+ * want to get on with it if we can.
+ * @param pConfig The configuration object.
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void GroupConfig::initFromXml(const xml::ElementNode *pElmGroup, bool fStrict, Config const *pConfig)
+{
+ /*
+ * Attributes:
+ */
+ if (!pElmGroup->getAttributeValue("name", m_strName) || m_strName.isEmpty())
+ {
+ if (fStrict)
+ throw ConfigFileError(pElmGroup, "Group as no name or the name is empty");
+ m_strName.printf("Group#%u", s_uGroupNo++);
+ }
+
+ /*
+ * Do common initialization (including children).
+ */
+ ConfigLevelBase::initFromXml(pElmGroup, fStrict, pConfig);
+}
+
+
+/**
+ * Internal worker for parsing the elements under /DHCPServer/Config/.
+ *
+ * VM Config entries are generated automatically from VirtualBox.xml
+ * with the MAC fetched from the VM config. The client id is nowhere
+ * in the picture there, so VM config is indexed with plain RTMAC, not
+ * ClientId (also see getOptions below).
+ *
+ * @param pElmConfig The \<Config\> element.
+ * @param fStrict Set if we're in strict mode, clear if we just
+ * want to get on with it if we can.
+ * @param pConfig The configuration object (for netmask).
+ * @throws std::bad_alloc, ConfigFileError
+ */
+void HostConfig::initFromXml(const xml::ElementNode *pElmConfig, bool fStrict, Config const *pConfig)
+{
+ /*
+ * Attributes:
+ */
+ /* The MAC address: */
+ ::getMacAddressAttribute(pElmConfig, "MACAddress", &m_MACAddress);
+
+ /* Name - optional: */
+ if (!pElmConfig->getAttributeValue("name", m_strName))
+ m_strName.printf("MAC:%RTmac", &m_MACAddress);
+
+ /* Fixed IP address assignment - optional: */
+ const char *pszFixedAddress = pElmConfig->findAttributeValue("fixedAddress");
+ if (!pszFixedAddress || *RTStrStripL(pszFixedAddress) == '\0')
+ m_fHaveFixedAddress = false;
+ else
+ {
+ ::getIPv4AddrAttribute(pElmConfig, "fixedAddress", &m_FixedAddress);
+ if (pConfig->isInIPv4Network(m_FixedAddress))
+ m_fHaveFixedAddress = true;
+ else
+ {
+ ConfigFileError Xcpt(pElmConfig, "fixedAddress '%s' is not the DHCP network", pszFixedAddress);
+ if (fStrict)
+ throw Xcpt;
+ LogRelFunc(("%s - ignoring the fixed address assignment\n", Xcpt.what()));
+ m_fHaveFixedAddress = false;
+ }
+ }
+
+ /*
+ * Do common initialization.
+ */
+ ConfigLevelBase::initFromXml(pElmConfig, fStrict, pConfig);
+}
+
+
+/**
+ * Assembles a list of hosts with fixed address assignments.
+ *
+ * @returns IPRT status code.
+ * @param a_rRetConfigs Where to return the configurations.
+ */
+int Config::getFixedAddressConfigs(HostConfigVec &a_rRetConfigs) const
+{
+ for (HostConfigMap::const_iterator it = m_HostConfigs.begin(); it != m_HostConfigs.end(); ++it)
+ {
+ HostConfig const *pHostConfig = it->second;
+ if (pHostConfig->haveFixedAddress())
+ try
+ {
+ a_rRetConfigs.push_back(pHostConfig);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Assembles a priorities vector of configurations for the client.
+ *
+ * @returns a_rRetConfigs for convenience.
+ * @param a_rRetConfigs Where to return the configurations.
+ * @param a_ridClient The client ID.
+ * @param a_ridVendorClass The vendor class ID if present.
+ * @param a_ridUserClass The user class ID if present
+ */
+Config::ConfigVec &Config::getConfigsForClient(Config::ConfigVec &a_rRetConfigs, const ClientId &a_ridClient,
+ const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const
+{
+ /* Host specific config first: */
+ HostConfigMap::const_iterator itHost = m_HostConfigs.find(a_ridClient.mac());
+ if (itHost != m_HostConfigs.end())
+ a_rRetConfigs.push_back(itHost->second);
+
+ /* Groups: */
+ for (GroupConfigMap::const_iterator itGrp = m_GroupConfigs.begin(); itGrp != m_GroupConfigs.end(); ++itGrp)
+ if (itGrp->second->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
+ a_rRetConfigs.push_back(itGrp->second);
+
+ /* Global: */
+ a_rRetConfigs.push_back(&m_GlobalConfig);
+
+ return a_rRetConfigs;
+}
+
+
+/**
+ * Method used by DHCPD to assemble a list of options for the client.
+ *
+ * @returns a_rRetOpts for convenience
+ * @param a_rRetOpts Where to put the requested options.
+ * @param a_rReqOpts The requested options.
+ * @param a_rConfigs Relevant configurations returned by
+ * Config::getConfigsForClient().
+ *
+ * @throws std::bad_alloc
+ */
+optmap_t &Config::getOptionsForClient(optmap_t &a_rRetOpts, const OptParameterRequest &a_rReqOpts, ConfigVec &a_rConfigs) const
+{
+ /*
+ * The client typcially requests a list of options. The list is subject to
+ * forced and supressed lists on each configuration level in a_rConfig. To
+ * efficiently manage it without resorting to maps, the current code
+ * assembles a C-style array of options on the stack that should be returned
+ * to the client.
+ */
+ uint8_t abOptions[256];
+ size_t cOptions = 0;
+ size_t iFirstForced = 255;
+#define IS_OPTION_PRESENT(a_bOption) (memchr(abOptions, (a_bOption), cOptions) != NULL)
+#define APPEND_NOT_PRESENT_OPTION(a_bOption) do { \
+ AssertLogRelMsgBreak(cOptions < sizeof(abOptions), \
+ ("a_bOption=%#x abOptions=%.*Rhxs\n", (a_bOption), sizeof(abOptions), &abOptions[0])); \
+ abOptions[cOptions++] = (a_bOption); \
+ } while (0)
+
+ const OptParameterRequest::value_t &reqValue = a_rReqOpts.value();
+ if (reqValue.size() != 0)
+ {
+ /* Copy the requested list and append any forced options from the configs: */
+ for (octets_t::const_iterator itOptReq = reqValue.begin(); itOptReq != reqValue.end(); ++itOptReq)
+ if (!IS_OPTION_PRESENT(*itOptReq))
+ APPEND_NOT_PRESENT_OPTION(*itOptReq);
+ iFirstForced = cOptions;
+ for (Config::ConfigVec::const_iterator itCfg = a_rConfigs.begin(); itCfg != a_rConfigs.end(); ++itCfg)
+ {
+ octets_t const &rForced = (*itCfg)->getForcedOptions();
+ for (octets_t::const_iterator itOpt = rForced.begin(); itOpt != rForced.end(); ++itOpt)
+ if (!IS_OPTION_PRESENT(*itOpt))
+ {
+ LogRel3((">>> Forcing option %d (%s)\n", *itOpt, DhcpOption::name(*itOpt)));
+ APPEND_NOT_PRESENT_OPTION(*itOpt);
+ }
+ }
+ }
+ else
+ {
+ /* No options requests, feed the client all available options: */
+ for (Config::ConfigVec::const_iterator itCfg = a_rConfigs.begin(); itCfg != a_rConfigs.end(); ++itCfg)
+ {
+ optmap_t const &rOptions = (*itCfg)->getOptions();
+ for (optmap_t::const_iterator itOpt = rOptions.begin(); itOpt != rOptions.end(); ++itOpt)
+ if (!IS_OPTION_PRESENT(itOpt->first))
+ APPEND_NOT_PRESENT_OPTION(itOpt->first);
+
+ }
+ }
+
+ /*
+ * Always supply the subnet:
+ */
+ a_rRetOpts << new OptSubnetMask(m_IPv4Netmask);
+
+ /*
+ * Try provide the options we've decided to return.
+ */
+ for (size_t iOpt = 0; iOpt < cOptions; iOpt++)
+ {
+ uint8_t const bOptReq = abOptions[iOpt];
+ if (iOpt < iFirstForced)
+ LogRel2((">>> requested option %d (%s)\n", bOptReq, DhcpOption::name(bOptReq)));
+ else
+ LogRel2((">>> forced option %d (%s)\n", bOptReq, DhcpOption::name(bOptReq)));
+
+ if (bOptReq != OptSubnetMask::optcode)
+ {
+ bool fFound = false;
+ for (size_t i = 0; i < a_rConfigs.size(); i++)
+ {
+ if (!a_rConfigs[i]->isOptionSuppressed(bOptReq))
+ {
+ optmap_t::const_iterator itFound;
+ if (a_rConfigs[i]->findOption(bOptReq, itFound)) /* crap interface */
+ {
+ LogRel2(("... found in %s (type %s)\n", a_rConfigs[i]->getName(), a_rConfigs[i]->getType()));
+ a_rRetOpts << itFound->second;
+ fFound = true;
+ break;
+ }
+ }
+ else
+ {
+ LogRel2(("... suppressed by %s (type %s)\n", a_rConfigs[i]->getName(), a_rConfigs[i]->getType()));
+ fFound = true;
+ break;
+ }
+ }
+ if (!fFound)
+ LogRel3(("... not found\n"));
+ }
+ else
+ LogRel2(("... always supplied\n"));
+ }
+
+#undef IS_OPTION_PRESENT
+#undef APPEND_NOT_PRESENT_OPTION
+ return a_rRetOpts;
+}
+
+
+
+/*********************************************************************************************************************************
+* Group Condition Matching *
+*********************************************************************************************************************************/
+
+bool GroupConfig::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const
+{
+ /*
+ * Check the inclusive ones first, only one need to match.
+ */
+ for (GroupConditionVec::const_iterator itIncl = m_Inclusive.begin(); itIncl != m_Inclusive.end(); ++itIncl)
+ if ((*itIncl)->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
+ {
+ /*
+ * Now make sure it isn't excluded by any of the exclusion condition.
+ */
+ for (GroupConditionVec::const_iterator itExcl = m_Exclusive.begin(); itExcl != m_Exclusive.end(); ++itExcl)
+ if ((*itIncl)->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
+ return false;
+ return true;
+ }
+
+ return false;
+}
+
+
+int GroupCondition::initCondition(const char *a_pszValue, bool a_fInclusive)
+{
+ m_fInclusive = a_fInclusive;
+ return m_strValue.assignNoThrow(a_pszValue);
+}
+
+
+bool GroupCondition::matchClassId(bool a_fPresent, const std::vector<uint8_t> &a_rBytes, bool fWildcard) const RT_NOEXCEPT
+{
+ if (a_fPresent)
+ {
+ size_t const cbBytes = a_rBytes.size();
+ if (cbBytes > 0)
+ {
+ if (a_rBytes[cbBytes - 1] == '\0')
+ {
+ uint8_t const *pb = &a_rBytes.front();
+ if (!fWildcard)
+ return m_strValue.equals((const char *)pb);
+ return RTStrSimplePatternMatch(m_strValue.c_str(), (const char *)pb);
+ }
+
+ if (cbBytes <= 255)
+ {
+ char szTmp[256];
+ memcpy(szTmp, &a_rBytes.front(), cbBytes);
+ szTmp[cbBytes] = '\0';
+ if (!fWildcard)
+ return m_strValue.equals(szTmp);
+ return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp);
+ }
+ }
+ }
+ return false;
+
+}
+
+
+int GroupConditionMAC::initCondition(const char *a_pszValue, bool a_fInclusive)
+{
+ int vrc = RTNetStrToMacAddr(a_pszValue, &m_MACAddress);
+ if (RT_SUCCESS(vrc))
+ return GroupCondition::initCondition(a_pszValue, a_fInclusive);
+ return vrc;
+}
+
+
+bool GroupConditionMAC::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
+{
+ RT_NOREF(a_ridVendorClass, a_ridUserClass);
+ return a_ridClient.mac() == m_MACAddress;
+}
+
+
+bool GroupConditionMACWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
+{
+ RT_NOREF(a_ridVendorClass, a_ridUserClass);
+ char szTmp[32];
+ RTStrPrintf(szTmp, sizeof(szTmp), "%RTmac", &a_ridClient.mac());
+ return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp);
+}
+
+
+bool GroupConditionVendorClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
+{
+ RT_NOREF(a_ridClient, a_ridUserClass);
+ return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value());
+}
+
+
+bool GroupConditionVendorClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
+{
+ RT_NOREF(a_ridClient, a_ridUserClass);
+ return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value(), true /*fWildcard*/);
+}
+
+
+bool GroupConditionUserClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
+{
+ RT_NOREF(a_ridClient, a_ridVendorClass);
+ return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value());
+}
+
+
+bool GroupConditionUserClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
+{
+ RT_NOREF(a_ridClient, a_ridVendorClass);
+ return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value(), true /*fWildcard*/);
+}
+
diff --git a/src/VBox/NetworkServices/Dhcpd/Config.h b/src/VBox/NetworkServices/Dhcpd/Config.h
new file mode 100644
index 00000000..133dc21c
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/Config.h
@@ -0,0 +1,401 @@
+/* $Id: Config.h $ */
+/** @file
+ * DHCP server - server configuration
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_Config_h
+#define VBOX_INCLUDED_SRC_Dhcpd_Config_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DhcpdInternal.h"
+#include <iprt/types.h>
+#include <iprt/net.h>
+#include <iprt/cpp/xml.h>
+#include <iprt/cpp/ministring.h>
+
+#include <VBox/intnet.h>
+
+#include "DhcpOptions.h"
+#include "ClientId.h"
+
+
+class Config;
+
+/**
+ * Base configuration
+ *
+ * @author bird (2019-07-15)
+ */
+class ConfigLevelBase
+{
+private:
+ /** DHCP options. */
+ optmap_t m_Options;
+protected:
+ /** Minimum lease time, zero means try next level up. */
+ uint32_t m_secMinLeaseTime;
+ /** Default lease time, zero means try next level up. */
+ uint32_t m_secDefaultLeaseTime;
+ /** Maximum lease time, zero means try next level up. */
+ uint32_t m_secMaxLeaseTime;
+
+ /** Options forced unsolicited upon the client. */
+ octets_t m_vecForcedOptions;
+ /** Options (typcially from higher up) that should be hidden from the client. */
+ octets_t m_vecSuppressedOptions;
+
+public:
+ ConfigLevelBase()
+ : m_Options()
+ , m_secMinLeaseTime(0)
+ , m_secDefaultLeaseTime(0)
+ , m_secMaxLeaseTime(0)
+ , m_vecForcedOptions()
+ , m_vecSuppressedOptions()
+ { }
+
+ virtual ~ConfigLevelBase()
+ { }
+
+ virtual void initFromXml(xml::ElementNode const *pElmConfig, bool fStrict, Config const *pConfig);
+ virtual const char *getType() const RT_NOEXCEPT = 0;
+ virtual const char *getName() const RT_NOEXCEPT = 0;
+
+ /**
+ * Tries to find DHCP option @a bOpt, returning an success indicator and
+ * iterator to the result.
+ */
+ bool findOption(uint8_t bOpt, optmap_t::const_iterator &a_rItRet) const RT_NOEXCEPT
+ {
+ a_rItRet = m_Options.find(bOpt);
+ return a_rItRet != m_Options.end();
+ }
+
+ /** Checks if @a bOpt is suppressed or not. */
+ bool isOptionSuppressed(uint8_t bOpt) const RT_NOEXCEPT
+ {
+ return m_vecSuppressedOptions.size() > 0
+ && memchr(&m_vecSuppressedOptions.front(), bOpt, m_vecSuppressedOptions.size()) != NULL;
+ }
+
+ /** @name Accessors
+ * @{ */
+ uint32_t getMinLeaseTime() const RT_NOEXCEPT { return m_secMinLeaseTime; }
+ uint32_t getDefaultLeaseTime() const RT_NOEXCEPT { return m_secDefaultLeaseTime; }
+ uint32_t getMaxLeaseTime() const RT_NOEXCEPT { return m_secMaxLeaseTime; }
+ octets_t const &getForcedOptions() const RT_NOEXCEPT { return m_vecForcedOptions; }
+ octets_t const &getSuppressedOptions() const RT_NOEXCEPT { return m_vecSuppressedOptions; }
+ optmap_t const &getOptions() const RT_NOEXCEPT { return m_Options; }
+ /** @} */
+
+protected:
+ void i_parseOption(const xml::ElementNode *pElmOption);
+ void i_parseForcedOrSuppressedOption(const xml::ElementNode *pElmOption, bool fForced);
+ virtual void i_parseChild(const xml::ElementNode *pElmChild, bool fStrict, Config const *pConfig);
+};
+
+
+/**
+ * Global config
+ */
+class GlobalConfig : public ConfigLevelBase
+{
+public:
+ GlobalConfig()
+ : ConfigLevelBase()
+ { }
+ void initFromXml(xml::ElementNode const *pElmOptions, bool fStrict, Config const *pConfig) RT_OVERRIDE;
+ const char *getType() const RT_NOEXCEPT RT_OVERRIDE { return "global"; }
+ const char *getName() const RT_NOEXCEPT RT_OVERRIDE { return "GlobalConfig"; }
+};
+
+
+/**
+ * Group membership condition.
+ */
+class GroupCondition
+{
+protected:
+ /** The value. */
+ RTCString m_strValue;
+ /** Inclusive (true) or exclusive (false), latter takes precedency. */
+ bool m_fInclusive;
+
+public:
+ virtual ~GroupCondition()
+ {}
+
+ virtual int initCondition(const char *a_pszValue, bool a_fInclusive);
+ virtual bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT = 0;
+
+ /** @name accessors
+ * @{ */
+ RTCString const &getValue() const RT_NOEXCEPT { return m_strValue; }
+ bool getInclusive() const RT_NOEXCEPT { return m_fInclusive; }
+ /** @} */
+
+protected:
+ bool matchClassId(bool a_fPresent, std::vector<uint8_t> const &a_rBytes, bool fWildcard = false) const RT_NOEXCEPT;
+};
+
+/** MAC condition. */
+class GroupConditionMAC : public GroupCondition
+{
+private:
+ RTMAC m_MACAddress;
+public:
+ int initCondition(const char *a_pszValue, bool a_fInclusive) RT_OVERRIDE;
+ bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT RT_OVERRIDE;
+};
+
+/** MAC wildcard condition. */
+class GroupConditionMACWildcard : public GroupCondition
+{
+public:
+ bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT RT_OVERRIDE;
+};
+
+/** Vendor class ID condition. */
+class GroupConditionVendorClassID : public GroupCondition
+{
+public:
+ bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT RT_OVERRIDE;
+};
+
+/** Vendor class ID wildcard condition. */
+class GroupConditionVendorClassIDWildcard : public GroupCondition
+{
+public:
+ bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT RT_OVERRIDE;
+};
+
+/** User class ID condition. */
+class GroupConditionUserClassID : public GroupCondition
+{
+public:
+ bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT RT_OVERRIDE;
+};
+
+/** User class ID wildcard condition. */
+class GroupConditionUserClassIDWildcard : public GroupCondition
+{
+public:
+ bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT RT_OVERRIDE;
+};
+
+
+/**
+ * Group config
+ */
+class GroupConfig : public ConfigLevelBase
+{
+private:
+ typedef std::vector<GroupCondition *> GroupConditionVec;
+
+ /** The group name. */
+ RTCString m_strName;
+ /** Vector containing the inclusive membership conditions (must match one). */
+ GroupConditionVec m_Inclusive;
+ /** Vector containing the exclusive membership conditions (must match none). */
+ GroupConditionVec m_Exclusive;
+
+public:
+ GroupConfig()
+ : ConfigLevelBase()
+ {
+ }
+
+ void initFromXml(xml::ElementNode const *pElmGroup, bool fStrict, Config const *pConfig) RT_OVERRIDE;
+ bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass, const OptUserClassId &a_ridUserClass) const;
+
+ /** @name Accessors
+ * @{ */
+ const char *getType() const RT_NOEXCEPT RT_OVERRIDE { return "group"; }
+ const char *getName() const RT_NOEXCEPT RT_OVERRIDE { return m_strName.c_str(); }
+ RTCString const &getGroupName() const RT_NOEXCEPT { return m_strName; }
+ /** @} */
+
+protected:
+ void i_parseChild(const xml::ElementNode *pElmChild, bool fStrict, Config const *pConfig) RT_OVERRIDE;
+ /** Used to name unnamed groups. */
+ static uint32_t s_uGroupNo;
+};
+
+
+/**
+ * Host (MAC address) specific configuration.
+ */
+class HostConfig : public ConfigLevelBase
+{
+protected:
+ /** The MAC address. */
+ RTMAC m_MACAddress;
+ /** Name annotating the entry. */
+ RTCString m_strName;
+ /** Fixed address assignment when m_fHaveFixedAddress is true. */
+ RTNETADDRIPV4 m_FixedAddress;
+ /** Set if we have a fixed address asignment. */
+ bool m_fHaveFixedAddress;
+
+public:
+ HostConfig()
+ : ConfigLevelBase()
+ , m_fHaveFixedAddress(false)
+ {
+ RT_ZERO(m_MACAddress);
+ RT_ZERO(m_FixedAddress);
+ }
+
+ void initFromXml(xml::ElementNode const *pElmConfig, bool fStrict, Config const *pConfig) RT_OVERRIDE;
+ const char *getType() const RT_NOEXCEPT RT_OVERRIDE { return "host"; }
+ const char *getName() const RT_NOEXCEPT RT_OVERRIDE { return m_strName.c_str(); }
+
+ /** @name Accessors
+ * @{ */
+ RTMAC const &getMACAddress() const RT_NOEXCEPT { return m_MACAddress; }
+ bool haveFixedAddress() const RT_NOEXCEPT { return m_fHaveFixedAddress; }
+ RTNETADDRIPV4 const & getFixedAddress() const RT_NOEXCEPT { return m_FixedAddress; }
+ /** @} */
+};
+
+
+/**
+ * DHCP server configuration.
+ */
+class Config
+{
+ /** Group configuration map. */
+ typedef std::map<RTCString, GroupConfig const * > GroupConfigMap;
+ /** Host configuration map. */
+ typedef std::map<RTMAC, HostConfig const * > HostConfigMap;
+
+
+ RTCString m_strHome; /**< path of ~/.VirtualBox or equivalent, */
+
+ RTCString m_strNetwork; /**< The name of the internal network the DHCP server is connected to. */
+ RTCString m_strLeasesFilename;/**< The lease DB filename. */
+
+ RTCString m_strTrunk; /**< The trunk name of the internal network. */
+ INTNETTRUNKTYPE m_enmTrunkType; /**< The trunk type of the internal network. */
+
+ RTMAC m_MacAddress; /**< The MAC address for the DHCP server. */
+
+ RTNETADDRIPV4 m_IPv4Address; /**< The IPv4 address of the DHCP server. */
+ RTNETADDRIPV4 m_IPv4Netmask; /**< The IPv4 netmask for the DHCP server. */
+
+ RTNETADDRIPV4 m_IPv4PoolFirst; /**< The first IPv4 address in the pool. */
+ RTNETADDRIPV4 m_IPv4PoolLast; /**< The last IPV4 address in the pool (inclusive like all other 'last' variables). */
+
+
+ /** The global configuration. */
+ GlobalConfig m_GlobalConfig;
+ /** The group configurations, indexed by group name. */
+ GroupConfigMap m_GroupConfigs;
+ /** The host configurations, indexed by MAC address. */
+ HostConfigMap m_HostConfigs;
+
+ /** Set if we've initialized the log already (via command line). */
+ static bool g_fInitializedLog;
+
+private:
+ Config();
+
+ int i_init() RT_NOEXCEPT;
+ int i_homeInit() RT_NOEXCEPT;
+ static Config *i_createInstanceAndCallInit() RT_NOEXCEPT;
+ int i_logInit() RT_NOEXCEPT;
+ static int i_logInitWithFilename(const char *pszFilename) RT_NOEXCEPT;
+ int i_complete() RT_NOEXCEPT;
+
+public:
+ /** @name Factory methods
+ * @{ */
+ static Config *hardcoded() RT_NOEXCEPT; /**< For testing. */
+ static Config *create(int argc, char **argv) RT_NOEXCEPT; /**< --config */
+ static Config *compat(int argc, char **argv);
+ /** @} */
+
+ /** @name Accessors
+ * @{ */
+ const RTCString &getHome() const RT_NOEXCEPT { return m_strHome; }
+
+ const RTCString &getNetwork() const RT_NOEXCEPT { return m_strNetwork; }
+ const RTCString &getLeasesFilename() const RT_NOEXCEPT { return m_strLeasesFilename; }
+
+ const RTCString &getTrunk() const RT_NOEXCEPT { return m_strTrunk; }
+ INTNETTRUNKTYPE getTrunkType() const RT_NOEXCEPT { return m_enmTrunkType; }
+
+ const RTMAC &getMacAddress() const RT_NOEXCEPT { return m_MacAddress; }
+
+ RTNETADDRIPV4 getIPv4Address() const RT_NOEXCEPT { return m_IPv4Address; }
+ RTNETADDRIPV4 getIPv4Netmask() const RT_NOEXCEPT { return m_IPv4Netmask; }
+ RTNETADDRIPV4 getIPv4PoolFirst() const RT_NOEXCEPT { return m_IPv4PoolFirst; }
+ RTNETADDRIPV4 getIPv4PoolLast() const RT_NOEXCEPT { return m_IPv4PoolLast; }
+ /** @} */
+
+ /** Gets the network (IP masked by network mask). */
+ RTNETADDRIPV4 getIPv4Network() const RT_NOEXCEPT
+ {
+ RTNETADDRIPV4 Network;
+ Network.u = m_IPv4Netmask.u & m_IPv4Address.u;
+ return Network;
+ }
+ /** Checks if the given IPv4 address is in the DHCP server network. */
+ bool isInIPv4Network(RTNETADDRIPV4 a_rAddress) const RT_NOEXCEPT
+ {
+ return (a_rAddress.u & getIPv4Netmask().u) == getIPv4Network().u;
+ }
+
+ /** Host configuration vector. */
+ typedef std::vector<HostConfig const *> HostConfigVec;
+ int getFixedAddressConfigs(HostConfigVec &a_rRetConfigs) const;
+
+ /** Configuration vector. */
+ typedef std::vector<ConfigLevelBase const *> ConfigVec;
+ ConfigVec &getConfigsForClient(ConfigVec &a_rRetConfigs, const ClientId &a_ridClient,
+ const OptVendorClassId &a_ridVendorClass,
+ const OptUserClassId &a_ridUserClass) const;
+ optmap_t &getOptionsForClient(optmap_t &a_rRetOpts, const OptParameterRequest &a_rReqOpts,
+ ConfigVec &a_rConfigs) const;
+
+private:
+ /** @name Configuration file reading and parsing
+ * @{ */
+ static Config *i_read(const char *pszFilename, bool fStrict) RT_NOEXCEPT;
+ void i_parseConfig(const xml::ElementNode *pElmRoot, bool fStrict);
+ void i_parseServer(const xml::ElementNode *pElmServer, bool fStrict);
+ /** @} */
+};
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_Config_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp b/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp
new file mode 100644
index 00000000..d0b3f819
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp
@@ -0,0 +1,420 @@
+/* $Id: DHCPD.cpp $ */
+/** @file
+ * DHCP server - protocol logic
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "DhcpdInternal.h"
+#include "DHCPD.h"
+#include "DhcpOptions.h"
+
+#include <iprt/message.h>
+
+
+DHCPD::DHCPD()
+ : m_pConfig(NULL), m_db()
+{
+}
+
+
+/**
+ * Initializes the DHCPD with the given config.
+ *
+ * @returns VBox status code.
+ * @param pConfig The configuration to use.
+ */
+int DHCPD::init(const Config *pConfig) RT_NOEXCEPT
+{
+ Assert(pConfig);
+ AssertReturn(!m_pConfig, VERR_INVALID_STATE);
+ m_pConfig = pConfig;
+
+ /* Load the lease database, ignoring most issues except being out of memory: */
+ int rc = m_db.init(pConfig);
+ if (RT_SUCCESS(rc))
+ {
+ rc = i_loadLeases();
+ if (rc != VERR_NO_MEMORY)
+ return VINF_SUCCESS;
+
+ DHCP_LOG_MSG_ERROR(("Ran out of memory loading leases from '%s'. Try rename or delete the file.\n",
+ pConfig->getLeasesFilename().c_str()));
+ }
+ return rc;
+}
+
+
+/**
+ * Load leases from pConfig->getLeasesFilename().
+ */
+int DHCPD::i_loadLeases() RT_NOEXCEPT
+{
+ return m_db.loadLeases(m_pConfig->getLeasesFilename());
+}
+
+
+/**
+ * Save the current leases to pConfig->getLeasesFilename(), doing expiry first.
+ *
+ * This is called after m_db is updated during a client request, so the on disk
+ * database is always up-to-date. This means it doesn't matter if we're
+ * terminated with extreme prejudice, and it allows Main to look up IP addresses
+ * for VMs.
+ *
+ * @throws nothing
+ */
+void DHCPD::i_saveLeases() RT_NOEXCEPT
+{
+ m_db.expire();
+ m_db.writeLeases(m_pConfig->getLeasesFilename());
+}
+
+
+/**
+ * Process a DHCP client message.
+ *
+ * Called by VBoxNetDhcpd::dhcp4Recv().
+ *
+ * @returns Pointer to DHCP reply (caller deletes this). NULL if no reply
+ * warranted or we're out of memory.
+ * @param req The client message.
+ * @throws nothing
+ */
+DhcpServerMessage *DHCPD::process(DhcpClientMessage &req) RT_NOEXCEPT
+{
+ /*
+ * Dump the package if release log level 3+1 are enable or if debug logging is
+ * enabled. We don't normally want to do this at the default log level, of course.
+ */
+ if ((LogRelIs3Enabled() && LogRelIsEnabled()) || LogIsEnabled())
+ req.dump();
+
+ /*
+ * Fend off requests that are not for us.
+ */
+ OptServerId sid(req);
+ if (sid.present() && sid.value().u != m_pConfig->getIPv4Address().u)
+ {
+ if (req.broadcasted() && req.messageType() == RTNET_DHCP_MT_REQUEST)
+ {
+ LogRel2(("Message is not for us, canceling any pending offer.\n"));
+ m_db.cancelOffer(req);
+ }
+ else
+ LogRel2(("Message is not for us.\n"));
+ return NULL;
+ }
+
+ /*
+ * Process it.
+ */
+ DhcpServerMessage *reply = NULL;
+
+ switch (req.messageType())
+ {
+ /*
+ * Requests that require server's reply.
+ */
+ case RTNET_DHCP_MT_DISCOVER:
+ try
+ {
+ reply = i_doDiscover(req);
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRelFunc(("i_doDiscover threw bad_alloc\n"));
+ }
+ break;
+
+ case RTNET_DHCP_MT_REQUEST:
+ try
+ {
+ reply = i_doRequest(req);
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRelFunc(("i_doRequest threw bad_alloc\n"));
+ }
+ break;
+
+ case RTNET_DHCP_MT_INFORM:
+ try
+ {
+ reply = i_doInform(req);
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRelFunc(("i_doInform threw bad_alloc\n"));
+ }
+ break;
+
+ /*
+ * Requests that don't have a reply.
+ */
+ case RTNET_DHCP_MT_DECLINE:
+ i_doDecline(req);
+ break;
+
+ case RTNET_DHCP_MT_RELEASE:
+ i_doRelease(req);
+ break;
+
+ /*
+ * Unexpected or unknown message types.
+ */
+ case RTNET_DHCP_MT_OFFER:
+ LogRel2(("Ignoring unexpected message of type RTNET_DHCP_MT_OFFER!\n"));
+ break;
+ case RTNET_DHCP_MT_ACK:
+ LogRel2(("Ignoring unexpected message of type RTNET_DHCP_MT_ACK!\n"));
+ break;
+ case RTNET_DHCP_MT_NAC:
+ LogRel2(("Ignoring unexpected message of type RTNET_DHCP_MT_NAC!\n"));
+ break;
+ default:
+ LogRel2(("Ignoring unexpected message of unknown type: %d (%#x)!\n", req.messageType(), req.messageType()));
+ break;
+ }
+
+ return reply;
+}
+
+
+/**
+ * Internal helper.
+ *
+ * @throws std::bad_alloc
+ */
+DhcpServerMessage *DHCPD::i_createMessage(int type, const DhcpClientMessage &req)
+{
+ return new DhcpServerMessage(req, type, m_pConfig->getIPv4Address());
+}
+
+
+/**
+ * 4.3.1 DHCPDISCOVER message
+ *
+ * When a server receives a DHCPDISCOVER message from a client, the server
+ * chooses a network address for the requesting client. If no address is
+ * available, the server may choose to report the problem to the system
+ * administrator. If an address is available, the new address SHOULD be chosen
+ * as follows:
+ * - The client's current address as recorded in the client's current binding,
+ * ELSE
+ * - The client's previous address as recorded in the client's (now expired or
+ * released) binding, if that address is in the server's pool of available
+ * addresses and not already allocated, ELSE
+ * - The address requested in the 'Requested IP Address' option, if that
+ * address is valid and not already allocated, ELSE
+ * - A new address allocated from the server's pool of available addresses;
+ * the address is selected based on the subnet from which the message was
+ * received (if 'giaddr' is 0) or on the address of the relay agent that
+ * forwarded the message ('giaddr' when not 0).
+ *
+ * ...
+ *
+ * @throws std::bad_alloc
+ */
+DhcpServerMessage *DHCPD::i_doDiscover(const DhcpClientMessage &req)
+{
+ /** @todo
+ * XXX: TODO: Windows iSCSI initiator sends DHCPDISCOVER first and
+ * it has ciaddr filled. Shouldn't let it screw up the normal
+ * lease we already have for that client, but we should probably
+ * reply with a pro-forma offer.
+ */
+ if (req.ciaddr().u != 0)
+ return NULL;
+
+ Config::ConfigVec vecConfigs;
+ m_pConfig->getConfigsForClient(vecConfigs, req.clientId(), OptVendorClassId(req), OptUserClassId(req));
+
+ Binding *b = m_db.allocateBinding(req, vecConfigs);
+ if (b == NULL)
+ return NULL;
+
+ std::unique_ptr<DhcpServerMessage> reply;
+
+ bool fRapidCommit = OptRapidCommit(req).present();
+ if (!fRapidCommit)
+ {
+ reply.reset(i_createMessage(RTNET_DHCP_MT_OFFER, req));
+
+ if (b->state() < Binding::OFFERED)
+ b->setState(Binding::OFFERED);
+
+ /** @todo use small lease time internally to quickly free unclaimed offers? */
+ }
+ else
+ {
+ reply.reset(i_createMessage(RTNET_DHCP_MT_ACK, req));
+ reply->addOption(OptRapidCommit(true));
+
+ b->setState(Binding::ACKED);
+ if (!b->isFixed())
+ i_saveLeases();
+ }
+
+ reply->setYiaddr(b->addr());
+ reply->addOption(OptLeaseTime(b->leaseTime()));
+
+ OptParameterRequest optlist(req);
+ optmap_t replyOptions;
+ reply->addOptions(m_pConfig->getOptionsForClient(replyOptions, optlist, vecConfigs));
+
+ // reply->maybeUnicast(req); /** @todo XXX: we reject ciaddr != 0 above */
+ return reply.release();
+}
+
+
+/**
+ * 4.3.2 DHCPREQUEST message
+ *
+ * A DHCPREQUEST message may come from a client responding to a DHCPOFFER
+ * message from a server, from a client verifying a previously allocated IP
+ * address or from a client extending the lease on a network address. If the
+ * DHCPREQUEST message contains a 'server identifier' option, the message is in
+ * response to a DHCPOFFER message. Otherwise, the message is a request to
+ * verify or extend an existing lease. If the client uses a 'client identifier'
+ * in a DHCPREQUEST message, it MUST use that same 'client identifier' in all
+ * subsequent messages. If the client included a list of requested parameters in
+ * a DHCPDISCOVER message, it MUST include that list in all subsequent messages.
+ *
+ * ...
+ *
+ * @throws std::bad_alloc
+ */
+DhcpServerMessage *DHCPD::i_doRequest(const DhcpClientMessage &req)
+{
+ OptRequestedAddress reqAddr(req);
+ if (req.ciaddr().u != 0 && reqAddr.present() && reqAddr.value().u != req.ciaddr().u)
+ {
+ std::unique_ptr<DhcpServerMessage> nak(i_createMessage(RTNET_DHCP_MT_NAC, req));
+ nak->addOption(OptMessage("Requested address does not match ciaddr"));
+ return nak.release();
+ }
+
+ Config::ConfigVec vecConfigs;
+ m_pConfig->getConfigsForClient(vecConfigs, req.clientId(), OptVendorClassId(req), OptUserClassId(req));
+
+ Binding *b = m_db.allocateBinding(req, vecConfigs);
+ if (b == NULL)
+ {
+ return i_createMessage(RTNET_DHCP_MT_NAC, req);
+ }
+
+ std::unique_ptr<DhcpServerMessage> ack(i_createMessage(RTNET_DHCP_MT_ACK, req));
+
+ b->setState(Binding::ACKED);
+ if (!b->isFixed())
+ i_saveLeases();
+
+ ack->setYiaddr(b->addr());
+ ack->addOption(OptLeaseTime(b->leaseTime()));
+
+ OptParameterRequest optlist(req);
+ optmap_t replyOptions;
+ ack->addOptions(m_pConfig->getOptionsForClient(replyOptions, optlist, vecConfigs));
+
+ ack->maybeUnicast(req);
+ return ack.release();
+}
+
+
+/**
+ * 4.3.5 DHCPINFORM message
+ *
+ * The server responds to a DHCPINFORM message by sending a DHCPACK message
+ * directly to the address given in the 'ciaddr' field of the DHCPINFORM
+ * message. The server MUST NOT send a lease expiration time to the client and
+ * SHOULD NOT fill in 'yiaddr'. The server includes other parameters in the
+ * DHCPACK message as defined in section 4.3.1.
+ *
+ * @throws std::bad_alloc
+ */
+DhcpServerMessage *DHCPD::i_doInform(const DhcpClientMessage &req)
+{
+ if (req.ciaddr().u == 0)
+ return NULL;
+
+ OptParameterRequest optlist(req);
+ if (!optlist.present())
+ return NULL;
+
+ Config::ConfigVec vecConfigs;
+ optmap_t info;
+ m_pConfig->getOptionsForClient(info, optlist, m_pConfig->getConfigsForClient(vecConfigs, req.clientId(),
+ OptVendorClassId(req), OptUserClassId(req)));
+ if (info.empty())
+ return NULL;
+
+ std::unique_ptr<DhcpServerMessage> ack(i_createMessage(RTNET_DHCP_MT_ACK, req));
+ ack->addOptions(info);
+ ack->maybeUnicast(req);
+ return ack.release();
+}
+
+
+/**
+ * 4.3.3 DHCPDECLINE message
+ *
+ * If the server receives a DHCPDECLINE message, the client has discovered
+ * through some other means that the suggested network address is already in
+ * use. The server MUST mark the network address as not available and SHOULD
+ * notify the local system administrator of a possible configuration problem.
+ *
+ * @throws nothing
+ */
+DhcpServerMessage *DHCPD::i_doDecline(const DhcpClientMessage &req) RT_NOEXCEPT
+{
+ RT_NOREF(req);
+ return NULL;
+}
+
+
+/**
+ * 4.3.4 DHCPRELEASE message
+ *
+ * Upon receipt of a DHCPRELEASE message, the server marks the network address
+ * as not allocated. The server SHOULD retain a record of the client's
+ * initialization parameters for possible reuse in response to subsequent
+ * requests from the client.
+ *
+ * @throws nothing
+ */
+DhcpServerMessage *DHCPD::i_doRelease(const DhcpClientMessage &req) RT_NOEXCEPT
+{
+ if (req.ciaddr().u != 0)
+ {
+ bool fReleased = m_db.releaseBinding(req);
+ if (fReleased)
+ i_saveLeases();
+ }
+
+ return NULL;
+}
diff --git a/src/VBox/NetworkServices/Dhcpd/DHCPD.h b/src/VBox/NetworkServices/Dhcpd/DHCPD.h
new file mode 100644
index 00000000..5b938868
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/DHCPD.h
@@ -0,0 +1,88 @@
+/* $Id: DHCPD.h $ */
+/** @file
+ * DHCP server - protocol logic
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_DHCPD_h
+#define VBOX_INCLUDED_SRC_Dhcpd_DHCPD_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DhcpdInternal.h"
+#include <iprt/cpp/ministring.h>
+#include "Config.h"
+#include "DhcpMessage.h"
+#include "Db.h"
+
+
+/**
+ * The core of the DHCP server.
+ *
+ * This class is feed DhcpClientMessages that VBoxNetDhcpd has picked up from
+ * the network. After processing a message it returns the appropriate response
+ * (if any) which VBoxNetDhcpd sends out.
+ */
+class DHCPD
+{
+ /** The DHCP configuration. */
+ const Config *m_pConfig;
+ /** The lease database. */
+ Db m_db;
+
+public:
+ DHCPD();
+
+ int init(const Config *) RT_NOEXCEPT;
+
+ DhcpServerMessage *process(const std::unique_ptr<DhcpClientMessage> &req) RT_NOEXCEPT
+ {
+ if (req.get() != NULL)
+ return process(*req.get());
+ return NULL;
+ }
+
+ DhcpServerMessage *process(DhcpClientMessage &req) RT_NOEXCEPT;
+
+private:
+ /** @name DHCP message processing methods
+ * @{ */
+ DhcpServerMessage *i_doDiscover(const DhcpClientMessage &req);
+ DhcpServerMessage *i_doRequest(const DhcpClientMessage &req);
+ DhcpServerMessage *i_doInform(const DhcpClientMessage &req);
+ DhcpServerMessage *i_doDecline(const DhcpClientMessage &req) RT_NOEXCEPT;
+ DhcpServerMessage *i_doRelease(const DhcpClientMessage &req) RT_NOEXCEPT;
+
+ DhcpServerMessage *i_createMessage(int type, const DhcpClientMessage &req);
+ /** @} */
+
+ /** @name Lease database handling
+ * @{ */
+ int i_loadLeases() RT_NOEXCEPT;
+ void i_saveLeases() RT_NOEXCEPT;
+ /** @} */
+};
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_DHCPD_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/Db.cpp b/src/VBox/NetworkServices/Dhcpd/Db.cpp
new file mode 100644
index 00000000..f4e9012a
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/Db.cpp
@@ -0,0 +1,1060 @@
+/* $Id: Db.cpp $ */
+/** @file
+ * DHCP server - address database
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "DhcpdInternal.h"
+#include <iprt/errcore.h>
+
+#include "Db.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Indicates whether has been called successfully yet. */
+bool Binding::g_fFormatRegistered = false;
+
+
+/**
+ * Registers the ClientId format type callback ("%R[binding]").
+ */
+void Binding::registerFormat() RT_NOEXCEPT
+{
+ if (!g_fFormatRegistered)
+ {
+ int rc = RTStrFormatTypeRegister("binding", rtStrFormat, NULL);
+ AssertRC(rc);
+ g_fFormatRegistered = true;
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRFORMATTYPE, Formats ClientId via "%R[binding]".}
+ */
+DECLCALLBACK(size_t)
+Binding::rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char *pszType, void const *pvValue,
+ int cchWidth, int cchPrecision, unsigned fFlags,
+ void *pvUser)
+{
+
+ AssertReturn(strcmp(pszType, "binding") == 0, 0);
+ RT_NOREF(pszType);
+
+ RT_NOREF(cchWidth, cchPrecision, fFlags);
+ RT_NOREF(pvUser);
+
+ const Binding *b = static_cast<const Binding *>(pvValue);
+ if (b == NULL)
+ return pfnOutput(pvArgOutput, RT_STR_TUPLE("<NULL>"));
+
+ size_t cb = RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%RTnaipv4", b->m_addr.u);
+ if (b->m_state == Binding::FREE)
+ cb += pfnOutput(pvArgOutput, RT_STR_TUPLE(" free"));
+ else if (b->m_fFixed)
+ cb += pfnOutput(pvArgOutput, RT_STR_TUPLE(" fixed"));
+ else
+ {
+ cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, " to %R[id], %s, valid from ", &b->m_id, b->stateName());
+
+ Timestamp tsIssued = b->issued();
+ cb += tsIssued.strFormatHelper(pfnOutput, pvArgOutput);
+
+ cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, " for %ds until ", b->leaseTime());
+
+ Timestamp tsValid = b->issued();
+ tsValid.addSeconds(b->leaseTime());
+ cb += tsValid.strFormatHelper(pfnOutput, pvArgOutput);
+ }
+
+ return cb;
+}
+
+
+/**
+ * Used to update the client ID of a fixed address assignment.
+ *
+ * We only have the MAC address when prepraring the binding, so the full client
+ * ID must be supplied when the client requests it.
+ *
+ * @param a_ridClient The client ID.
+ * @throws std::bad_alloc
+ */
+void Binding::idUpdate(const ClientId &a_ridClient)
+{
+ AssertReturnVoid(isFixed());
+ m_id = a_ridClient;
+}
+
+
+/**
+ * Get the state as a string for the XML lease database.
+ */
+const char *Binding::stateName() const RT_NOEXCEPT
+{
+ switch (m_state)
+ {
+ case FREE:
+ return "free";
+ case RELEASED:
+ return "released";
+ case EXPIRED:
+ return "expired";
+ case OFFERED:
+ return "offered";
+ case ACKED:
+ return "acked";
+ default:
+ AssertMsgFailed(("%d\n", m_state));
+ return "released";
+ }
+}
+
+
+/**
+ * Sets the state by name (reverse of Binding::stateName()).
+ */
+Binding &Binding::setState(const char *pszStateName) RT_NOEXCEPT
+{
+ if (strcmp(pszStateName, "free") == 0)
+ m_state = Binding::FREE;
+ else if (strcmp(pszStateName, "released") == 0)
+ m_state = Binding::RELEASED;
+ else if (strcmp(pszStateName, "expired") == 0)
+ m_state = Binding::EXPIRED;
+ else if (strcmp(pszStateName, "offered") == 0)
+ m_state = Binding::OFFERED;
+ else if (strcmp(pszStateName, "acked") == 0)
+ m_state = Binding::ACKED;
+ else
+ {
+ AssertMsgFailed(("%d\n", m_state));
+ m_state = Binding::RELEASED;
+ }
+
+ return *this;
+}
+
+
+/**
+ * Expires the binding if it's past the specified deadline.
+ *
+ * @returns False if already expired, released or freed, otherwise true (i.e.
+ * does not indicate whether action was taken or not).
+ * @param tsDeadline The expiry deadline to use.
+ */
+bool Binding::expire(Timestamp tsDeadline) RT_NOEXCEPT
+{
+ if (m_state <= Binding::EXPIRED || m_fFixed)
+ return false;
+
+ Timestamp tsExpire = m_issued;
+ tsExpire.addSeconds(m_secLease);
+
+ if (tsExpire < tsDeadline)
+ {
+ if (m_state == Binding::OFFERED)
+ setState(Binding::FREE);
+ else
+ setState(Binding::EXPIRED);
+ }
+ return true;
+}
+
+
+/**
+ * Serializes the binding to XML for the lease database.
+ *
+ * @throw std::bad_alloc
+ * @note DHCPServerImpl.cpp contains a reader, keep it in sync.
+ */
+void Binding::toXML(xml::ElementNode *pElmParent) const
+{
+ /*
+ * Lease
+ */
+ xml::ElementNode *pElmLease = pElmParent->createChild("Lease");
+
+ pElmLease->setAttribute("mac", RTCStringFmt("%RTmac", &m_id.mac()));
+ if (m_id.id().present())
+ {
+ /* I'd prefer RTSTRPRINTHEXBYTES_F_SEP_COLON but there's no decoder */
+ size_t cbStrId = m_id.id().value().size() * 2 + 1;
+ char *pszId = new char[cbStrId];
+ int rc = RTStrPrintHexBytes(pszId, cbStrId,
+ &m_id.id().value().front(), m_id.id().value().size(),
+ 0);
+ AssertRC(rc);
+ pElmLease->setAttribute("id", pszId);
+ delete[] pszId;
+ }
+
+ /* unused but we need it to keep the old code happy */
+ pElmLease->setAttribute("network", "0.0.0.0");
+ pElmLease->setAttribute("state", stateName());
+
+ /*
+ * Lease/Address
+ */
+ xml::ElementNode *pElmAddr = pElmLease->createChild("Address");
+ pElmAddr->setAttribute("value", RTCStringFmt("%RTnaipv4", m_addr.u));
+
+ /*
+ * Lease/Time
+ */
+ xml::ElementNode *pElmTime = pElmLease->createChild("Time");
+ pElmTime->setAttribute("issued", m_issued.getAbsSeconds());
+ pElmTime->setAttribute("expiration", m_secLease);
+}
+
+
+/**
+ * Deserializes the binding from the XML lease database.
+ *
+ * @param pElmLease The "Lease" element to serialize into.
+ * @return Pointer to the resulting binding, NULL on failure.
+ * @throw std::bad_alloc
+ * @note DHCPServerImpl.cpp contains a similar reader, keep it in sync.
+ */
+Binding *Binding::fromXML(const xml::ElementNode *pElmLease)
+{
+ /* Note! Lease/@network seems to always have bogus value, ignore it. */
+ /* Note! We parse the mandatory attributes and elements first, then
+ the optional ones. This means things appear a little jumbled. */
+
+ /*
+ * Lease/@mac - mandatory.
+ */
+ const char *pszMacAddress = pElmLease->findAttributeValue("mac");
+ if (!pszMacAddress)
+ DHCP_LOG_RET_NULL(("Binding::fromXML: <Lease> element without 'mac' attribute! Skipping lease.\n"));
+
+ RTMAC mac;
+ int rc = RTNetStrToMacAddr(pszMacAddress, &mac);
+ if (RT_FAILURE(rc))
+ DHCP_LOG_RET_NULL(("Binding::fromXML: Malformed mac address attribute value '%s': %Rrc - Skipping lease.\n",
+ pszMacAddress, rc));
+
+ /*
+ * Lease/Address/@value - mandatory.
+ */
+ const char *pszAddress = pElmLease->findChildElementAttributeValue("Address", "value");
+ if (!pszAddress)
+ DHCP_LOG_RET_NULL(("Binding::fromXML: Could not find <Address> with a 'value' attribute! Skipping lease.\n"));
+
+ RTNETADDRIPV4 addr;
+ rc = RTNetStrToIPv4Addr(pszAddress, &addr);
+ if (RT_FAILURE(rc))
+ DHCP_LOG_RET_NULL(("Binding::fromXML: Malformed IPv4 address value '%s': %Rrc - Skipping lease.\n", pszAddress, rc));
+
+ /*
+ * Lease/Time - mandatory.
+ */
+ const xml::ElementNode *pElmTime = pElmLease->findChildElement("Time");
+ if (pElmTime == NULL)
+ DHCP_LOG_RET_NULL(("Binding::fromXML: No <Time> element under <Lease mac=%RTmac>! Skipping lease.\n", &mac));
+
+ /*
+ * Lease/Time/@issued - mandatory.
+ */
+ int64_t secIssued;
+ if (!pElmTime->getAttributeValue("issued", &secIssued))
+ DHCP_LOG_RET_NULL(("Binding::fromXML: <Time> element for %RTmac has no valid 'issued' attribute! Skipping lease.\n", &mac));
+
+ /*
+ * Lease/Time/@expiration - mandatory.
+ */
+ uint32_t cSecToLive;
+ if (!pElmTime->getAttributeValue("expiration", &cSecToLive))
+ DHCP_LOG_RET_NULL(("Binding::fromXML: <Time> element for %RTmac has no valid 'expiration' attribute! Skipping lease.\n", &mac));
+
+ std::unique_ptr<Binding> b(new Binding(addr));
+
+ /*
+ * Lease/@state - mandatory but not present in old leases file, so pretent
+ * we're loading an expired one if absent.
+ */
+ const char *pszState = pElmLease->findAttributeValue("state");
+ if (pszState)
+ {
+ b->m_issued = Timestamp::absSeconds(secIssued);
+ b->setState(pszState);
+ }
+ else
+ { /** @todo XXX: old code wrote timestamps instead of absolute time. */
+ /* pretend that lease has just ended */
+ LogRel(("Binding::fromXML: No 'state' attribute for <Lease mac=%RTmac> (ts=%RI64 ttl=%RU32)! Assuming EXPIRED.\n",
+ &mac, secIssued, cSecToLive));
+ b->m_issued = Timestamp::now().subSeconds(cSecToLive);
+ b->m_state = Binding::EXPIRED;
+ }
+ b->m_secLease = cSecToLive;
+
+
+ /*
+ * Lease/@id - optional, ignore if bad.
+ * Value format: "deadbeef..." or "de:ad:be:ef...".
+ */
+ const char *pszClientId = pElmLease->findAttributeValue("id");
+ if (pszClientId)
+ {
+ uint8_t abBytes[255];
+ size_t cbActual;
+ rc = RTStrConvertHexBytesEx(pszClientId, abBytes, sizeof(abBytes), RTSTRCONVERTHEXBYTES_F_SEP_COLON, NULL, &cbActual);
+ if (RT_SUCCESS(rc))
+ {
+ b->m_id = ClientId(mac, OptClientId(std::vector<uint8_t>(&abBytes[0], &abBytes[cbActual]))); /* throws bad_alloc */
+ if (rc != VINF_BUFFER_UNDERFLOW && rc != VINF_SUCCESS)
+ LogRel(("Binding::fromXML: imperfect 'id' attribute: rc=%Rrc, cbActual=%u, '%s'\n", rc, cbActual, pszClientId));
+ }
+ else
+ {
+ LogRel(("Binding::fromXML: ignoring malformed 'id' attribute: rc=%Rrc, cbActual=%u, '%s'\n",
+ rc, cbActual, pszClientId));
+ b->m_id = ClientId(mac, OptClientId());
+ }
+ }
+ else
+ b->m_id = ClientId(mac, OptClientId());
+
+ return b.release();
+}
+
+
+
+/*********************************************************************************************************************************
+* Class Db Implementation *
+*********************************************************************************************************************************/
+
+Db::Db()
+ : m_pConfig(NULL)
+{
+}
+
+
+Db::~Db()
+{
+ /** @todo free bindings */
+}
+
+
+int Db::init(const Config *pConfig)
+{
+ Binding::registerFormat();
+
+ m_pConfig = pConfig;
+
+ int rc = m_pool.init(pConfig->getIPv4PoolFirst(), pConfig->getIPv4PoolLast());
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * If the server IP is in the dynamic range, preallocate it like a fixed assignment.
+ */
+ rc = i_enterFixedAddressAssignment(pConfig->getIPv4Address(), pConfig->getMacAddress());
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Preallocate any fixed address assignments:
+ */
+ Config::HostConfigVec vecHostConfigs;
+ rc = pConfig->getFixedAddressConfigs(vecHostConfigs);
+ for (Config::HostConfigVec::const_iterator it = vecHostConfigs.begin();
+ it != vecHostConfigs.end() && RT_SUCCESS(rc); ++it)
+ rc = i_enterFixedAddressAssignment((*it)->getFixedAddress(), (*it)->getMACAddress());
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Used by Db::init() to register a fixed address assignment.
+ *
+ * @returns IPRT status code.
+ * @param a_rAddress The IPv4 address assignment.
+ * @param a_rMACAddress The MAC address.
+ */
+int Db::i_enterFixedAddressAssignment(RTNETADDRIPV4 const &a_rAddress, RTMAC const &a_rMACAddress) RT_NOEXCEPT
+{
+ LogRelFunc(("%RTmac: %RTnaipv4\n", &a_rMACAddress, a_rAddress));
+ Assert(m_pConfig->isInIPv4Network(a_rAddress)); /* should've been checked elsewhere already */
+
+ /*
+ * If the address is part of the pool, we have to allocate it to
+ * prevent it from being used again.
+ */
+ if (m_pool.contains(a_rAddress))
+ {
+ if (!m_pool.allocate(a_rAddress))
+ {
+ LogRelFunc(("%RTnaipv4 already allocated?\n", a_rAddress));
+ return VERR_ADDRESS_CONFLICT;
+ }
+ }
+
+ /*
+ * Create the binding.
+ */
+ Binding *pBinding = NULL;
+ try
+ {
+ pBinding = new Binding(a_rAddress, a_rMACAddress, true /*fFixed*/);
+ m_bindings.push_front(pBinding);
+ }
+ catch (std::bad_alloc &)
+ {
+ if (pBinding)
+ delete pBinding;
+ return VERR_NO_MEMORY;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Expire old binding (leases).
+ */
+void Db::expire() RT_NOEXCEPT
+{
+ const Timestamp now = Timestamp::now();
+ for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+ b->expire(now);
+ }
+}
+
+
+/**
+ * Internal worker that creates a binding for the given client, allocating new
+ * IPv4 address for it.
+ *
+ * @returns Pointer to the binding.
+ * @param id The client ID.
+ */
+Binding *Db::i_createBinding(const ClientId &id)
+{
+ Binding *pBinding = NULL;
+ RTNETADDRIPV4 addr = m_pool.allocate();
+ if (addr.u != 0)
+ {
+ try
+ {
+ pBinding = new Binding(addr, id);
+ m_bindings.push_front(pBinding);
+ }
+ catch (std::bad_alloc &)
+ {
+ if (pBinding)
+ delete pBinding;
+ /** @todo free address (no pool method for that) */
+ }
+ }
+ return pBinding;
+}
+
+
+/**
+ * Internal worker that creates a binding to the specified IPv4 address for the
+ * given client.
+ *
+ * @returns Pointer to the binding.
+ * NULL if the address is in use or we ran out of memory.
+ * @param addr The IPv4 address.
+ * @param id The client.
+ */
+Binding *Db::i_createBinding(RTNETADDRIPV4 addr, const ClientId &id)
+{
+ bool fAvailable = m_pool.allocate(addr);
+ if (!fAvailable)
+ {
+ /** @todo
+ * XXX: this should not happen. If the address is from the
+ * pool, which we have verified before, then either it's in
+ * the free pool or there's an binding (possibly free) for it.
+ */
+ return NULL;
+ }
+
+ Binding *b = new Binding(addr, id);
+ m_bindings.push_front(b);
+ return b;
+}
+
+
+/**
+ * Internal worker that allocates an IPv4 address for the given client, taking
+ * the preferred address (@a addr) into account when possible and if non-zero.
+ */
+Binding *Db::i_allocateAddress(const ClientId &id, RTNETADDRIPV4 addr)
+{
+ Assert(addr.u == 0 || addressBelongs(addr));
+
+ if (addr.u != 0)
+ LogRel(("> allocateAddress %RTnaipv4 to client %R[id]\n", addr.u, &id));
+ else
+ LogRel(("> allocateAddress to client %R[id]\n", &id));
+
+ /*
+ * Allocate existing address if client has one. Ignore requested
+ * address in that case. While here, look for free addresses and
+ * addresses that can be reused.
+ */
+ Binding *addrBinding = NULL;
+ Binding *freeBinding = NULL;
+ Binding *reuseBinding = NULL;
+ const Timestamp now = Timestamp::now();
+ for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+ b->expire(now);
+
+ /*
+ * We've already seen this client, give it its old binding.
+ *
+ * If the client's MAC address is configured with a fixed
+ * address, give its preconfigured binding. Fixed bindings
+ * are always at the head of the m_bindings list, so we
+ * won't be confused by any old leases of the client.
+ */
+ if (b->m_id == id)
+ {
+ LogRel(("> ... found existing binding %R[binding]\n", b));
+ return b;
+ }
+ if (b->isFixed() && b->id().mac() == id.mac())
+ {
+ b->idUpdate(id);
+ LogRel(("> ... found fixed binding %R[binding]\n", b));
+ return b;
+ }
+
+ if (addr.u != 0 && b->m_addr.u == addr.u)
+ {
+ Assert(addrBinding == NULL);
+ addrBinding = b;
+ LogRel(("> .... noted existing binding %R[binding]\n", addrBinding));
+ }
+
+ /* if we haven't found a free binding yet, keep looking */
+ if (freeBinding == NULL)
+ {
+ if (b->m_state == Binding::FREE)
+ {
+ freeBinding = b;
+ LogRel(("> .... noted free binding %R[binding]\n", freeBinding));
+ continue;
+ }
+
+ /* still no free binding, can this one be reused? */
+ if (b->m_state == Binding::RELEASED)
+ {
+ if ( reuseBinding == NULL
+ /* released binding is better than an expired one */
+ || reuseBinding->m_state == Binding::EXPIRED)
+ {
+ reuseBinding = b;
+ LogRel(("> .... noted released binding %R[binding]\n", reuseBinding));
+ }
+ }
+ else if (b->m_state == Binding::EXPIRED)
+ {
+ if ( reuseBinding == NULL
+ /* long expired binding is bettern than a recent one */
+ /* || (reuseBinding->m_state == Binding::EXPIRED && b->olderThan(reuseBinding)) */)
+ {
+ reuseBinding = b;
+ LogRel(("> .... noted expired binding %R[binding]\n", reuseBinding));
+ }
+ }
+ }
+ }
+
+ /*
+ * Allocate requested address if we can.
+ */
+ if (addr.u != 0)
+ {
+ if (addrBinding == NULL)
+ {
+ addrBinding = i_createBinding(addr, id);
+ Assert(addrBinding != NULL);
+ LogRel(("> .... creating new binding for this address %R[binding]\n", addrBinding));
+ return addrBinding;
+ }
+
+ if (addrBinding->m_state <= Binding::EXPIRED) /* not in use */
+ {
+ LogRel(("> .... reusing %s binding for this address\n", addrBinding->stateName()));
+ addrBinding->giveTo(id);
+ return addrBinding;
+ }
+ LogRel(("> .... cannot reuse %s binding for this address\n", addrBinding->stateName()));
+ }
+
+ /*
+ * Allocate new (or reuse).
+ */
+ Binding *idBinding = NULL;
+ if (freeBinding != NULL)
+ {
+ idBinding = freeBinding;
+ LogRel(("> .... reusing free binding\n"));
+ }
+ else
+ {
+ idBinding = i_createBinding();
+ if (idBinding != NULL)
+ LogRel(("> .... creating new binding\n"));
+ else
+ {
+ idBinding = reuseBinding;
+ if (idBinding != NULL)
+ LogRel(("> .... reusing %s binding %R[binding]\n", reuseBinding->stateName(), reuseBinding));
+ else
+ DHCP_LOG_RET_NULL(("> .... failed to allocate binding\n"));
+ }
+ }
+
+ idBinding->giveTo(id);
+ LogRel(("> .... allocated %R[binding]\n", idBinding));
+
+ return idBinding;
+}
+
+
+
+/**
+ * Called by DHCPD to allocate a binding for the specified request.
+ *
+ * @returns Pointer to the binding, NULL on failure.
+ * @param req The DHCP request being served.
+ * @param rConfigVec The configurations that applies to the client.
+ * Used for lease time calculation.
+ */
+Binding *Db::allocateBinding(const DhcpClientMessage &req, Config::ConfigVec const &rConfigVec)
+{
+ const ClientId &id(req.clientId());
+
+ /*
+ * Get and validate the requested address (if present).
+ *
+ * Fixed assignments are often outside the dynamic range, so we much detect
+ * those to make sure they aren't rejected based on IP range. ASSUMES fixed
+ * assignments are at the head of the binding list.
+ */
+ OptRequestedAddress reqAddr(req);
+ if (reqAddr.present() && !addressBelongs(reqAddr.value()))
+ {
+ bool fIsFixed = false;
+ for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end() && (*it)->isFixed(); ++it)
+ if (reqAddr.value().u == (*it)->addr().u)
+ {
+ if ( (*it)->id() == id
+ || (*it)->id().mac() == id.mac())
+ {
+ fIsFixed = true;
+ break;
+ }
+ }
+ if (fIsFixed)
+ reqAddr = OptRequestedAddress();
+ else if (req.messageType() == RTNET_DHCP_MT_DISCOVER)
+ {
+ LogRel(("DISCOVER: ignoring invalid requested address\n"));
+ reqAddr = OptRequestedAddress();
+ }
+ else
+ DHCP_LOG_RET_NULL(("rejecting invalid requested address\n"));
+ }
+
+ /*
+ * Allocate the address.
+ */
+ Binding *b = i_allocateAddress(id, reqAddr.value());
+ if (b != NULL)
+ {
+ Assert(b->id() == id);
+
+ /*
+ * Figure out the lease time.
+ */
+ uint32_t secMin = 0;
+ uint32_t secDfl = 0;
+ uint32_t secMax = 0;
+ for (Config::ConfigVec::const_iterator it = rConfigVec.begin(); it != rConfigVec.end(); ++it)
+ {
+ ConfigLevelBase const *pConfig = *it;
+ if (secMin == 0)
+ secMin = pConfig->getMinLeaseTime();
+ if (secDfl == 0)
+ secDfl = pConfig->getDefaultLeaseTime();
+ if (secMax == 0)
+ secMax = pConfig->getMaxLeaseTime();
+ }
+ Assert(secMin); Assert(secMax); Assert(secDfl); /* global config always have non-defaults set */
+ if (secMin > secMax)
+ secMin = secMax;
+
+ OptLeaseTime reqLeaseTime(req);
+ if (!reqLeaseTime.present())
+ {
+ b->setLeaseTime(secDfl);
+ LogRel2(("Lease time %u secs (default)\n", b->leaseTime()));
+ }
+ else if (reqLeaseTime.value() < secMin)
+ {
+ b->setLeaseTime(secMin);
+ LogRel2(("Lease time %u secs (min)\n", b->leaseTime()));
+ }
+ else if (reqLeaseTime.value() > secMax)
+ {
+ b->setLeaseTime(secMax);
+ LogRel2(("Lease time %u secs (max)\n", b->leaseTime()));
+ }
+ else
+ {
+ b->setLeaseTime(reqLeaseTime.value());
+ LogRel2(("Lease time %u secs (requested)\n", b->leaseTime()));
+ }
+ }
+ return b;
+}
+
+
+/**
+ * Internal worker used by loadLease().
+ *
+ * @returns IPRT status code.
+ * @param pNewBinding The new binding to add.
+ */
+int Db::i_addBinding(Binding *pNewBinding) RT_NOEXCEPT
+{
+ /*
+ * Validate the binding against the range and existing bindings.
+ */
+ if (!addressBelongs(pNewBinding->m_addr))
+ {
+ LogRel(("Binding for out of range address %RTnaipv4 ignored\n", pNewBinding->m_addr.u));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+
+ if (pNewBinding->m_addr.u == b->m_addr.u)
+ {
+ LogRel(("> ADD: %R[binding]\n", pNewBinding));
+ LogRel(("> .... duplicate ip: %R[binding]\n", b));
+ return VERR_DUPLICATE;
+ }
+
+ if (pNewBinding->m_id == b->m_id)
+ {
+ LogRel(("> ADD: %R[binding]\n", pNewBinding));
+ LogRel(("> .... duplicate id: %R[binding]\n", b));
+ return VERR_DUPLICATE;
+ }
+ }
+
+ /*
+ * Allocate the address and add the binding to the list.
+ */
+ AssertLogRelMsgReturn(m_pool.allocate(pNewBinding->m_addr),
+ ("> ADD: failed to claim IP %R[binding]\n", pNewBinding),
+ VERR_INTERNAL_ERROR);
+ try
+ {
+ m_bindings.push_back(pNewBinding);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Called by DHCP to cancel an offset.
+ *
+ * @param req The DHCP request.
+ */
+void Db::cancelOffer(const DhcpClientMessage &req) RT_NOEXCEPT
+{
+ const OptRequestedAddress reqAddr(req);
+ if (!reqAddr.present())
+ return;
+
+ const RTNETADDRIPV4 addr = reqAddr.value();
+ const ClientId &id(req.clientId());
+
+ for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+
+ if (b->addr().u == addr.u && b->id() == id)
+ {
+ if (b->state() == Binding::OFFERED)
+ {
+ LogRel2(("Db::cancelOffer: cancelling %R[binding]\n", b));
+ if (!b->isFixed())
+ {
+ b->setLeaseTime(0);
+ b->setState(Binding::RELEASED);
+ }
+ else
+ b->setState(Binding::ACKED);
+ }
+ else
+ LogRel2(("Db::cancelOffer: not offered state: %R[binding]\n", b));
+ return;
+ }
+ }
+ LogRel2(("Db::cancelOffer: not found (%RTnaipv4, %R[id])\n", addr.u, &id));
+}
+
+
+/**
+ * Called by DHCP to cancel an offset.
+ *
+ * @param req The DHCP request.
+ * @returns true if found and released, otherwise false.
+ * @throws nothing
+ */
+bool Db::releaseBinding(const DhcpClientMessage &req) RT_NOEXCEPT
+{
+ const RTNETADDRIPV4 addr = req.ciaddr();
+ const ClientId &id(req.clientId());
+
+ for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+
+ if (b->addr().u == addr.u && b->id() == id)
+ {
+ LogRel2(("Db::releaseBinding: releasing %R[binding]\n", b));
+ if (!b->isFixed())
+ {
+ b->setState(Binding::RELEASED);
+ return true;
+ }
+ b->setState(Binding::ACKED);
+ return false;
+ }
+ }
+
+ LogRel2(("Db::releaseBinding: not found (%RTnaipv4, %R[id])\n", addr.u, &id));
+ return false;
+}
+
+
+/**
+ * Called by DHCPD to write out the lease database to @a strFilename.
+ *
+ * @returns IPRT status code.
+ * @param strFilename The file to write it to.
+ */
+int Db::writeLeases(const RTCString &strFilename) const RT_NOEXCEPT
+{
+ LogRel(("writing leases to %s\n", strFilename.c_str()));
+
+ /** @todo This could easily be written directly to the file w/o going thru
+ * a xml::Document, xml::XmlFileWriter, hammering the heap and being
+ * required to catch a lot of different exceptions at various points.
+ * (RTStrmOpen, bunch of RTStrmPrintf using \%RMas and \%RMes.,
+ * RTStrmClose closely followed by a couple of renames.)
+ */
+
+ /*
+ * Create the document and root element.
+ */
+ xml::Document doc;
+ try
+ {
+ xml::ElementNode *pElmRoot = doc.createRootElement("Leases");
+ pElmRoot->setAttribute("version", "1.0");
+
+ /*
+ * Add the leases.
+ */
+ for (bindings_t::const_iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
+ {
+ const Binding *b = *it;
+ if (!b->isFixed())
+ b->toXML(pElmRoot);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ /*
+ * Write the document to the specified file in a safe manner (written to temporary
+ * file, renamed to destination on success)
+ */
+ try
+ {
+ xml::XmlFileWriter writer(doc);
+ writer.write(strFilename.c_str(), true /*fSafe*/);
+ }
+ catch (const xml::EIPRTFailure &e)
+ {
+ LogRel(("%s\n", e.what()));
+ return e.rc();
+ }
+ catch (const RTCError &e)
+ {
+ LogRel(("%s\n", e.what()));
+ return VERR_GENERAL_FAILURE;
+ }
+ catch (...)
+ {
+ LogRel(("Unknown exception while writing '%s'\n", strFilename.c_str()));
+ return VERR_UNEXPECTED_EXCEPTION;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Called by DHCPD to load the lease database to @a strFilename.
+ *
+ * @note Does not clear the database state before doing the load.
+ *
+ * @returns IPRT status code.
+ * @param strFilename The file to load it from.
+ * @throws nothing
+ */
+int Db::loadLeases(const RTCString &strFilename) RT_NOEXCEPT
+{
+ LogRel(("loading leases from %s\n", strFilename.c_str()));
+
+ /*
+ * Load the file into an XML document.
+ */
+ xml::Document doc;
+ try
+ {
+ xml::XmlFileParser parser;
+ parser.read(strFilename.c_str(), doc);
+ }
+ catch (const xml::EIPRTFailure &e)
+ {
+ LogRel(("%s\n", e.what()));
+ return e.rc();
+ }
+ catch (const RTCError &e)
+ {
+ LogRel(("%s\n", e.what()));
+ return VERR_GENERAL_FAILURE;
+ }
+ catch (...)
+ {
+ LogRel(("Unknown exception while reading and parsing '%s'\n", strFilename.c_str()));
+ return VERR_UNEXPECTED_EXCEPTION;
+ }
+
+ /*
+ * Check that the root element is "Leases" and process its children.
+ */
+ xml::ElementNode *pElmRoot = doc.getRootElement();
+ if (!pElmRoot)
+ {
+ LogRel(("No root element in '%s'\n", strFilename.c_str()));
+ return VERR_NOT_FOUND;
+ }
+ if (!pElmRoot->nameEquals("Leases"))
+ {
+ LogRel(("No root element is not 'Leases' in '%s', but '%s'\n", strFilename.c_str(), pElmRoot->getName()));
+ return VERR_NOT_FOUND;
+ }
+
+ int rc = VINF_SUCCESS;
+ xml::NodesLoop it(*pElmRoot);
+ const xml::ElementNode *pElmLease;
+ while ((pElmLease = it.forAllNodes()) != NULL)
+ {
+ if (pElmLease->nameEquals("Lease"))
+ {
+ int rc2 = i_loadLease(pElmLease);
+ if (RT_SUCCESS(rc2))
+ { /* likely */ }
+ else if (rc2 == VERR_NO_MEMORY)
+ return rc2;
+ else
+ rc = -rc2;
+ }
+ else
+ LogRel(("Ignoring unexpected element '%s' under 'Leases'...\n", pElmLease->getName()));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Internal worker for loadLeases() that handles one 'Lease' element.
+ *
+ * @param pElmLease The 'Lease' element to handle.
+ * @return IPRT status code.
+ */
+int Db::i_loadLease(const xml::ElementNode *pElmLease) RT_NOEXCEPT
+{
+ Binding *pBinding = NULL;
+ try
+ {
+ pBinding = Binding::fromXML(pElmLease);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ if (pBinding)
+ {
+ bool fExpired = pBinding->expire();
+ if (!fExpired)
+ LogRel(("> LOAD: lease %R[binding]\n", pBinding));
+ else
+ LogRel(("> LOAD: EXPIRED lease %R[binding]\n", pBinding));
+
+ int rc = i_addBinding(pBinding);
+ if (RT_FAILURE(rc))
+ delete pBinding;
+ return rc;
+ }
+ LogRel(("> LOAD: failed to load lease!\n"));
+ return VERR_PARSE_ERROR;
+}
diff --git a/src/VBox/NetworkServices/Dhcpd/Db.h b/src/VBox/NetworkServices/Dhcpd/Db.h
new file mode 100644
index 00000000..5208cf3b
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/Db.h
@@ -0,0 +1,221 @@
+/* $Id: Db.h $ */
+/** @file
+ * DHCP server - address database
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_Db_h
+#define VBOX_INCLUDED_SRC_Dhcpd_Db_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DhcpdInternal.h"
+#include <iprt/net.h>
+
+#include <iprt/cpp/ministring.h>
+#include <iprt/cpp/xml.h>
+
+#include <list>
+
+#include "Timestamp.h"
+#include "ClientId.h"
+#include "IPv4Pool.h"
+#include "Config.h"
+#include "DhcpMessage.h"
+
+
+/**
+ * An address binding in the lease database.
+ *
+ * This is how an allocated IPv4 address is mananged.
+ */
+class Binding
+{
+ friend class Db;
+
+public:
+ enum State { FREE, RELEASED, EXPIRED, OFFERED, ACKED };
+
+private:
+ const RTNETADDRIPV4 m_addr;
+ State m_state;
+ ClientId m_id;
+ Timestamp m_issued;
+ uint32_t m_secLease;
+ /** Set if this is a fixed assignment. */
+ bool m_fFixed;
+
+public:
+ Binding();
+ Binding(const Binding &);
+
+ explicit Binding(RTNETADDRIPV4 a_Addr)
+ : m_addr(a_Addr), m_state(FREE), m_issued(), m_secLease(0), m_fFixed(false)
+ {}
+
+ Binding(RTNETADDRIPV4 a_Addr, const ClientId &a_id)
+ : m_addr(a_Addr), m_state(FREE), m_id(a_id), m_issued(), m_secLease(0), m_fFixed(false)
+ {}
+
+ Binding(RTNETADDRIPV4 a_Addr, const RTMAC &a_MACAddress, bool a_fFixed)
+ : m_addr(a_Addr)
+ , m_state(ACKED)
+ , m_id(ClientId(a_MACAddress, OptClientId()))
+ , m_issued(Timestamp::now())
+ , m_secLease(UINT32_MAX - 1)
+ , m_fFixed(a_fFixed)
+ {}
+
+
+ /** @name Attribute accessors
+ * @{ */
+ RTNETADDRIPV4 addr() const RT_NOEXCEPT { return m_addr; }
+
+ const ClientId &id() const RT_NOEXCEPT { return m_id; }
+ void idUpdate(const ClientId &a_ridClient);
+
+ uint32_t leaseTime() const RT_NOEXCEPT { return m_secLease; }
+ Timestamp issued() const RT_NOEXCEPT { return m_issued; }
+
+ State state() const RT_NOEXCEPT { return m_state; }
+ const char *stateName() const RT_NOEXCEPT;
+ Binding &setState(const char *pszStateName) RT_NOEXCEPT;
+ Binding &setState(State stateParam) RT_NOEXCEPT
+ {
+ m_state = stateParam;
+ return *this;
+ }
+
+ bool isFixed() const RT_NOEXCEPT { return m_fFixed; }
+ /** @} */
+
+
+ Binding &setLeaseTime(uint32_t secLease) RT_NOEXCEPT
+ {
+ m_issued = Timestamp::now();
+ m_secLease = secLease;
+ return *this;
+ }
+
+ /** Reassigns the binding to the given client. */
+ Binding &giveTo(const ClientId &a_id) RT_NOEXCEPT
+ {
+ m_id = a_id;
+ m_state = FREE;
+ return *this;
+ }
+
+ void free()
+ {
+ m_id = ClientId();
+ m_state = FREE;
+ }
+
+ bool expire(Timestamp tsDeadline) RT_NOEXCEPT;
+ bool expire() RT_NOEXCEPT
+ {
+ return expire(Timestamp::now());
+ }
+
+ /** @name Serialization
+ * @{ */
+ static Binding *fromXML(const xml::ElementNode *pElmLease);
+ void toXML(xml::ElementNode *pElmParent) const;
+ /** @} */
+
+ /** @name String formatting of %R[binding].
+ * @{ */
+ static void registerFormat() RT_NOEXCEPT;
+private:
+ static DECLCALLBACK(size_t) rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, const char *pszType,
+ void const *pvValue, int cchWidth, int cchPrecision, unsigned fFlags, void *pvUser);
+ static bool g_fFormatRegistered;
+ /** @} */
+
+ Binding &operator=(const Binding &); /**< Shuts up warning C4626 (incorrect warning?). */
+};
+
+
+/**
+ * The lease database.
+ *
+ * There is currently just one instance of this class in a running DHCP server
+ * residing in Dhcpd::m_db. It covers one single range of IPv4 addresses, which
+ * currently unbound addressed are managed by m_pool. The allocated addresses
+ * are kept in the m_bindings list. Once an address has been allocated, it will
+ * stay in the m_bindings list even after released or expired.
+ */
+class Db
+{
+private:
+ typedef std::list<Binding *> bindings_t;
+
+ /** Configuration (set at init).
+ * @note Currently not used. */
+ const Config *m_pConfig;
+ /** The lease database.
+ * @note Since fixed assignments are added during initialization, they will
+ * always be first. The allocateBinding() code depends on this. */
+ bindings_t m_bindings;
+ /** Address allocation pool. */
+ IPv4Pool m_pool;
+
+public:
+ Db();
+ ~Db();
+
+ int init(const Config *pConfig);
+
+ /** Check if @a addr belonges to this lease database. */
+ bool addressBelongs(RTNETADDRIPV4 addr) const RT_NOEXCEPT { return m_pool.contains(addr); }
+
+ Binding *allocateBinding(const DhcpClientMessage &req, Config::ConfigVec const &rConfigVec);
+ bool releaseBinding(const DhcpClientMessage &req) RT_NOEXCEPT;
+
+ void cancelOffer(const DhcpClientMessage &req) RT_NOEXCEPT;
+
+ void expire() RT_NOEXCEPT;
+
+ /** @name Database serialization methods
+ * @{ */
+ int loadLeases(const RTCString &strFilename) RT_NOEXCEPT;
+private:
+ int i_loadLease(const xml::ElementNode *pElmLease) RT_NOEXCEPT;
+public:
+ int writeLeases(const RTCString &strFilename) const RT_NOEXCEPT;
+ /** @} */
+
+private:
+ int i_enterFixedAddressAssignment(RTNETADDRIPV4 const &a_rAddress, RTMAC const &a_rMACAddress) RT_NOEXCEPT;
+ Binding *i_createBinding(const ClientId &id = ClientId());
+ Binding *i_createBinding(RTNETADDRIPV4 addr, const ClientId &id = ClientId());
+
+ Binding *i_allocateAddress(const ClientId &id, RTNETADDRIPV4 addr);
+
+ /* add binding e.g. from the leases file */
+ int i_addBinding(Binding *pNewBinding) RT_NOEXCEPT;
+};
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_Db_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp
new file mode 100644
index 00000000..718dcf4e
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp
@@ -0,0 +1,447 @@
+/* $Id: DhcpMessage.cpp $ */
+/** @file
+ * DHCP Message and its de/serialization.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "DhcpdInternal.h"
+#include "DhcpMessage.h"
+#include "DhcpOptions.h"
+
+#include <iprt/ctype.h>
+#include <iprt/string.h>
+
+
+
+DhcpMessage::DhcpMessage()
+ : m_xid(0)
+ , m_flags(0)
+ , m_ciaddr()
+ , m_yiaddr()
+ , m_siaddr()
+ , m_giaddr()
+#if 0 /* not currently unused */
+ , m_sname()
+ , m_file()
+#endif
+ , m_optMessageType()
+{
+}
+
+
+/**
+ * Does common message dumping.
+ */
+void DhcpMessage::dump() const RT_NOEXCEPT
+{
+ switch (m_optMessageType.value())
+ {
+ case RTNET_DHCP_MT_DISCOVER: LogRel(("DISCOVER")); break;
+ case RTNET_DHCP_MT_OFFER: LogRel(("OFFER")); break;
+ case RTNET_DHCP_MT_REQUEST: LogRel(("REQUEST")); break;
+ case RTNET_DHCP_MT_DECLINE: LogRel(("DECLINE")); break;
+ case RTNET_DHCP_MT_ACK: LogRel(("ACK")); break;
+ case RTNET_DHCP_MT_NAC: LogRel(("NAC")); break;
+ case RTNET_DHCP_MT_RELEASE: LogRel(("RELEASE")); break;
+ case RTNET_DHCP_MT_INFORM: LogRel(("INFORM")); break;
+ default:
+ LogRel(("<Unknown Mesage Type %d>", m_optMessageType.value()));
+ break;
+ }
+
+ LogRel((" xid 0x%08x", m_xid));
+ LogRel((" chaddr %RTmac\n", &m_mac));
+ LogRel((" ciaddr %RTnaipv4", m_ciaddr.u));
+ if (m_yiaddr.u != 0)
+ LogRel((" yiaddr %RTnaipv4", m_yiaddr.u));
+ if (m_siaddr.u != 0)
+ LogRel((" siaddr %RTnaipv4", m_siaddr.u));
+ if (m_giaddr.u != 0)
+ LogRel((" giaddr %RTnaipv4", m_giaddr.u));
+ if (broadcast())
+ LogRel((" broadcast\n"));
+ else
+ LogRel(("\n"));
+}
+
+
+/*********************************************************************************************************************************
+* DhcpClientMessage Implementation *
+*********************************************************************************************************************************/
+
+/* static */
+DhcpClientMessage *DhcpClientMessage::parse(bool broadcasted, const void *buf, size_t buflen)
+{
+ /*
+ * Validate the request.
+ */
+ if (buflen < RT_OFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts))
+ DHCP_LOG2_RET_NULL(("DhcpClientMessage::parse: %zu bytes datagram is too short\n", buflen));
+
+ PCRTNETBOOTP bp = (PCRTNETBOOTP)buf;
+
+ if (bp->bp_op != RTNETBOOTP_OP_REQUEST)
+ DHCP_LOG2_RET_NULL(("DhcpClientMessage::parse: bad opcode: %d\n", bp->bp_op));
+
+ if (bp->bp_htype != RTNET_ARP_ETHER)
+ DHCP_LOG2_RET_NULL(("DhcpClientMessage::parse: unsupported htype %d\n", bp->bp_htype));
+
+ if (bp->bp_hlen != sizeof(RTMAC))
+ DHCP_LOG2_RET_NULL(("DhcpClientMessage::parse: unexpected hlen %d\n", bp->bp_hlen));
+
+ if ( (bp->bp_chaddr.Mac.au8[0] & 0x01) != 0
+ && (bp->bp_flags & RTNET_DHCP_FLAG_BROADCAST) == 0)
+ LogRel2(("DhcpClientMessage::parse: multicast chaddr %RTmac without broadcast flag\n", &bp->bp_chaddr.Mac));
+
+ /* we don't want to deal with forwarding */
+ if (bp->bp_giaddr.u != 0)
+ DHCP_LOG2_RET_NULL(("DhcpClientMessage::parse: giaddr %RTnaipv4\n", bp->bp_giaddr.u));
+
+ if (bp->bp_hops != 0)
+ DHCP_LOG2_RET_NULL(("DhcpClientMessage::parse: non-zero hops %d\n", bp->bp_hops));
+
+ if (bp->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32_C(RTNET_DHCP_COOKIE))
+ DHCP_LOG2_RET_NULL(("DhcpClientMessage::parse: bad cookie %#RX32\n", bp->bp_vend.Dhcp.dhcp_cookie));
+
+ /*
+ * Convert it into a DhcpClientMessage instance.
+ */
+ std::unique_ptr<DhcpClientMessage> msg(new DhcpClientMessage());
+
+ msg->m_broadcasted = broadcasted;
+ msg->m_xid = bp->bp_xid;
+ msg->m_flags = bp->bp_flags;
+ msg->m_mac = bp->bp_chaddr.Mac;
+ msg->m_ciaddr = bp->bp_ciaddr;
+ msg->m_yiaddr = bp->bp_yiaddr;
+ msg->m_siaddr = bp->bp_siaddr;
+ msg->m_giaddr = bp->bp_giaddr;
+
+ int fOptOverload = msg->i_parseOptions(&bp->bp_vend.Dhcp.dhcp_opts[0],
+ buflen - RT_OFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts));
+ if (fOptOverload < 0)
+ return NULL;
+
+ /* "The 'file' field MUST be interpreted next ..." */
+ if (fOptOverload & RTNET_DHCP_OPTION_OVERLOAD_FILE)
+ {
+ int status = msg->i_parseOptions(bp->bp_file, sizeof(bp->bp_file));
+ if (status != 0)
+ return NULL;
+ }
+#if 0 /* not currently unused */
+ else if (bp->bp_file[0] != '\0')
+ {
+ /* must be zero terminated, ignore if not */
+ const char *pszFile = (const char *)bp->bp_file;
+ size_t len = RTStrNLen(pszFile, sizeof(bp->bp_file));
+ if (len < sizeof(bp->bp_file))
+ msg->m_file.assign(pszFile, len);
+ }
+#endif
+
+ /* "... followed by the 'sname' field." */
+ if (fOptOverload & RTNET_DHCP_OPTION_OVERLOAD_SNAME)
+ {
+ int status = msg->i_parseOptions(bp->bp_sname, sizeof(bp->bp_sname));
+ if (status != 0) /* NB: this includes "nested" Option Overload */
+ return NULL;
+ }
+#if 0 /* not currently unused */
+ else if (bp->bp_sname[0] != '\0')
+ {
+ /* must be zero terminated, ignore if not */
+ const char *pszSName = (const char *)bp->bp_sname;
+ size_t len = RTStrNLen(pszSName, sizeof(bp->bp_sname));
+ if (len < sizeof(bp->bp_sname))
+ msg->m_sname.assign(pszSName, len);
+ }
+#endif
+
+ msg->m_optMessageType = OptMessageType(*msg);
+ if (!msg->m_optMessageType.present())
+ return NULL;
+
+ msg->m_id = ClientId(msg->m_mac, OptClientId(*msg));
+
+ return msg.release();
+}
+
+
+int DhcpClientMessage::i_parseOptions(const uint8_t *pbBuf, size_t cbBuf) RT_NOEXCEPT
+{
+ int fOptOverload = 0;
+ while (cbBuf > 0)
+ {
+ uint8_t const bOpt = *pbBuf++;
+ --cbBuf;
+
+ if (bOpt == RTNET_DHCP_OPT_PAD)
+ continue;
+
+ if (bOpt == RTNET_DHCP_OPT_END)
+ break;
+
+ if (cbBuf == 0)
+ DHCP_LOG_RET(-1, ("option %d has no length field\n", bOpt));
+
+ uint8_t const cbOpt = *pbBuf++;
+ --cbBuf;
+
+ if (cbOpt > cbBuf)
+ DHCP_LOG_RET(-1, ("option %d truncated (length %d, but only %zu bytes left)\n", bOpt, cbOpt, cbBuf));
+
+#if 0
+ rawopts_t::const_iterator it(m_optmap.find(bOpt));
+ if (it != m_optmap.cend())
+ return -1;
+#endif
+ if (bOpt == RTNET_DHCP_OPT_OPTION_OVERLOAD)
+ {
+ if (cbOpt != 1)
+ DHCP_LOG_RET(-1, ("Overload Option (option %d) has invalid length %d\n", bOpt, cbOpt));
+
+ fOptOverload = *pbBuf;
+
+ if ((fOptOverload & ~RTNET_DHCP_OPTION_OVERLOAD_MASK) != 0)
+ DHCP_LOG_RET(-1, ("Overload Option (option %d) has invalid value 0x%x\n", bOpt, fOptOverload));
+ }
+ else
+ m_rawopts.insert(std::make_pair(bOpt, octets_t(pbBuf, pbBuf + cbOpt)));
+
+ pbBuf += cbOpt;
+ cbBuf -= cbOpt;
+ }
+
+ return fOptOverload;
+}
+
+
+/**
+ * Dumps the message.
+ */
+void DhcpClientMessage::dump() const RT_NOEXCEPT
+{
+ DhcpMessage::dump();
+
+ if (OptRapidCommit(*this).present())
+ LogRel((" (rapid commit)"));
+
+ try
+ {
+ const OptServerId sid(*this);
+ if (sid.present())
+ LogRel((" for server %RTnaipv4", sid.value().u));
+
+ const OptClientId cid(*this);
+ if (cid.present())
+ {
+ if (cid.value().size() > 0)
+ LogRel((" client id: %.*Rhxs\n", cid.value().size(), &cid.value().front()));
+ else
+ LogRel((" client id: <empty>\n"));
+ }
+
+ const OptRequestedAddress reqAddr(*this);
+ if (reqAddr.present())
+ LogRel((" requested address %RTnaipv4", reqAddr.value().u));
+ const OptLeaseTime reqLeaseTime(*this);
+ if (reqLeaseTime.present())
+ LogRel((" requested lease time %d", reqAddr.value()));
+ if (reqAddr.present() || reqLeaseTime.present())
+ LogRel(("\n"));
+
+ const OptParameterRequest params(*this);
+ if (params.present())
+ {
+ LogRel((" params {"));
+ typedef OptParameterRequest::value_t::const_iterator it_t;
+ for (it_t it = params.value().begin(); it != params.value().end(); ++it)
+ LogRel((" %d", *it));
+ LogRel((" }\n"));
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRel(("bad_alloc during dumping\n"));
+ }
+
+ for (rawopts_t::const_iterator it = m_rawopts.begin(); it != m_rawopts.end(); ++it)
+ {
+ const uint8_t optcode = (*it).first;
+ switch (optcode)
+ {
+ case OptMessageType::optcode: /* FALLTHROUGH */
+ case OptClientId::optcode: /* FALLTHROUGH */
+ case OptRequestedAddress::optcode: /* FALLTHROUGH */
+ case OptLeaseTime::optcode: /* FALLTHROUGH */
+ case OptParameterRequest::optcode: /* FALLTHROUGH */
+ case OptRapidCommit::optcode:
+ break;
+
+ default:
+ {
+ size_t const cbBytes = it->second.size();
+ uint8_t const *pbBytes = &it->second.front();
+ bool fAllPrintable = true;
+ for (size_t off = 0; off < cbBytes; off++)
+ if (!RT_C_IS_PRINT((char )pbBytes[off]))
+ {
+ fAllPrintable = false;
+ break;
+ }
+ if (fAllPrintable)
+ LogRel((" %2d: '%.*s'\n", optcode, cbBytes, pbBytes));
+ else
+ LogRel((" %2d: %.*Rhxs\n", optcode, cbBytes, pbBytes));
+ }
+ }
+ }
+}
+
+
+
+/*********************************************************************************************************************************
+* DhcpServerMessage Implementation *
+*********************************************************************************************************************************/
+
+DhcpServerMessage::DhcpServerMessage(const DhcpClientMessage &req, uint8_t messageTypeParam, RTNETADDRIPV4 serverAddr)
+ : DhcpMessage()
+ , m_optServerId(serverAddr)
+{
+ m_dst.u = 0xffffffff; /* broadcast */
+
+ m_optMessageType = OptMessageType(messageTypeParam);
+
+ /* copy values from the request (cf. RFC2131 Table 3) */
+ m_xid = req.xid();
+ m_flags = req.flags();
+ m_giaddr = req.giaddr();
+ m_mac = req.mac();
+
+ if (req.messageType() == RTNET_DHCP_MT_REQUEST)
+ m_ciaddr = req.ciaddr();
+}
+
+
+void DhcpServerMessage::maybeUnicast(const DhcpClientMessage &req) RT_NOEXCEPT
+{
+ if (!req.broadcast() && req.ciaddr().u != 0)
+ setDst(req.ciaddr());
+}
+
+
+/**
+ * @throws std::bad_alloc
+ */
+void DhcpServerMessage::addOption(DhcpOption *opt)
+{
+ m_optmap << opt;
+}
+
+
+/**
+ * @throws std::bad_alloc
+ */
+void DhcpServerMessage::addOptions(const optmap_t &optmap)
+{
+ for (optmap_t::const_iterator it = optmap.begin(); it != optmap.end(); ++it)
+ m_optmap << it->second;
+}
+
+
+/**
+ * @throws std::bad_alloc
+ */
+int DhcpServerMessage::encode(octets_t &data)
+{
+ /*
+ * Header, including DHCP cookie.
+ */
+ RTNETBOOTP bp;
+ RT_ZERO(bp);
+
+ bp.bp_op = RTNETBOOTP_OP_REPLY;
+ bp.bp_htype = RTNET_ARP_ETHER;
+ bp.bp_hlen = sizeof(RTMAC);
+
+ bp.bp_xid = m_xid;
+
+ bp.bp_ciaddr = m_ciaddr;
+ bp.bp_yiaddr = m_yiaddr;
+ bp.bp_siaddr = m_siaddr;
+ bp.bp_giaddr = m_giaddr;
+
+ bp.bp_chaddr.Mac = m_mac;
+
+ bp.bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32_C(RTNET_DHCP_COOKIE);
+
+ data.insert(data.end(), (uint8_t *)&bp, (uint8_t *)&bp.bp_vend.Dhcp.dhcp_opts);
+
+ /** @todo TFTP, bootfile name, etc. pick from extended options if no
+ * override in effect? */
+
+ /*
+ * Options
+ */
+ data << m_optServerId
+ << m_optMessageType;
+
+ for (optmap_t::const_iterator it = m_optmap.begin(); it != m_optmap.end(); ++it)
+ {
+ LogRel3(("encoding option %d (%s)\n", it->first, DhcpOption::name(it->first)));
+ DhcpOption &opt = *it->second;
+ data << opt;
+ }
+
+ data << OptEnd();
+
+ AssertCompile(RTNET_DHCP_NORMAL_SIZE == 548);
+ if (data.size() < RTNET_DHCP_NORMAL_SIZE)
+ data.resize(RTNET_DHCP_NORMAL_SIZE);
+
+ /** @todo dump it */
+ if ((LogRelIs4Enabled() && LogRelIsEnabled()) || LogIsEnabled())
+ dump();
+ if ((LogRelIs5Enabled() && LogRelIsEnabled()) || LogIs5Enabled())
+ LogRel5(("encoded message: %u bytes\n%.*Rhxd\n", data.size(), data.size(), &data.front()));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Dumps a server message to the log.
+ */
+void DhcpServerMessage::dump() const RT_NOEXCEPT
+{
+ DhcpMessage::dump();
+
+ /** @todo dump option details. */
+}
+
diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpMessage.h b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.h
new file mode 100644
index 00000000..f92e8074
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.h
@@ -0,0 +1,161 @@
+/* $Id: DhcpMessage.h $ */
+/** @file
+ * DHCP Message and its de/serialization.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_DhcpMessage_h
+#define VBOX_INCLUDED_SRC_Dhcpd_DhcpMessage_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DhcpdInternal.h"
+#include <iprt/net.h>
+#include <iprt/cpp/ministring.h>
+#include "ClientId.h"
+#include "DhcpOptions.h"
+
+
+/**
+ * Base class for internal DHCP client and server message representations.
+ */
+class DhcpMessage
+{
+protected:
+ uint32_t m_xid;
+ uint16_t m_flags;
+
+ RTMAC m_mac;
+
+ RTNETADDRIPV4 m_ciaddr;
+ RTNETADDRIPV4 m_yiaddr;
+ RTNETADDRIPV4 m_siaddr;
+ RTNETADDRIPV4 m_giaddr;
+
+#if 0 /* not currently unused, so avoid wasting time on them for now. */
+ RTCString m_sname; /**< @note Not necessarily UTF-8 clean. */
+ RTCString m_file; /**< @note Not necessarily UTF-8 clean. */
+#endif
+
+ OptMessageType m_optMessageType;
+
+protected:
+ DhcpMessage();
+
+public:
+ /** @name Accessors
+ * @{ */
+ uint32_t xid() const RT_NOEXCEPT { return m_xid; }
+
+ uint16_t flags() const RT_NOEXCEPT { return m_flags; }
+ bool broadcast() const RT_NOEXCEPT { return (m_flags & RTNET_DHCP_FLAG_BROADCAST) != 0; }
+
+ const RTMAC &mac() const RT_NOEXCEPT { return m_mac; }
+
+ RTNETADDRIPV4 ciaddr() const RT_NOEXCEPT { return m_ciaddr; }
+ RTNETADDRIPV4 yiaddr() const RT_NOEXCEPT { return m_yiaddr; }
+ RTNETADDRIPV4 siaddr() const RT_NOEXCEPT { return m_siaddr; }
+ RTNETADDRIPV4 giaddr() const RT_NOEXCEPT { return m_giaddr; }
+
+ void setCiaddr(RTNETADDRIPV4 addr) RT_NOEXCEPT { m_ciaddr = addr; }
+ void setYiaddr(RTNETADDRIPV4 addr) RT_NOEXCEPT { m_yiaddr = addr; }
+ void setSiaddr(RTNETADDRIPV4 addr) RT_NOEXCEPT { m_siaddr = addr; }
+ void setGiaddr(RTNETADDRIPV4 addr) RT_NOEXCEPT { m_giaddr = addr; }
+
+ uint8_t messageType() const RT_NOEXCEPT
+ {
+ Assert(m_optMessageType.present());
+ return m_optMessageType.value();
+ }
+ /** @} */
+
+ void dump() const RT_NOEXCEPT;
+};
+
+
+/**
+ * Decoded DHCP client message.
+ *
+ * This is the internal decoded representation of a DHCP message picked up from
+ * the wire.
+ */
+class DhcpClientMessage
+ : public DhcpMessage
+{
+protected:
+ rawopts_t m_rawopts;
+ ClientId m_id;
+ bool m_broadcasted;
+
+public:
+ static DhcpClientMessage *parse(bool broadcasted, const void *buf, size_t buflen);
+
+ /** @name Getters
+ * @{ */
+ bool broadcasted() const RT_NOEXCEPT { return m_broadcasted; }
+ const rawopts_t &rawopts() const RT_NOEXCEPT { return m_rawopts; }
+ const ClientId &clientId() const RT_NOEXCEPT { return m_id; }
+ /** @} */
+
+ void dump() const RT_NOEXCEPT;
+
+protected:
+ int i_parseOptions(const uint8_t *pbBuf, size_t cbBuf) RT_NOEXCEPT;
+};
+
+
+
+/**
+ * DHCP server message for encoding.
+ */
+class DhcpServerMessage
+ : public DhcpMessage
+{
+protected:
+ RTNETADDRIPV4 m_dst;
+ OptServerId m_optServerId;
+ optmap_t m_optmap;
+
+public:
+ DhcpServerMessage(const DhcpClientMessage &req, uint8_t messageType, RTNETADDRIPV4 serverAddr);
+
+ /** @name Accessors
+ * @{ */
+ RTNETADDRIPV4 dst() const RT_NOEXCEPT { return m_dst; }
+ void setDst(RTNETADDRIPV4 aDst) RT_NOEXCEPT { m_dst = aDst; }
+
+ void maybeUnicast(const DhcpClientMessage &req) RT_NOEXCEPT;
+
+ void addOption(DhcpOption *opt);
+ void addOption(const DhcpOption &opt) { addOption(opt.clone()); }
+
+ void addOptions(const optmap_t &optmap);
+ /** @} */
+
+ int encode(octets_t &data);
+ void dump() const RT_NOEXCEPT;
+};
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_DhcpMessage_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp
new file mode 100644
index 00000000..96e34562
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp
@@ -0,0 +1,515 @@
+/* $Id: DhcpOptions.cpp $ */
+/** @file
+ * DHCP server - DHCP options
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "DhcpdInternal.h"
+#include "DhcpOptions.h"
+#ifndef IN_VBOXSVC
+# include "DhcpMessage.h"
+#endif
+
+#include <iprt/cidr.h>
+
+
+#ifndef IN_VBOXSVC
+
+optmap_t &operator<<(optmap_t &optmap, DhcpOption *option)
+{
+ if (option == NULL)
+ return optmap;
+
+ if (option->present())
+ optmap[option->optcode()] = std::shared_ptr<DhcpOption>(option);
+ else
+ optmap.erase(option->optcode());
+
+ return optmap;
+}
+
+
+optmap_t &operator<<(optmap_t &optmap, const std::shared_ptr<DhcpOption> &option)
+{
+ if (!option)
+ return optmap;
+
+ if (option->present())
+ optmap[option->optcode()] = option;
+ else
+ optmap.erase(option->optcode());
+
+ return optmap;
+}
+
+#endif /* !IN_VBOXSVC */
+
+
+int DhcpOption::encode(octets_t &dst) const
+{
+ if (!m_fPresent)
+ return VERR_INVALID_STATE;
+
+ size_t cbOrig = dst.size();
+
+ append(dst, m_OptCode);
+ appendLength(dst, 0); /* placeholder */
+
+ ssize_t cbValue = encodeValue(dst);
+ if (cbValue < 0 || UINT8_MAX <= cbValue)
+ {
+ dst.resize(cbOrig); /* undo */
+ return VERR_INVALID_PARAMETER;
+ }
+
+ dst[cbOrig+1] = (uint8_t)cbValue;
+ return VINF_SUCCESS;
+}
+
+
+/* static */
+const octets_t *DhcpOption::findOption(const rawopts_t &aOptMap, uint8_t aOptCode)
+{
+ rawopts_t::const_iterator it(aOptMap.find(aOptCode));
+ if (it == aOptMap.end())
+ return NULL;
+
+ return &it->second;
+}
+
+
+int DhcpOption::decode(const rawopts_t &map)
+{
+ const octets_t *rawopt = DhcpOption::findOption(map, m_OptCode);
+ if (rawopt == NULL)
+ return VERR_NOT_FOUND;
+
+ int rc = decodeValue(*rawopt, rawopt->size());
+ if (RT_FAILURE(rc))
+ return VERR_INVALID_PARAMETER;
+
+ return VINF_SUCCESS;
+}
+
+
+#ifndef IN_VBOXSVC
+int DhcpOption::decode(const DhcpClientMessage &req)
+{
+ return decode(req.rawopts());
+}
+#endif
+
+
+int DhcpOption::parse1(bool &aValue, const char *pcszValue)
+{
+ pcszValue = RTStrStripL(pcszValue);
+ if ( strcmp(pcszValue, "true") == 0
+ || strcmp(pcszValue, "1") == 0
+ || strcmp(pcszValue, "yes") == 0
+ || strcmp(pcszValue, "on") == 0 )
+ {
+ aValue = true;
+ return VINF_SUCCESS;
+ }
+
+ if ( strcmp(pcszValue, "false") == 0
+ || strcmp(pcszValue, "0") == 0
+ || strcmp(pcszValue, "no") == 0
+ || strcmp(pcszValue, "off") == 0 )
+ {
+ aValue = false;
+ return VINF_SUCCESS;
+ }
+
+ uint8_t bTmp;
+ int rc = RTStrToUInt8Full(RTStrStripL(pcszValue), 10, &bTmp);
+ if (rc == VERR_TRAILING_SPACES)
+ rc = VINF_SUCCESS;
+ if (RT_SUCCESS(rc))
+ aValue = bTmp != 0;
+
+ return rc;
+}
+
+
+int DhcpOption::parse1(uint8_t &aValue, const char *pcszValue)
+{
+ int rc = RTStrToUInt8Full(RTStrStripL(pcszValue), 10, &aValue);
+ if (rc == VERR_TRAILING_SPACES)
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+int DhcpOption::parse1(uint16_t &aValue, const char *pcszValue)
+{
+ int rc = RTStrToUInt16Full(RTStrStripL(pcszValue), 10, &aValue);
+
+ if (rc == VERR_TRAILING_SPACES)
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+int DhcpOption::parse1(uint32_t &aValue, const char *pcszValue)
+{
+ int rc = RTStrToUInt32Full(RTStrStripL(pcszValue), 10, &aValue);
+
+ if (rc == VERR_TRAILING_SPACES)
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+int DhcpOption::parse1(RTNETADDRIPV4 &aValue, const char *pcszValue)
+{
+ return RTNetStrToIPv4Addr(pcszValue, &aValue);
+}
+
+
+int DhcpOption::parse1(DhcpIpv4AddrAndMask &aValue, const char *pcszValue)
+{
+ return RTCidrStrToIPv4(pcszValue, &aValue.Ipv4, &aValue.Mask);
+}
+
+
+template <typename a_Type>
+/*static*/ int DhcpOption::parseList(std::vector<a_Type> &aList, const char *pcszValue)
+{
+ std::vector<a_Type> vecTmp;
+
+ pcszValue = RTStrStripL(pcszValue);
+ for (;;)
+ {
+ /* Assume space, tab, comma or semicolon is used as separator (superset of RTStrStrip): */
+ const char *pszNext = strpbrk(pcszValue, " ,;:\t\n\r");
+ char szTmp[256];
+ if (pszNext)
+ {
+ size_t cchToCopy = (size_t)(pszNext - pcszValue);
+ if (cchToCopy >= sizeof(szTmp))
+ return VERR_INVALID_PARAMETER;
+ memcpy(szTmp, pcszValue, cchToCopy);
+ szTmp[cchToCopy] = '\0';
+ pcszValue = szTmp;
+
+ /* Advance pszNext past the separator character and fluff: */
+ char ch;
+ do
+ pszNext++;
+ while ((ch = *pszNext) == ' ' || ch == ':' || ch == ';' || ch == '\t' || ch == '\n' || ch == '\r');
+ if (ch == '\0')
+ pszNext = NULL;
+ }
+
+ /* Try convert it: */
+ a_Type Value;
+ int rc = DhcpOption::parse1(Value, pcszValue);
+ if (RT_SUCCESS(rc))
+ vecTmp.push_back(Value);
+ else
+ return VERR_INVALID_PARAMETER;
+
+ if (pszNext)
+ pcszValue = pszNext;
+ else
+ break;
+ }
+
+ aList.swap(vecTmp);
+ return VINF_SUCCESS;
+
+}
+
+/** ASSUME that uint8_t means hex byte strings. */
+template <>
+/*static*/ int DhcpOption::parseList(std::vector<uint8_t> &aList, const char *pcszValue)
+{
+ uint8_t abBuf[255];
+ size_t cbReturned = 0;
+ int rc = RTStrConvertHexBytesEx(RTStrStripL(pcszValue), abBuf, sizeof(abBuf), RTSTRCONVERTHEXBYTES_F_SEP_COLON,
+ NULL, &cbReturned);
+ if (RT_SUCCESS(rc))
+ {
+ if (rc != VWRN_TRAILING_CHARS)
+ {
+ for (size_t i = 0; i < cbReturned; i++)
+ aList.push_back(abBuf[i]);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_TRAILING_CHARS;
+ }
+ return rc;
+}
+
+
+
+/*
+ * XXX: See DHCPServer::encodeOption()
+ */
+int DhcpOption::parseHex(octets_t &aRawValue, const char *pcszValue)
+{
+ uint8_t abBuf[255];
+ size_t cbReturned = 0;
+ int rc = RTStrConvertHexBytesEx(RTStrStripL(pcszValue), abBuf, sizeof(abBuf), RTSTRCONVERTHEXBYTES_F_SEP_COLON,
+ NULL, &cbReturned);
+ if (RT_SUCCESS(rc))
+ {
+ if (rc != VWRN_TRAILING_CHARS)
+ {
+ for (size_t i = 0; i < cbReturned; i++)
+ aRawValue.push_back(abBuf[i]);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_TRAILING_CHARS;
+ }
+ return rc;
+}
+
+
+/*static*/ DhcpOption *DhcpOption::parse(uint8_t aOptCode, int aEnc, const char *pcszValue, int *prc /*= NULL*/)
+{
+ int rcIgn;
+ if (!prc)
+ prc = &rcIgn;
+
+ switch (aEnc)
+ {
+ case 0: /* DHCPOptionEncoding_Normal */
+ switch (aOptCode)
+ {
+#define HANDLE(a_OptClass) \
+ case a_OptClass::optcode: \
+ return a_OptClass::parse(pcszValue, prc)
+
+ HANDLE(OptSubnetMask); // 1
+ HANDLE(OptTimeOffset); // 2
+ HANDLE(OptRouters); // 3
+ HANDLE(OptTimeServers); // 4
+ HANDLE(OptNameServers); // 5
+ HANDLE(OptDNSes); // 6
+ HANDLE(OptLogServers); // 7
+ HANDLE(OptCookieServers); // 8
+ HANDLE(OptLPRServers); // 9
+ HANDLE(OptImpressServers); // 10
+ HANDLE(OptResourceLocationServers); // 11
+ HANDLE(OptHostName); // 12
+ HANDLE(OptBootFileSize); // 13
+ HANDLE(OptMeritDumpFile); // 14
+ HANDLE(OptDomainName); // 15
+ HANDLE(OptSwapServer); // 16
+ HANDLE(OptRootPath); // 17
+ HANDLE(OptExtensionPath); // 18
+ HANDLE(OptIPForwarding); // 19
+ HANDLE(OptNonLocalSourceRouting); // 20
+ HANDLE(OptPolicyFilter); // 21
+ HANDLE(OptMaxDgramReassemblySize); // 22
+ HANDLE(OptDefaultIPTTL); // 23
+ HANDLE(OptPathMTUAgingTimeout); // 24
+ HANDLE(OptPathMTUPlateauTable); // 25
+ HANDLE(OptInterfaceMTU); // 26
+ HANDLE(OptAllSubnetsAreLocal); // 27
+ HANDLE(OptBroadcastAddress); // 28
+ HANDLE(OptPerformMaskDiscovery); // 29
+ HANDLE(OptMaskSupplier); // 30
+ HANDLE(OptPerformRouterDiscovery); // 31
+ HANDLE(OptRouterSolicitationAddress); // 32
+ HANDLE(OptStaticRoute); // 33
+ HANDLE(OptTrailerEncapsulation); // 34
+ HANDLE(OptARPCacheTimeout); // 35
+ HANDLE(OptEthernetEncapsulation); // 36
+ HANDLE(OptTCPDefaultTTL); // 37
+ HANDLE(OptTCPKeepaliveInterval); // 38
+ HANDLE(OptTCPKeepaliveGarbage); // 39
+ HANDLE(OptNISDomain); // 40
+ HANDLE(OptNISServers); // 41
+ HANDLE(OptNTPServers); // 42
+ //HANDLE(OptVendorSpecificInfo); // 43 - Only DHCPOptionEncoding_hex
+ HANDLE(OptNetBIOSNameServers); // 44
+ HANDLE(OptNetBIOSDatagramServers); // 45
+ HANDLE(OptNetBIOSNodeType); // 46
+ //HANDLE(OptNetBIOSScope); // 47 - Only DHCPOptionEncoding_hex
+ HANDLE(OptXWindowsFontServers); // 48
+ HANDLE(OptXWindowsDisplayManager); // 49
+#ifndef IN_VBOXSVC /* Don't allow these in new configs */
+ // OptRequestedAddress (50) is client only and not configurable.
+ HANDLE(OptLeaseTime); // 51 - for historical reasons? Configuable elsewhere now.
+ // OptOptionOverload (52) is part of the protocol and not configurable.
+ // OptMessageType (53) is part of the protocol and not configurable.
+ // OptServerId (54) is the IP address of the server and configurable elsewhere.
+ // OptParameterRequest (55) is client only and not configurable.
+ // OptMessage (56) is server failure message and not configurable.
+ // OptMaxDHCPMessageSize (57) is client only (?) and not configurable.
+ HANDLE(OptRenewalTime); // 58 - for historical reasons?
+ HANDLE(OptRebindingTime); // 59 - for historical reasons?
+ // OptVendorClassId (60) is client only and not configurable.
+ // OptClientId (61) is client only and not configurable.
+#endif
+ HANDLE(OptNetWareIPDomainName); // 62
+ //HANDLE(OptNetWareIPInformation); // 63 - Only DHCPOptionEncoding_hex
+ HANDLE(OptNISPlusDomain); // 64
+ HANDLE(OptNISPlusServers); // 65
+ HANDLE(OptTFTPServerName); // 66 - perhaps we should use an alternative way to configure these.
+ HANDLE(OptBootfileName); // 67 - perhaps we should use an alternative way to configure these.
+ HANDLE(OptMobileIPHomeAgents); // 68
+ HANDLE(OptSMTPServers); // 69
+ HANDLE(OptPOP3Servers); // 70
+ HANDLE(OptNNTPServers); // 71
+ HANDLE(OptWWWServers); // 72
+ HANDLE(OptFingerServers); // 73
+ HANDLE(OptIRCServers); // 74
+ HANDLE(OptStreetTalkServers); // 75
+ HANDLE(OptSTDAServers); // 76
+ // OptUserClassId (77) is client only and not configurable.
+ //HANDLE(OptSLPDirectoryAgent); // 78 - Only DHCPOptionEncoding_hex
+ //HANDLE(OptSLPServiceScope); // 79 - Only DHCPOptionEncoding_hex
+ // OptRapidCommit (80) is not configurable.
+
+ //HANDLE(OptDomainSearch); // 119 - Only DHCPOptionEncoding_hex
+
+#undef HANDLE
+ default:
+ if (prc)
+ *prc = VERR_NOT_IMPLEMENTED;
+ return NULL;
+ }
+ break;
+
+ case 1:
+ return RawOption::parse(aOptCode, pcszValue, prc);
+
+ default:
+ if (prc)
+ *prc = VERR_WRONG_TYPE;
+ return NULL;
+ }
+}
+
+
+/**
+ * Gets the option name (simply "unknown" if not known) for logging purposes.
+ */
+/*static*/ const char *DhcpOption::name(uint8_t aOptCode)
+{
+ switch (aOptCode)
+ {
+#define HANDLE(a_OptClass) \
+ case a_OptClass::optcode: \
+ return &#a_OptClass[3]
+
+ HANDLE(OptSubnetMask); // 1
+ HANDLE(OptTimeOffset); // 2
+ HANDLE(OptRouters); // 3
+ HANDLE(OptTimeServers); // 4
+ HANDLE(OptNameServers); // 5
+ HANDLE(OptDNSes); // 6
+ HANDLE(OptLogServers); // 7
+ HANDLE(OptCookieServers); // 8
+ HANDLE(OptLPRServers); // 9
+ HANDLE(OptImpressServers); // 10
+ HANDLE(OptResourceLocationServers); // 11
+ HANDLE(OptHostName); // 12
+ HANDLE(OptBootFileSize); // 13
+ HANDLE(OptMeritDumpFile); // 14
+ HANDLE(OptDomainName); // 15
+ HANDLE(OptSwapServer); // 16
+ HANDLE(OptRootPath); // 17
+ HANDLE(OptExtensionPath); // 18
+ HANDLE(OptIPForwarding); // 19
+ HANDLE(OptNonLocalSourceRouting); // 20
+ HANDLE(OptPolicyFilter); // 21
+ HANDLE(OptMaxDgramReassemblySize); // 22
+ HANDLE(OptDefaultIPTTL); // 23
+ HANDLE(OptPathMTUAgingTimeout); // 24
+ HANDLE(OptPathMTUPlateauTable); // 25
+ HANDLE(OptInterfaceMTU); // 26
+ HANDLE(OptAllSubnetsAreLocal); // 27
+ HANDLE(OptBroadcastAddress); // 28
+ HANDLE(OptPerformMaskDiscovery); // 29
+ HANDLE(OptMaskSupplier); // 30
+ HANDLE(OptPerformRouterDiscovery); // 31
+ HANDLE(OptRouterSolicitationAddress); // 32
+ HANDLE(OptStaticRoute); // 33
+ HANDLE(OptTrailerEncapsulation); // 34
+ HANDLE(OptARPCacheTimeout); // 35
+ HANDLE(OptEthernetEncapsulation); // 36
+ HANDLE(OptTCPDefaultTTL); // 37
+ HANDLE(OptTCPKeepaliveInterval); // 38
+ HANDLE(OptTCPKeepaliveGarbage); // 39
+ HANDLE(OptNISDomain); // 40
+ HANDLE(OptNISServers); // 41
+ HANDLE(OptNTPServers); // 42
+ HANDLE(OptVendorSpecificInfo); // 43
+ HANDLE(OptNetBIOSNameServers); // 44
+ HANDLE(OptNetBIOSDatagramServers); // 45
+ HANDLE(OptNetBIOSNodeType); // 46
+ HANDLE(OptNetBIOSScope); // 47
+ HANDLE(OptXWindowsFontServers); // 48
+ HANDLE(OptXWindowsDisplayManager); // 49
+ HANDLE(OptRequestedAddress); // 50
+ HANDLE(OptLeaseTime); // 51
+ //HANDLE(OptOptionOverload); // 52
+ HANDLE(OptMessageType); // 53
+ HANDLE(OptServerId); // 54
+ HANDLE(OptParameterRequest); // 55
+ HANDLE(OptMessage); // 56
+ HANDLE(OptMaxDHCPMessageSize); // 57
+ HANDLE(OptRenewalTime); // 58
+ HANDLE(OptRebindingTime); // 59
+ HANDLE(OptVendorClassId); // 60
+ HANDLE(OptClientId); // 61
+ HANDLE(OptNetWareIPDomainName); // 62
+ HANDLE(OptNetWareIPInformation); // 63
+ HANDLE(OptNISPlusDomain); // 64
+ HANDLE(OptNISPlusServers); // 65
+ HANDLE(OptTFTPServerName); // 66
+ HANDLE(OptBootfileName); // 67
+ HANDLE(OptMobileIPHomeAgents); // 68
+ HANDLE(OptSMTPServers); // 69
+ HANDLE(OptPOP3Servers); // 70
+ HANDLE(OptNNTPServers); // 71
+ HANDLE(OptWWWServers); // 72
+ HANDLE(OptFingerServers); // 73
+ HANDLE(OptIRCServers); // 74
+ HANDLE(OptStreetTalkServers); // 75
+ HANDLE(OptSTDAServers); // 76
+ HANDLE(OptUserClassId); // 77
+ HANDLE(OptSLPDirectoryAgent); // 78 - Only DHCPOptionEncoding_hex
+ HANDLE(OptSLPServiceScope); // 79 - Only DHCPOptionEncoding_hex
+ HANDLE(OptRapidCommit); // 80
+
+ HANDLE(OptDomainSearch); // 119 - Only DHCPOptionEncoding_hex
+
+#undef HANDLE
+ default:
+ return "unknown";
+ }
+}
+
diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpOptions.h b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.h
new file mode 100644
index 00000000..45408842
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.h
@@ -0,0 +1,832 @@
+/* $Id: DhcpOptions.h $ */
+/** @file
+ * DHCP server - DHCP options
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_DhcpOptions_h
+#define VBOX_INCLUDED_SRC_Dhcpd_DhcpOptions_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DhcpdInternal.h"
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/net.h>
+#include <iprt/string.h>
+#include <iprt/cpp/ministring.h>
+
+
+class DhcpClientMessage;
+
+typedef struct DhcpIpv4AddrAndMask
+{
+ RTNETADDRIPV4 Ipv4;
+ RTNETADDRIPV4 Mask;
+} DhcpIpv4AddrAndMask;
+
+
+class DhcpOption
+{
+protected:
+ uint8_t m_OptCode;
+ bool m_fPresent;
+
+public:
+ explicit DhcpOption(uint8_t aOptCode)
+ : m_OptCode(aOptCode), m_fPresent(true)
+ {}
+
+ DhcpOption(uint8_t aOptCode, bool fPresent)
+ : m_OptCode(aOptCode), m_fPresent(fPresent)
+ {}
+
+ virtual DhcpOption *clone() const = 0;
+
+ virtual ~DhcpOption()
+ {}
+
+public:
+ static DhcpOption *parse(uint8_t aOptCode, int aEnc, const char *pcszValue, int *prc = NULL);
+ static const char *name(uint8_t bOptcode);
+
+public:
+ uint8_t optcode() const RT_NOEXCEPT { return m_OptCode; }
+ bool present() const RT_NOEXCEPT { return m_fPresent; }
+
+public:
+ int encode(octets_t &dst) const;
+
+ int decode(const rawopts_t &map);
+ int decode(const DhcpClientMessage &req);
+
+protected:
+ virtual ssize_t encodeValue(octets_t &dst) const = 0;
+ virtual int decodeValue(const octets_t &src, size_t cb) = 0;
+
+protected:
+ static const octets_t *findOption(const rawopts_t &aOptMap, uint8_t aOptCode);
+
+protected:
+ /** @name Serialization
+ * @{ */
+ static void append(octets_t &aDst, bool aValue)
+ {
+ uint8_t b = aValue ? 1 : 0;
+ aDst.push_back(b);
+ }
+
+ static void append(octets_t &aDst, uint8_t aValue)
+ {
+ aDst.push_back(aValue);
+ }
+
+ static void append(octets_t &aDst, uint16_t aValue)
+ {
+ RTUINT16U u16 = { RT_H2N_U16(aValue) };
+ aDst.insert(aDst.end(), u16.au8, u16.au8 + sizeof(aValue));
+ }
+
+ static void append(octets_t &aDst, uint32_t aValue)
+ {
+ RTUINT32U u32 = { RT_H2N_U32(aValue) };
+ aDst.insert(aDst.end(), u32.au8, u32.au8 + sizeof(aValue));
+ }
+
+ static void append(octets_t &aDst, RTNETADDRIPV4 aIPv4)
+ {
+ aDst.insert(aDst.end(), aIPv4.au8, aIPv4.au8 + sizeof(aIPv4));
+ }
+
+ static void append(octets_t &aDst, DhcpIpv4AddrAndMask aIPv4)
+ {
+ aDst.insert(aDst.end(), (uint8_t *)&aIPv4, (uint8_t *)&aIPv4 + sizeof(aIPv4));
+ }
+
+ static void append(octets_t &aDst, const char *pszString, size_t cb)
+ {
+ aDst.insert(aDst.end(), pszString, pszString + cb);
+ }
+
+ static void append(octets_t &aDst, const RTCString &str)
+ {
+ append(aDst, str.c_str(), str.length());
+ }
+
+ /* non-overloaded name to avoid ambiguity */
+ static void appendLength(octets_t &aDst, size_t cb)
+ {
+ append(aDst, static_cast<uint8_t>(cb));
+ }
+
+ /** @} */
+
+
+ /** @name Deserialization
+ * @{ */
+ static void extract(bool &aValue, octets_t::const_iterator &pos)
+ {
+ aValue = *pos != 0;
+ pos += sizeof(uint8_t);
+ }
+
+ static void extract(uint8_t &aValue, octets_t::const_iterator &pos)
+ {
+ aValue = *pos;
+ pos += sizeof(uint8_t);
+ }
+
+ static void extract(uint16_t &aValue, octets_t::const_iterator &pos)
+ {
+ RTUINT16U u16;
+ memcpy(u16.au8, &pos[0], sizeof(uint16_t));
+ aValue = RT_N2H_U16(u16.u);
+ pos += sizeof(uint16_t);
+ }
+
+ static void extract(uint32_t &aValue, octets_t::const_iterator &pos)
+ {
+ RTUINT32U u32;
+ memcpy(u32.au8, &pos[0], sizeof(uint32_t));
+ aValue = RT_N2H_U32(u32.u);
+ pos += sizeof(uint32_t);
+ }
+
+ static void extract(RTNETADDRIPV4 &aValue, octets_t::const_iterator &pos)
+ {
+ memcpy(aValue.au8, &pos[0], sizeof(RTNETADDRIPV4));
+ pos += sizeof(RTNETADDRIPV4);
+ }
+
+ static void extract(DhcpIpv4AddrAndMask &aValue, octets_t::const_iterator &pos)
+ {
+ memcpy(&aValue, &pos[0], sizeof(aValue));
+ pos += sizeof(aValue);
+ }
+
+#if 0 /** @todo fix me */
+ static void extract(RTCString &aString, octets_t::const_iterator &pos, size_t cb)
+ {
+ aString.replace(aString.begin(), aString.end(), &pos[0], &pos[cb]);
+ pos += cb;
+ }
+#endif
+
+ /** @} */
+
+ /** @name Parse textual representation (e.g. in config file)
+ * @{ */
+ static int parse1(bool &aValue, const char *pcszValue);
+ static int parse1(uint8_t &aValue, const char *pcszValue);
+ static int parse1(uint16_t &aValue, const char *pcszValue);
+ static int parse1(uint32_t &aValue, const char *pcszValue);
+ static int parse1(RTNETADDRIPV4 &aValue, const char *pcszValue);
+ static int parse1(DhcpIpv4AddrAndMask &aValue, const char *pcszValue);
+
+ template <typename a_Type> static int parseList(std::vector<a_Type> &aList, const char *pcszValue);
+
+ static int parseHex(octets_t &aRawValue, const char *pcszValue);
+
+ /** @} */
+};
+
+
+inline octets_t &operator<<(octets_t &dst, const DhcpOption &option)
+{
+ option.encode(dst);
+ return dst;
+}
+
+
+#ifndef IN_VBOXSVC
+optmap_t &operator<<(optmap_t &optmap, DhcpOption *option);
+optmap_t &operator<<(optmap_t &optmap, const std::shared_ptr<DhcpOption> &option);
+#endif
+
+
+
+/**
+ * Only for << OptEnd() syntactic sugar...
+ */
+struct OptEnd {};
+inline octets_t &operator<<(octets_t &dst, const OptEnd &end)
+{
+ RT_NOREF(end);
+
+ dst.push_back(RTNET_DHCP_OPT_END);
+ return dst;
+}
+
+
+
+/**
+ * Option that has no value
+ */
+class OptNoValueBase
+ : public DhcpOption
+{
+public:
+ explicit OptNoValueBase(uint8_t aOptCode)
+ : DhcpOption(aOptCode, false)
+ {}
+
+ OptNoValueBase(uint8_t aOptCode, bool fPresent)
+ : DhcpOption(aOptCode, fPresent)
+ {}
+
+ OptNoValueBase(uint8_t aOptCode, const DhcpClientMessage &req)
+ : DhcpOption(aOptCode, false)
+ {
+ decode(req);
+ }
+
+ virtual OptNoValueBase *clone() const
+ {
+ return new OptNoValueBase(*this);
+ }
+
+protected:
+ virtual ssize_t encodeValue(octets_t &dst) const
+ {
+ RT_NOREF(dst);
+ return 0;
+ }
+
+public:
+ static bool isLengthValid(size_t cb)
+ {
+ return cb == 0;
+ }
+
+ virtual int decodeValue(const octets_t &src, size_t cb)
+ {
+ RT_NOREF(src);
+
+ if (!isLengthValid(cb))
+ return VERR_INVALID_PARAMETER;
+
+ m_fPresent = true;
+ return VINF_SUCCESS;
+ }
+};
+
+template <uint8_t _OptCode>
+class OptNoValue
+ : public OptNoValueBase
+{
+public:
+ static const uint8_t optcode = _OptCode;
+
+ OptNoValue()
+ : OptNoValueBase(optcode)
+ {}
+
+ explicit OptNoValue(bool fPresent) /* there's no overloaded ctor with value */
+ : OptNoValueBase(optcode, fPresent)
+ {}
+
+ explicit OptNoValue(const DhcpClientMessage &req)
+ : OptNoValueBase(optcode, req)
+ {}
+};
+
+
+
+/*
+ * Option that contains single value of fixed-size type T
+ */
+template <typename T>
+class OptValueBase
+ : public DhcpOption
+{
+public:
+ typedef T value_t;
+
+protected:
+ T m_Value;
+
+ explicit OptValueBase(uint8_t aOptCode)
+ : DhcpOption(aOptCode, false), m_Value()
+ {}
+
+ OptValueBase(uint8_t aOptCode, const T &aOptValue)
+ : DhcpOption(aOptCode), m_Value(aOptValue)
+ {}
+
+ OptValueBase(uint8_t aOptCode, const DhcpClientMessage &req)
+ : DhcpOption(aOptCode, false), m_Value()
+ {
+ decode(req);
+ }
+
+public:
+ virtual OptValueBase *clone() const
+ {
+ return new OptValueBase(*this);
+ }
+
+public:
+ T &value() { return m_Value; }
+ const T &value() const { return m_Value; }
+
+protected:
+ virtual ssize_t encodeValue(octets_t &dst) const
+ {
+ append(dst, m_Value);
+ return sizeof(T);
+ }
+
+public:
+ static bool isLengthValid(size_t cb)
+ {
+ return cb == sizeof(T);
+ }
+
+ virtual int decodeValue(const octets_t &src, size_t cb)
+ {
+ if (!isLengthValid(cb))
+ return VERR_INVALID_PARAMETER;
+
+ octets_t::const_iterator pos(src.begin());
+ extract(m_Value, pos);
+
+ m_fPresent = true;
+ return VINF_SUCCESS;
+ }
+};
+
+template<uint8_t _OptCode, typename T>
+class OptValue
+ : public OptValueBase<T>
+{
+public:
+ using typename OptValueBase<T>::value_t;
+
+public:
+ static const uint8_t optcode = _OptCode;
+
+ OptValue()
+ : OptValueBase<T>(optcode)
+ {}
+
+ explicit OptValue(const T &aOptValue)
+ : OptValueBase<T>(optcode, aOptValue)
+ {}
+
+ explicit OptValue(const DhcpClientMessage &req)
+ : OptValueBase<T>(optcode, req)
+ {}
+
+ static OptValue *parse(const char *pcszValue, int *prc)
+ {
+ typename OptValueBase<T>::value_t v;
+ int rc = DhcpOption::parse1(v, pcszValue);
+ *prc = rc;
+ if (RT_SUCCESS(rc))
+ return new OptValue(v);
+ return NULL;
+ }
+};
+
+
+
+/**
+ * Option that contains a string.
+ */
+class OptStringBase
+ : public DhcpOption
+{
+public:
+ typedef RTCString value_t;
+
+protected:
+ RTCString m_String;
+
+ explicit OptStringBase(uint8_t aOptCode)
+ : DhcpOption(aOptCode, false), m_String()
+ {}
+
+ OptStringBase(uint8_t aOptCode, const RTCString &aOptString)
+ : DhcpOption(aOptCode), m_String(aOptString)
+ {}
+
+ OptStringBase(uint8_t aOptCode, const DhcpClientMessage &req)
+ : DhcpOption(aOptCode, false), m_String()
+ {
+ decode(req);
+ }
+
+public:
+ virtual OptStringBase *clone() const
+ {
+ return new OptStringBase(*this);
+ }
+
+public:
+ RTCString &value() { return m_String; }
+ const RTCString &value() const { return m_String; }
+
+protected:
+ virtual ssize_t encodeValue(octets_t &dst) const
+ {
+ if (!isLengthValid(m_String.length()))
+ return -1;
+
+ append(dst, m_String);
+ return (ssize_t)m_String.length();
+ }
+
+public:
+ static bool isLengthValid(size_t cb)
+ {
+ return cb <= UINT8_MAX;
+ }
+
+ virtual int decodeValue(const octets_t &src, size_t cb)
+ {
+ if (!isLengthValid(cb))
+ return VERR_INVALID_PARAMETER;
+
+ int rc = m_String.assignNoThrow((char *)&src.front(), cb); /** @todo encoding. */
+ m_fPresent = true;
+ return rc;
+ }
+};
+
+template<uint8_t _OptCode>
+class OptString
+ : public OptStringBase
+{
+public:
+ static const uint8_t optcode = _OptCode;
+
+ OptString()
+ : OptStringBase(optcode)
+ {}
+
+ explicit OptString(const RTCString &aOptString)
+ : OptStringBase(optcode, aOptString)
+ {}
+
+ explicit OptString(const DhcpClientMessage &req)
+ : OptStringBase(optcode, req)
+ {}
+
+ static OptString *parse(const char *pcszValue, int *prc)
+ {
+ *prc = VINF_SUCCESS;
+ return new OptString(pcszValue);
+ }
+};
+
+
+
+/*
+ * Option that contains a list of values of type T
+ */
+template <typename T>
+class OptListBase
+ : public DhcpOption
+{
+public:
+ typedef std::vector<T> value_t;
+
+protected:
+ std::vector<T> m_List;
+
+ explicit OptListBase(uint8_t aOptCode)
+ : DhcpOption(aOptCode, false), m_List()
+ {}
+
+ OptListBase(uint8_t aOptCode, const T &aOptSingle)
+ : DhcpOption(aOptCode), m_List(1, aOptSingle)
+ {}
+
+ OptListBase(uint8_t aOptCode, const std::vector<T> &aOptList)
+ : DhcpOption(aOptCode), m_List(aOptList)
+ {}
+
+ OptListBase(uint8_t aOptCode, const DhcpClientMessage &req)
+ : DhcpOption(aOptCode, false), m_List()
+ {
+ decode(req);
+ }
+
+public:
+ virtual OptListBase *clone() const
+ {
+ return new OptListBase(*this);
+ }
+
+public:
+ std::vector<T> &value() { return m_List; }
+ const std::vector<T> &value() const { return m_List; }
+
+protected:
+ virtual ssize_t encodeValue(octets_t &dst) const
+ {
+ const size_t cbItem = sizeof(T);
+ size_t cbValue = 0;
+
+ for (size_t i = 0; i < m_List.size(); ++i)
+ {
+ if (cbValue + cbItem > UINT8_MAX)
+ break;
+
+ append(dst, m_List[i]);
+ cbValue += cbItem;
+ }
+
+ return (ssize_t)cbValue;
+ }
+
+public:
+ static bool isLengthValid(size_t cb)
+ {
+ return cb % sizeof(T) == 0;
+ }
+
+ virtual int decodeValue(const octets_t &src, size_t cb)
+ {
+ if (!isLengthValid(cb))
+ return VERR_INVALID_PARAMETER;
+
+ m_List.erase(m_List.begin(), m_List.end());
+
+ octets_t::const_iterator pos(src.begin());
+ for (size_t i = 0; i < cb / sizeof(T); ++i)
+ {
+ T item;
+ extract(item, pos);
+ m_List.push_back(item);
+ }
+ m_fPresent = true;
+ return VINF_SUCCESS;
+ }
+};
+
+template<uint8_t _OptCode, typename T>
+class OptList
+ : public OptListBase<T>
+
+{
+public:
+ using typename OptListBase<T>::value_t;
+
+public:
+ static const uint8_t optcode = _OptCode;
+
+ OptList()
+ : OptListBase<T>(optcode)
+ {}
+
+ explicit OptList(const T &aOptSingle)
+ : OptListBase<T>(optcode, aOptSingle)
+ {}
+
+ explicit OptList(const std::vector<T> &aOptList)
+ : OptListBase<T>(optcode, aOptList)
+ {}
+
+ explicit OptList(const DhcpClientMessage &req)
+ : OptListBase<T>(optcode, req)
+ {}
+
+ static OptList *parse(const char *pcszValue, int *prc)
+ {
+ typename OptListBase<T>::value_t v;
+ int rc = DhcpOption::parseList<T>(v, pcszValue);
+ if (RT_SUCCESS(rc))
+ {
+ if (!v.empty())
+ {
+ *prc = rc;
+ return new OptList(v);
+ }
+ rc = VERR_NO_DATA;
+ }
+ *prc = rc;
+ return NULL;
+ }
+};
+
+
+template<uint8_t _OptCode, typename T>
+class OptPairList
+ : public OptListBase<T>
+
+{
+public:
+ using typename OptListBase<T>::value_t;
+
+public:
+ static const uint8_t optcode = _OptCode;
+
+ OptPairList()
+ : OptListBase<T>(optcode)
+ {}
+
+ explicit OptPairList(const T &aOptSingle)
+ : OptListBase<T>(optcode, aOptSingle)
+ {}
+
+ explicit OptPairList(const std::vector<T> &aOptList)
+ : OptListBase<T>(optcode, aOptList)
+ {}
+
+ explicit OptPairList(const DhcpClientMessage &req)
+ : OptListBase<T>(optcode, req)
+ {}
+
+ static OptPairList *parse(const char *pcszValue, int *prc)
+ {
+ typename OptListBase<T>::value_t v;
+ int rc = DhcpOption::parseList<T>(v, pcszValue);
+ if (RT_SUCCESS(rc))
+ {
+ if (!v.empty())
+ {
+ if ((v.size() & 1) == 0)
+ {
+ *prc = rc;
+ return new OptPairList(v);
+ }
+ rc = VERR_UNEVEN_INPUT;
+ }
+ else
+ rc = VERR_NO_DATA;
+ }
+ *prc = rc;
+ return NULL;
+ }
+};
+
+
+/*
+ * Options specified by raw binary data that we don't know how to
+ * interpret.
+ */
+class RawOption
+ : public DhcpOption
+{
+protected:
+ octets_t m_Data;
+
+public:
+ explicit RawOption(uint8_t aOptCode)
+ : DhcpOption(aOptCode, false), m_Data()
+ {}
+
+ RawOption(uint8_t aOptCode, const octets_t &aSrc)
+ : DhcpOption(aOptCode), m_Data(aSrc)
+ {}
+
+public:
+ virtual RawOption *clone() const
+ {
+ return new RawOption(*this);
+ }
+
+
+protected:
+ virtual ssize_t encodeValue(octets_t &dst) const
+ {
+ dst.insert(dst.end(), m_Data.begin(), m_Data.end());
+ return (ssize_t)m_Data.size();
+ }
+
+ virtual int decodeValue(const octets_t &src, size_t cb)
+ {
+ octets_t::const_iterator beg(src.begin());
+ octets_t data(beg, beg + (ssize_t)cb);
+ m_Data.swap(data);
+
+ m_fPresent = true;
+ return VINF_SUCCESS;
+ }
+
+public:
+ static RawOption *parse(uint8_t aOptCode, const char *pcszValue, int *prc)
+ {
+ octets_t data;
+ int rc = DhcpOption::parseHex(data, pcszValue);
+ *prc = rc;
+ if (RT_SUCCESS(rc))
+ return new RawOption(aOptCode, data);
+ return NULL;
+ }
+};
+
+
+
+/** @name The DHCP options types.
+ * @{
+ */
+typedef OptValue<1, RTNETADDRIPV4> OptSubnetMask;
+typedef OptValue<2, uint32_t> OptTimeOffset;
+typedef OptList<3, RTNETADDRIPV4> OptRouters;
+typedef OptList<4, RTNETADDRIPV4> OptTimeServers;
+typedef OptList<5, RTNETADDRIPV4> OptNameServers;
+typedef OptList<6, RTNETADDRIPV4> OptDNSes;
+typedef OptList<7, RTNETADDRIPV4> OptLogServers;
+typedef OptList<8, RTNETADDRIPV4> OptCookieServers;
+typedef OptList<9, RTNETADDRIPV4> OptLPRServers;
+typedef OptList<10, RTNETADDRIPV4> OptImpressServers;
+typedef OptList<11, RTNETADDRIPV4> OptResourceLocationServers;
+typedef OptString<12> OptHostName;
+typedef OptValue<13, uint16_t> OptBootFileSize;
+typedef OptString<14> OptMeritDumpFile;
+typedef OptString<15> OptDomainName;
+typedef OptValue<16, RTNETADDRIPV4> OptSwapServer;
+typedef OptString<17> OptRootPath;
+typedef OptString<18> OptExtensionPath;
+typedef OptValue<19, bool> OptIPForwarding;
+typedef OptValue<20, bool> OptNonLocalSourceRouting;
+typedef OptList<21, DhcpIpv4AddrAndMask> OptPolicyFilter;
+typedef OptValue<22, uint16_t> OptMaxDgramReassemblySize;
+typedef OptValue<23, uint16_t> OptDefaultIPTTL;
+typedef OptValue<24, uint32_t> OptPathMTUAgingTimeout;
+typedef OptList<25, uint16_t> OptPathMTUPlateauTable;
+typedef OptValue<26, uint16_t> OptInterfaceMTU;
+typedef OptValue<27, bool> OptAllSubnetsAreLocal;
+typedef OptValue<28, RTNETADDRIPV4> OptBroadcastAddress;
+typedef OptValue<29, bool> OptPerformMaskDiscovery;
+typedef OptValue<30, bool> OptMaskSupplier;
+typedef OptValue<31, bool> OptPerformRouterDiscovery;
+typedef OptValue<32, RTNETADDRIPV4> OptRouterSolicitationAddress;
+typedef OptPairList<33, RTNETADDRIPV4> OptStaticRoute;
+typedef OptValue<34, bool> OptTrailerEncapsulation;
+typedef OptValue<35, uint32_t> OptARPCacheTimeout;
+typedef OptValue<36, bool> OptEthernetEncapsulation;
+typedef OptValue<37, uint8_t> OptTCPDefaultTTL;
+typedef OptValue<38, uint32_t> OptTCPKeepaliveInterval;
+typedef OptValue<39, bool> OptTCPKeepaliveGarbage;
+typedef OptString<40> OptNISDomain;
+typedef OptList<41, RTNETADDRIPV4> OptNISServers;
+typedef OptList<42, RTNETADDRIPV4> OptNTPServers;
+/* DHCP related options: */
+typedef OptList<43, uint8_t> OptVendorSpecificInfo;
+typedef OptList<44, RTNETADDRIPV4> OptNetBIOSNameServers;
+typedef OptList<45, RTNETADDRIPV4> OptNetBIOSDatagramServers;
+typedef OptValue<46, uint8_t> OptNetBIOSNodeType;
+typedef OptList<47, uint8_t> OptNetBIOSScope; /**< uint8_t or string? */
+typedef OptList<48, RTNETADDRIPV4> OptXWindowsFontServers;
+typedef OptList<49, RTNETADDRIPV4> OptXWindowsDisplayManager;
+typedef OptValue<50, RTNETADDRIPV4> OptRequestedAddress;
+typedef OptValue<51, uint32_t> OptLeaseTime;
+/* 52 - option overload is syntactic and handled internally */
+typedef OptValue<53, uint8_t> OptMessageType;
+typedef OptValue<54, RTNETADDRIPV4> OptServerId;
+typedef OptList<55, uint8_t> OptParameterRequest;
+typedef OptString<56> OptMessage;
+typedef OptValue<57, uint16_t> OptMaxDHCPMessageSize;
+typedef OptValue<58, uint32_t> OptRenewalTime;
+typedef OptValue<59, uint32_t> OptRebindingTime;
+typedef OptList<60, uint8_t> OptVendorClassId;
+typedef OptList<61, uint8_t> OptClientId;
+typedef OptString<62> OptNetWareIPDomainName; /**< RFC2242 */
+typedef OptList<63, uint8_t> OptNetWareIPInformation; /**< complicated, so just byte list for now. RFC2242 */
+typedef OptString<64> OptNISPlusDomain;
+typedef OptString<65> OptNISPlusServers;
+typedef OptString<66> OptTFTPServerName; /**< when overloaded */
+typedef OptString<67> OptBootfileName; /**< when overloaded */
+typedef OptList<68, RTNETADDRIPV4> OptMobileIPHomeAgents;
+typedef OptList<69, RTNETADDRIPV4> OptSMTPServers;
+typedef OptList<70, RTNETADDRIPV4> OptPOP3Servers;
+typedef OptList<71, RTNETADDRIPV4> OptNNTPServers;
+typedef OptList<72, RTNETADDRIPV4> OptWWWServers;
+typedef OptList<73, RTNETADDRIPV4> OptFingerServers;
+typedef OptList<74, RTNETADDRIPV4> OptIRCServers;
+typedef OptList<75, RTNETADDRIPV4> OptStreetTalkServers;
+typedef OptList<76, RTNETADDRIPV4> OptSTDAServers;
+typedef OptList<77, uint8_t> OptUserClassId;
+typedef OptList<78, uint8_t> OptSLPDirectoryAgent; /**< complicated, so just byte list for now. RFC2610 */
+typedef OptList<79, uint8_t> OptSLPServiceScope; /**< complicated, so just byte list for now. RFC2610 */
+typedef OptNoValue<80> OptRapidCommit; /**< RFC4039 */
+typedef OptList<119, uint8_t> OptDomainSearch; /**< RFC3397 */
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_DhcpOptions_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpdInternal.h b/src/VBox/NetworkServices/Dhcpd/DhcpdInternal.h
new file mode 100644
index 00000000..dbeb7676
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/DhcpdInternal.h
@@ -0,0 +1,100 @@
+/* $Id: DhcpdInternal.h $ */
+/** @file
+ * DHCP server - Internal header.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_DhcpdInternal_h
+#define VBOX_INCLUDED_SRC_Dhcpd_DhcpdInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifndef IN_VBOXSVC
+# define LOG_GROUP LOG_GROUP_NET_DHCPD
+#elif !defined(LOG_GROUP)
+# define LOG_GROUP LOG_GROUP_MAIN_DHCPCONFIG
+#endif
+#include <iprt/stdint.h>
+#include <iprt/string.h>
+#include <VBox/log.h>
+
+#include <map>
+#include <vector>
+
+#ifndef IN_VBOXSVC
+
+# if __cplusplus >= 199711
+#include <memory>
+using std::shared_ptr;
+# else
+# include <tr1/memory>
+using std::tr1::shared_ptr;
+# endif
+
+class DhcpOption;
+/** DHCP option map (keyed by option number, DhcpOption value). */
+typedef std::map<uint8_t, std::shared_ptr<DhcpOption> > optmap_t;
+
+#endif /* !IN_VBOXSVC */
+
+/** Byte vector. */
+typedef std::vector<uint8_t> octets_t;
+
+/** Raw DHCP option map (keyed by option number, byte vector value). */
+typedef std::map<uint8_t, octets_t> rawopts_t;
+
+
+/** Equal compare operator for mac address. */
+DECLINLINE(bool) operator==(const RTMAC &l, const RTMAC &r)
+{
+ return memcmp(&l, &r, sizeof(RTMAC)) == 0;
+}
+
+/** Less-than compare operator for mac address. */
+DECLINLINE(bool) operator<(const RTMAC &l, const RTMAC &r)
+{
+ return memcmp(&l, &r, sizeof(RTMAC)) < 0;
+}
+
+
+/** @name LogXRel + return NULL helpers
+ * @{ */
+#define DHCP_LOG_RET_NULL(a_MsgArgs) do { LogRel(a_MsgArgs); return NULL; } while (0)
+#define DHCP_LOG2_RET_NULL(a_MsgArgs) do { LogRel2(a_MsgArgs); return NULL; } while (0)
+#define DHCP_LOG3_RET_NULL(a_MsgArgs) do { LogRel3(a_MsgArgs); return NULL; } while (0)
+/** @} */
+
+
+/** @name LogXRel + return a_rcRet helpers
+ * @{ */
+#define DHCP_LOG_RET(a_rcRet, a_MsgArgs) do { LogRel(a_MsgArgs); return (a_rcRet); } while (0)
+#define DHCP_LOG2_RET(a_rcRet, a_MsgArgs) do { LogRel2(a_MsgArgs); return (a_rcRet); } while (0)
+#define DHCP_LOG3_RET(a_rcRet, a_MsgArgs) do { LogRel3(a_MsgArgs); return (a_rcRet); } while (0)
+/** @} */
+
+/** LogRel + RTMsgError helper. */
+#define DHCP_LOG_MSG_ERROR(a_MsgArgs) do { LogRel(a_MsgArgs); RTMsgError a_MsgArgs; } while (0)
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_DhcpdInternal_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp b/src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp
new file mode 100644
index 00000000..5d1e52a1
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp
@@ -0,0 +1,209 @@
+/* $Id: IPv4Pool.cpp $ */
+/** @file
+ * DHCP server - A pool of IPv4 addresses.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "DhcpdInternal.h"
+#include <iprt/errcore.h>
+
+#include "IPv4Pool.h"
+
+
+int IPv4Pool::init(const IPv4Range &aRange) RT_NOEXCEPT
+{
+ AssertReturn(aRange.isValid(), VERR_INVALID_PARAMETER);
+
+ m_range = aRange;
+ try
+ {
+ m_pool.insert(m_range);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ return VINF_SUCCESS;
+}
+
+
+int IPv4Pool::init(RTNETADDRIPV4 aFirstAddr, RTNETADDRIPV4 aLastAddr) RT_NOEXCEPT
+{
+ return init(IPv4Range(aFirstAddr, aLastAddr));
+}
+
+
+/**
+ * Internal worker for inserting a range into the pool of available addresses.
+ *
+ * @returns IPRT status code (asserted).
+ * @param a_Range The range to insert.
+ */
+int IPv4Pool::i_insert(const IPv4Range &a_Range) RT_NOEXCEPT
+{
+ /*
+ * Check preconditions. Asserting because nobody checks the return code.
+ */
+ AssertReturn(m_range.isValid(), VERR_INVALID_STATE);
+ AssertReturn(a_Range.isValid(), VERR_INVALID_PARAMETER);
+ AssertReturn(m_range.contains(a_Range), VERR_INVALID_PARAMETER);
+
+ /*
+ * Check that the incoming range doesn't overlap with existing ranges in the pool.
+ */
+ it_t itHint = m_pool.upper_bound(IPv4Range(a_Range.LastAddr)); /* successor, insertion hint */
+#if 0 /** @todo r=bird: This code is wrong. It has no end() check for starters. Since the method is
+ * only for internal consumption, I've replaced it with a strict build assertion. */
+ if (itHint != m_pool.begin())
+ {
+ it_t prev(itHint);
+ --prev;
+ if (a_Range.FirstAddr <= prev->LastAddr)
+ {
+ LogRel(("%08x-%08x conflicts with %08x-%08x\n",
+ a_Range.FirstAddr, a_Range.LastAddr,
+ prev->FirstAddr, prev->LastAddr));
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+#endif
+#ifdef VBOX_STRICT
+ for (it_t it2 = m_pool.begin(); it2 != m_pool.end(); ++it2)
+ AssertMsg(it2->LastAddr < a_Range.FirstAddr || it2->FirstAddr > a_Range.LastAddr,
+ ("%08RX32-%08RX32 conflicts with %08RX32-%08RX32\n",
+ a_Range.FirstAddr, a_Range.LastAddr, it2->FirstAddr, it2->LastAddr));
+#endif
+
+ /*
+ * No overlaps, insert it.
+ */
+ try
+ {
+ m_pool.insert(itHint, a_Range);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Allocates an available IPv4 address from the pool.
+ *
+ * @returns Non-zero network order IPv4 address on success, zero address
+ * (0.0.0.0) on failure.
+ */
+RTNETADDRIPV4 IPv4Pool::allocate()
+{
+ RTNETADDRIPV4 RetAddr;
+ if (!m_pool.empty())
+ {
+ /* Grab the first address in the pool: */
+ it_t itBeg = m_pool.begin();
+ RetAddr.u = RT_H2N_U32(itBeg->FirstAddr);
+
+ if (itBeg->FirstAddr == itBeg->LastAddr)
+ m_pool.erase(itBeg);
+ else
+ {
+ /* Trim the entry (re-inserting it): */
+ IPv4Range trimmed = *itBeg;
+ trimmed.FirstAddr += 1;
+ Assert(trimmed.FirstAddr <= trimmed.LastAddr);
+ m_pool.erase(itBeg);
+ try
+ {
+ m_pool.insert(trimmed);
+ }
+ catch (std::bad_alloc &)
+ {
+ /** @todo r=bird: Theortically the insert could fail with a bad_alloc and we'd
+ * drop a range of IP address. It would be nice if we could safely modify itBit
+ * without having to re-insert it. The author of this code (not bird) didn't
+ * seem to think this safe?
+ *
+ * If we want to play safe and all that, just use a AVLRU32TREE (or AVLRU64TREE
+ * if lazy) AVL tree from IPRT. Since we know exactly how it's implemented and
+ * works, there will be no uncertanties like this when using it (both here
+ * and in the i_insert validation logic). */
+ LogRelFunc(("Caught bad_alloc! We're truely buggered now!\n"));
+ }
+ }
+ }
+ else
+ RetAddr.u = 0;
+ return RetAddr;
+}
+
+
+/**
+ * Allocate the given address.
+ *
+ * @returns Success indicator.
+ * @param a_Addr The IP address to allocate (network order).
+ */
+bool IPv4Pool::allocate(RTNETADDRIPV4 a_Addr)
+{
+ /*
+ * Find the range containing a_Addr.
+ */
+ it_t it = m_pool.lower_bound(IPv4Range(a_Addr)); /* candidate range */
+ if (it != m_pool.end())
+ {
+ Assert(RT_N2H_U32(a_Addr.u) <= it->LastAddr); /* by definition of < and lower_bound */
+
+ if (it->contains(a_Addr))
+ {
+ /*
+ * Remove a_Addr from the range by way of re-insertion.
+ */
+ const IPV4HADDR haddr = RT_N2H_U32(a_Addr.u);
+ IPV4HADDR first = it->FirstAddr;
+ IPV4HADDR last = it->LastAddr;
+
+ m_pool.erase(it);
+ if (first != last)
+ {
+ if (haddr == first)
+ i_insert(++first, last);
+ else if (haddr == last)
+ i_insert(first, --last);
+ else
+ {
+ i_insert(first, haddr - 1);
+ i_insert(haddr + 1, last);
+ }
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/VBox/NetworkServices/Dhcpd/IPv4Pool.h b/src/VBox/NetworkServices/Dhcpd/IPv4Pool.h
new file mode 100644
index 00000000..12aa8f54
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/IPv4Pool.h
@@ -0,0 +1,154 @@
+/* $Id: IPv4Pool.h $ */
+/** @file
+ * DHCP server - a pool of IPv4 addresses
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_IPv4Pool_h
+#define VBOX_INCLUDED_SRC_Dhcpd_IPv4Pool_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/asm.h>
+#include <iprt/stdint.h>
+#include <iprt/net.h>
+#include <set>
+
+
+/** Host order IPv4 address. */
+typedef uint32_t IPV4HADDR;
+
+
+/**
+ * A range of IPv4 addresses (in host order).
+ */
+struct IPv4Range
+{
+ IPV4HADDR FirstAddr; /**< Lowest address. */
+ IPV4HADDR LastAddr; /**< Higest address (inclusive). */
+
+ IPv4Range() RT_NOEXCEPT
+ : FirstAddr(0), LastAddr(0)
+ {}
+
+ explicit IPv4Range(IPV4HADDR aSingleAddr) RT_NOEXCEPT
+ : FirstAddr(aSingleAddr), LastAddr(aSingleAddr)
+ {}
+
+ IPv4Range(IPV4HADDR aFirstAddr, IPV4HADDR aLastAddr) RT_NOEXCEPT
+ : FirstAddr(aFirstAddr), LastAddr(aLastAddr)
+ {}
+
+ explicit IPv4Range(RTNETADDRIPV4 aSingleAddr) RT_NOEXCEPT
+ : FirstAddr(RT_N2H_U32(aSingleAddr.u)), LastAddr(RT_N2H_U32(aSingleAddr.u))
+ {}
+
+ IPv4Range(RTNETADDRIPV4 aFirstAddr, RTNETADDRIPV4 aLastAddr) RT_NOEXCEPT
+ : FirstAddr(RT_N2H_U32(aFirstAddr.u)), LastAddr(RT_N2H_U32(aLastAddr.u))
+ {}
+
+ bool isValid() const RT_NOEXCEPT
+ {
+ return FirstAddr <= LastAddr;
+ }
+
+ bool contains(IPV4HADDR addr) const RT_NOEXCEPT
+ {
+ return FirstAddr <= addr && addr <= LastAddr;
+ }
+
+ bool contains(RTNETADDRIPV4 addr) const RT_NOEXCEPT
+ {
+ return contains(RT_N2H_U32(addr.u));
+ }
+
+ /** Checks if this range includes the @a a_rRange. */
+ bool contains(const IPv4Range &a_rRange) const RT_NOEXCEPT
+ {
+ return a_rRange.isValid()
+ && FirstAddr <= a_rRange.FirstAddr
+ && a_rRange.LastAddr <= LastAddr;
+ }
+};
+
+
+inline bool operator==(const IPv4Range &l, const IPv4Range &r) RT_NOEXCEPT
+{
+ return l.FirstAddr == r.FirstAddr && l.LastAddr == r.LastAddr;
+}
+
+
+inline bool operator<(const IPv4Range &l, const IPv4Range &r) RT_NOEXCEPT
+{
+ return l.LastAddr < r.FirstAddr;
+}
+
+
+/**
+ * IPv4 address pool.
+ *
+ * This manages a single range of IPv4 addresses (m_range). Unallocated
+ * addresses are tracked as a set of sub-ranges in the m_pool set.
+ *
+ */
+class IPv4Pool
+{
+ typedef std::set<IPv4Range> set_t;
+ typedef set_t::iterator it_t;
+
+ /** The IPv4 range of this pool. */
+ IPv4Range m_range;
+ /** Pool of available IPv4 ranges. */
+ set_t m_pool;
+
+public:
+ IPv4Pool()
+ {}
+
+ int init(const IPv4Range &aRange) RT_NOEXCEPT;
+ int init(RTNETADDRIPV4 aFirstAddr, RTNETADDRIPV4 aLastAddr) RT_NOEXCEPT;
+
+ RTNETADDRIPV4 allocate();
+ bool allocate(RTNETADDRIPV4);
+
+ /**
+ * Checks if the pool range includes @a a_Addr (allocation status not considered).
+ */
+ bool contains(RTNETADDRIPV4 a_Addr) const RT_NOEXCEPT
+ {
+ return m_range.contains(a_Addr);
+ }
+
+private:
+ int i_insert(const IPv4Range &range) RT_NOEXCEPT;
+#if 0
+ int i_insert(IPV4HADDR a_Single) RT_NOEXCEPT { return i_insert(IPv4Range(a_Single)); }
+#endif
+ int i_insert(IPV4HADDR a_First, IPV4HADDR a_Last) RT_NOEXCEPT { return i_insert(IPv4Range(a_First, a_Last)); }
+ int i_insert(RTNETADDRIPV4 a_Single) RT_NOEXCEPT { return i_insert(IPv4Range(a_Single)); }
+ int i_insert(RTNETADDRIPV4 a_First, RTNETADDRIPV4 a_Last) RT_NOEXCEPT { return i_insert(IPv4Range(a_First, a_Last)); }
+};
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_IPv4Pool_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk
new file mode 100644
index 00000000..c5340ad4
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk
@@ -0,0 +1,108 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-makefile for the DHCP server.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH := ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifndef LWIP_SOURCES
+ include $(PATH_SUB_CURRENT)/../../Devices/Network/lwip-new/Config.kmk
+endif
+
+#
+# Hardended stub executable.
+#
+ifdef VBOX_WITH_HARDENING
+ PROGRAMS += VBoxNetDHCPHardened
+ VBoxNetDHCPHardened_TEMPLATE = VBoxR3HardenedExe
+ VBoxNetDHCPHardened_NAME = VBoxNetDHCP
+ VBoxNetDHCPHardened_SOURCES = VBoxNetDhcpdHardened.cpp
+ VBoxNetDHCPHardened_LDFLAGS.win = /SUBSYSTEM:windows
+ $(call VBOX_SET_VER_INFO_DLL,VBoxNetDHCPHardened,VirtualBox DHCP Server,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description.
+endif
+
+
+#
+# The DHCP server module (dll if hardended)
+#
+ifdef VBOX_WITH_HARDENING
+ DLLS += VBoxNetDHCP
+ VBoxNetDHCP_TEMPLATE := VBoxR3Dll
+else
+ PROGRAMS += VBoxNetDHCP
+ VBoxNetDHCP_TEMPLATE := VBoxR3Exe
+endif
+
+# (current dir is for for lwipopts.h)
+VBoxNetDHCP_INCS += . $(addprefix ../../Devices/Network/lwip-new/,$(LWIP_INCS))
+
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxNetDHCP_DEFS = KBUILD_TYPE="$(KBUILD_TYPE)"
+else
+ VBoxNetDHCP_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\"
+endif
+VBoxNetDHCP_DEFS += \
+ $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,)
+
+#VBoxNetDHCP_DEFS += IPv6
+#VBoxNetDHCP_DEFS.linux = WITH_VALGRIND
+ifneq ($(KBUILD_TARGET),win)
+ VBoxNetDHCP_DEFS += VBOX_WITH_XPCOM
+ VBoxNetDHCP_INCS += $(VBOX_XPCOM_INCS)
+ ifneq ($(KBUILD_TARGET),darwin)
+ # We want -std=c++11 for 4.7 and newer compilers, and -std=c++0x for older ones.
+ VBoxNetDHCP_CXXFLAGS += -std=$(if $(VBOX_GCC_VERSION_CXX),$(if $(VBOX_GCC_VERSION_CXX) < 40700,c++0x,c++11),c++0x)
+ endif
+endif
+VBoxNetDHCP_INCS += \
+ ../NetLib
+VBoxNetDHCP_SOURCES = \
+ ClientId.cpp \
+ Config.cpp \
+ DHCPD.cpp \
+ Db.cpp \
+ DhcpMessage.cpp \
+ DhcpOptions.cpp \
+ IPv4Pool.cpp \
+ Timestamp.cpp \
+ VBoxNetDhcpd.cpp \
+ ../NetLib/IntNetIf.cpp \
+ ../../Main/glue/VBoxLogRelCreate.cpp \
+ ../../Main/glue/GetVBoxUserHomeDirectory.cpp \
+ $(addprefix ../../Devices/Network/lwip-new/,$(LWIP_SOURCES))
+
+VBoxNetDHCP_LIBS = $(LIB_RUNTIME)
+VBoxNetDHCP_LIBS.solaris += socket nsl
+VBoxNetDHCP_LDFLAGS.win = /SUBSYSTEM:windows
+
+ifdef VBOX_WITH_HARDENING
+ $(call VBOX_SET_VER_INFO_DLL,VBoxNetDHCP,VirtualBox DHCP Server (dll),$(VBOX_WINDOWS_ICON_FILE)) # Version info / description.
+else
+ $(call VBOX_SET_VER_INFO_EXE,VBoxNetDHCP,VirtualBox DHCP Server,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description.
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/NetworkServices/Dhcpd/Timestamp.cpp b/src/VBox/NetworkServices/Dhcpd/Timestamp.cpp
new file mode 100644
index 00000000..014eebb6
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/Timestamp.cpp
@@ -0,0 +1,45 @@
+/* $Id: Timestamp.cpp $ */
+/** @file
+ * DHCP server - timestamps
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "DhcpdInternal.h"
+#include "Timestamp.h"
+
+
+size_t Timestamp::strFormatHelper(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput) const RT_NOEXCEPT
+{
+ RTTIMESPEC TimeSpec;
+ RTTIME Time;
+ char szBuf[64];
+ ssize_t cchBuf = RTTimeToStringEx(RTTimeExplode(&Time, getAbsTimeSpec(&TimeSpec)), szBuf, sizeof(szBuf), 0);
+ Assert(cchBuf > 0);
+ return pfnOutput(pvArgOutput, szBuf, cchBuf);
+}
+
diff --git a/src/VBox/NetworkServices/Dhcpd/Timestamp.h b/src/VBox/NetworkServices/Dhcpd/Timestamp.h
new file mode 100644
index 00000000..083cbc6b
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/Timestamp.h
@@ -0,0 +1,122 @@
+/* $Id: Timestamp.h $ */
+/** @file
+ * DHCP server - timestamps
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_Timestamp_h
+#define VBOX_INCLUDED_SRC_Dhcpd_Timestamp_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/time.h>
+
+
+/**
+ * Wrapper around RTTIMESPEC.
+ *
+ * @note Originally wanting to use RTTimeNanoTS rather than RTTimeNow. The term
+ * "absolute" was used for when the RTTimeNanoTS() value was converted to
+ * something approximating unix epoch relative time with help of
+ * RTTimeNow(). Code was later changed to just wrap RTTIMESPEC and drop
+ * all usage of RTTimeNanoTS, ASSUMING that system time is stable.
+ */
+class Timestamp
+{
+ RTTIMESPEC m_TimeSpec;
+
+public:
+ Timestamp() RT_NOEXCEPT
+ {
+ RTTimeSpecSetNano(&m_TimeSpec, 0);
+ }
+
+ Timestamp(PCRTTIMESPEC a_pTimeSpec) RT_NOEXCEPT
+ {
+ m_TimeSpec = *a_pTimeSpec;
+ }
+
+ /** Get a timestamp initialized to current time. */
+ static Timestamp now() RT_NOEXCEPT
+ {
+ RTTIMESPEC Tmp;
+ return Timestamp(RTTimeNow(&Tmp));
+ }
+
+ /** Get a timestamp with the given value in seconds since unix epoch. */
+ static Timestamp absSeconds(int64_t secTimestamp) RT_NOEXCEPT
+ {
+ RTTIMESPEC Tmp;
+ return Timestamp(RTTimeSpecSetSeconds(&Tmp, secTimestamp));
+ }
+
+ Timestamp &addSeconds(int64_t cSecs) RT_NOEXCEPT
+ {
+ RTTimeSpecAddSeconds(&m_TimeSpec, cSecs);
+ return *this;
+ }
+
+ Timestamp &subSeconds(int64_t cSecs) RT_NOEXCEPT
+ {
+ RTTimeSpecSubSeconds(&m_TimeSpec, cSecs);
+ return *this;
+ }
+
+ RTTIMESPEC *getAbsTimeSpec(RTTIMESPEC *pTime) const RT_NOEXCEPT
+ {
+ *pTime = m_TimeSpec;
+ return pTime;
+ }
+
+ int64_t getAbsSeconds() const RT_NOEXCEPT
+ {
+ return RTTimeSpecGetSeconds(&m_TimeSpec);
+ }
+
+ /** Only for log formatting. */
+ size_t strFormatHelper(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput) const RT_NOEXCEPT;
+
+ int compare(const Timestamp &a_rRight) const RT_NOEXCEPT
+ {
+ return RTTimeSpecCompare(&m_TimeSpec, &a_rRight.m_TimeSpec);
+ }
+
+ friend bool operator<( const Timestamp &, const Timestamp &) RT_NOEXCEPT;
+ friend bool operator>( const Timestamp &, const Timestamp &) RT_NOEXCEPT;
+ friend bool operator==(const Timestamp &, const Timestamp &) RT_NOEXCEPT;
+ friend bool operator!=(const Timestamp &, const Timestamp &) RT_NOEXCEPT;
+ friend bool operator<=(const Timestamp &, const Timestamp &) RT_NOEXCEPT;
+ friend bool operator>=(const Timestamp &, const Timestamp &) RT_NOEXCEPT;
+};
+
+
+inline bool operator<( const Timestamp &l, const Timestamp &r) RT_NOEXCEPT { return l.compare(r) < 0; }
+inline bool operator>( const Timestamp &l, const Timestamp &r) RT_NOEXCEPT { return l.compare(r) > 0; }
+inline bool operator==(const Timestamp &l, const Timestamp &r) RT_NOEXCEPT { return l.compare(r) == 0; }
+inline bool operator!=(const Timestamp &l, const Timestamp &r) RT_NOEXCEPT { return l.compare(r) != 0; }
+inline bool operator<=(const Timestamp &l, const Timestamp &r) RT_NOEXCEPT { return l.compare(r) <= 0; }
+inline bool operator>=(const Timestamp &l, const Timestamp &r) RT_NOEXCEPT { return l.compare(r) >= 0; }
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_Timestamp_h */
diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp
new file mode 100644
index 00000000..563c7e39
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp
@@ -0,0 +1,533 @@
+/* $Id: VBoxNetDhcpd.cpp $ */
+/** @file
+ * VBoxNetDhcpd - DHCP server for host-only and NAT networks.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_net_dhcp VBoxNetDHCP
+ *
+ * Write a few words...
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cdefs.h>
+
+/*
+ * Need to get host/network order conversion stuff from Windows headers,
+ * so we do not define them in LWIP and then try to re-define them in
+ * Windows headers.
+ */
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/winsock2.h>
+#endif
+
+#include "DhcpdInternal.h"
+#include <iprt/param.h>
+#include <iprt/errcore.h>
+
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+#include <iprt/net.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+
+#include <VBox/sup.h>
+#include <VBox/vmm/vmm.h>
+#include <VBox/vmm/pdmnetinline.h>
+#include <VBox/intnet.h>
+#include <VBox/intnetinline.h>
+
+#include "VBoxLwipCore.h"
+#include "Config.h"
+#include "DHCPD.h"
+#include "DhcpMessage.h"
+
+extern "C"
+{
+#include "lwip/sys.h"
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+#include "lwip/tcpip.h"
+#include "lwip/udp.h"
+#include "netif/etharp.h"
+}
+
+#include <iprt/sanitized/string>
+#include <vector>
+#include <memory>
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+
+#include "IntNetIf.h"
+
+struct delete_pbuf
+{
+ delete_pbuf() {}
+ void operator()(struct pbuf *p) const { pbuf_free(p); }
+};
+
+typedef std::unique_ptr<pbuf, delete_pbuf> unique_ptr_pbuf;
+
+
+class VBoxNetDhcpd
+{
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(VBoxNetDhcpd);
+public:
+ VBoxNetDhcpd();
+ ~VBoxNetDhcpd();
+
+ int main(int argc, char **argv);
+
+private:
+ /** The logger instance. */
+ PRTLOGGER m_pStderrReleaseLogger;
+ /** Internal network interface handle. */
+ INTNETIFCTX m_hIf;
+ /** lwip stack connected to the intnet */
+ struct netif m_LwipNetif;
+ /** The DHCP server config. */
+ Config *m_Config;
+ /** Listening pcb */
+ struct udp_pcb *m_Dhcp4Pcb;
+ /** DHCP server instance. */
+ DHCPD m_server;
+
+ int logInitStderr();
+
+ /*
+ * Internal network plumbing.
+ */
+ int ifInit(const RTCString &strNetwork,
+ const RTCString &strTrunk = RTCString(),
+ INTNETTRUNKTYPE enmTrunkType = kIntNetTrunkType_WhateverNone);
+
+ static DECLCALLBACK(void) ifInput(void *pvUser, void *pvFrame, uint32_t cbFrame);
+ void ifInputWorker(void *pvFrame, uint32_t cbFrame);
+
+ /*
+ * lwIP callbacks
+ */
+ static DECLCALLBACK(void) lwipInitCB(void *pvArg);
+ void lwipInit();
+
+ static err_t netifInitCB(netif *pNetif) RT_NOTHROW_PROTO;
+ err_t netifInit(netif *pNetif);
+
+ static err_t netifLinkOutputCB(netif *pNetif, pbuf *pPBuf) RT_NOTHROW_PROTO;
+ err_t netifLinkOutput(pbuf *pPBuf);
+
+ static void dhcp4RecvCB(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+ ip_addr_t *addr, u16_t port) RT_NOTHROW_PROTO;
+ void dhcp4Recv(struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port);
+};
+
+
+VBoxNetDhcpd::VBoxNetDhcpd()
+ : m_pStderrReleaseLogger(NULL),
+ m_hIf(NULL),
+ m_LwipNetif(),
+ m_Config(NULL),
+ m_Dhcp4Pcb(NULL)
+{
+ logInitStderr();
+}
+
+
+VBoxNetDhcpd::~VBoxNetDhcpd()
+{
+ if (m_hIf != NULL)
+ {
+ int rc = IntNetR3IfDestroy(m_hIf);
+ AssertRC(rc);
+ m_hIf = NULL;
+ }
+}
+
+
+/*
+ * We don't know the name of the release log file until we parse our
+ * configuration because we use network name as basename. To get
+ * early logging to work, start with stderr-only release logger.
+ *
+ * We disable "sup" for this logger to avoid spam from SUPR3Init().
+ */
+int VBoxNetDhcpd::logInitStderr()
+{
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+
+ uint32_t fFlags = 0;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+
+ PRTLOGGER pLogger;
+ int rc = RTLogCreate(&pLogger, fFlags,
+ "all -sup all.restrict -default.restrict",
+ NULL, /* environment base */
+ RT_ELEMENTS(s_apszGroups), s_apszGroups,
+ RTLOGDEST_STDERR, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("Failed to init stderr logger: %Rrs\n", rc);
+ return rc;
+ }
+
+ m_pStderrReleaseLogger = pLogger;
+ RTLogRelSetDefaultInstance(m_pStderrReleaseLogger);
+
+ return VINF_SUCCESS;
+}
+
+
+int VBoxNetDhcpd::ifInit(const RTCString &strNetwork,
+ const RTCString &strTrunk,
+ INTNETTRUNKTYPE enmTrunkType)
+{
+ if (enmTrunkType == kIntNetTrunkType_Invalid)
+ enmTrunkType = kIntNetTrunkType_WhateverNone;
+
+ int rc = IntNetR3IfCreateEx(&m_hIf, strNetwork.c_str(), enmTrunkType,
+ strTrunk.c_str(), _128K /*cbSend*/, _256K /*cbRecv*/,
+ 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ rc = IntNetR3IfSetActive(m_hIf, true /*fActive*/);
+
+ return rc;
+}
+
+
+void VBoxNetDhcpd::ifInputWorker(void *pvFrame, uint32_t cbFrame)
+{
+ struct pbuf *p = pbuf_alloc(PBUF_RAW, (u16_t)cbFrame + ETH_PAD_SIZE, PBUF_POOL);
+ AssertPtrReturnVoid(p);
+
+ /*
+ * The code below is inlined version of:
+ *
+ * pbuf_header(p, -ETH_PAD_SIZE); // hide padding
+ * pbuf_take(p, pvFrame, cbFrame);
+ * pbuf_header(p, ETH_PAD_SIZE); // reveal padding
+ */
+ struct pbuf *q = p;
+ uint8_t *pbChunk = (uint8_t *)pvFrame;
+ do
+ {
+ uint8_t *payload = (uint8_t *)q->payload;
+ size_t len = q->len;
+
+#if ETH_PAD_SIZE
+ if (RT_LIKELY(q == p)) /* single pbuf is large enough */
+ {
+ payload += ETH_PAD_SIZE;
+ len -= ETH_PAD_SIZE;
+ }
+#endif
+ memcpy(payload, pbChunk, len);
+ pbChunk += len;
+ q = q->next;
+ } while (RT_UNLIKELY(q != NULL));
+
+ m_LwipNetif.input(p, &m_LwipNetif);
+}
+
+
+/**
+ * Got a frame from the internal network, feed it to the lwIP stack.
+ */
+/*static*/
+DECLCALLBACK(void) VBoxNetDhcpd::ifInput(void *pvUser, void *pvFrame, uint32_t cbFrame)
+{
+ AssertReturnVoid(pvFrame);
+ AssertReturnVoid( cbFrame > sizeof(RTNETETHERHDR)
+ && cbFrame <= UINT16_MAX - ETH_PAD_SIZE);
+
+ VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(pvUser);
+ self->ifInputWorker(pvFrame, cbFrame);
+}
+
+
+/**
+ * Got a frame from the lwIP stack, feed it to the internal network.
+ */
+err_t VBoxNetDhcpd::netifLinkOutput(pbuf *pPBuf)
+{
+ if (pPBuf->tot_len < sizeof(struct eth_hdr)) /* includes ETH_PAD_SIZE */
+ return ERR_ARG;
+
+ u16_t cbFrame = pPBuf->tot_len - ETH_PAD_SIZE;
+ INTNETFRAME Frame;
+ int rc = IntNetR3IfQueryOutputFrame(m_hIf, cbFrame, &Frame);
+ if (RT_FAILURE(rc))
+ return ERR_MEM;
+
+ pbuf_copy_partial(pPBuf, Frame.pvFrame, cbFrame, ETH_PAD_SIZE);
+ IntNetR3IfOutputFrameCommit(m_hIf, &Frame);
+ return ERR_OK;
+}
+
+
+/* static */ DECLCALLBACK(void) VBoxNetDhcpd::lwipInitCB(void *pvArg)
+{
+ AssertPtrReturnVoid(pvArg);
+
+ VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(pvArg);
+ self->lwipInit();
+}
+
+
+/* static */ err_t VBoxNetDhcpd::netifInitCB(netif *pNetif) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pNetif, ERR_ARG);
+
+ VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(pNetif->state);
+ return self->netifInit(pNetif);
+}
+
+
+/* static */ err_t VBoxNetDhcpd::netifLinkOutputCB(netif *pNetif, pbuf *pPBuf) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pNetif, ERR_ARG);
+ AssertPtrReturn(pPBuf, ERR_ARG);
+
+ VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(pNetif->state);
+ AssertPtrReturn(self, ERR_IF);
+
+ return self->netifLinkOutput(pPBuf);
+}
+
+
+/* static */ void VBoxNetDhcpd::dhcp4RecvCB(void *arg, struct udp_pcb *pcb,
+ struct pbuf *p,
+ ip_addr_t *addr, u16_t port) RT_NOTHROW_DEF
+{
+ AssertPtrReturnVoid(arg);
+
+ VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(arg);
+ self->dhcp4Recv(pcb, p, addr, port);
+ pbuf_free(p);
+}
+
+
+
+
+
+int VBoxNetDhcpd::main(int argc, char **argv)
+{
+ /*
+ * Register string format types.
+ */
+ ClientId::registerFormat();
+ Binding::registerFormat();
+
+ /*
+ * Parse the command line into a configuration object.
+ */
+ m_Config = Config::create(argc, argv);
+ if (m_Config == NULL)
+ return VERR_GENERAL_FAILURE;
+
+ /*
+ * Initialize the server.
+ */
+ int rc = m_server.init(m_Config);
+ if (RT_SUCCESS(rc))
+ {
+ /* connect to the intnet */
+ rc = ifInit(m_Config->getNetwork(), m_Config->getTrunk(), m_Config->getTrunkType());
+ if (RT_SUCCESS(rc))
+ {
+ /* setup lwip */
+ rc = vboxLwipCoreInitialize(lwipInitCB, this);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Pump packets more or less for ever.
+ */
+ rc = IntNetR3IfPumpPkts(m_hIf, ifInput, this,
+ NULL /*pfnInputGso*/, NULL /*pvUserGso*/);
+ }
+ else
+ DHCP_LOG_MSG_ERROR(("Terminating - vboxLwipCoreInitialize failed: %Rrc\n", rc));
+ }
+ else
+ DHCP_LOG_MSG_ERROR(("Terminating - ifInit failed: %Rrc\n", rc));
+ }
+ else
+ DHCP_LOG_MSG_ERROR(("Terminating - Dhcpd::init failed: %Rrc\n", rc));
+ return rc;
+}
+
+
+void VBoxNetDhcpd::lwipInit()
+{
+ err_t error;
+
+ ip_addr_t addr, mask;
+ ip4_addr_set_u32(&addr, m_Config->getIPv4Address().u);
+ ip4_addr_set_u32(&mask, m_Config->getIPv4Netmask().u);
+
+ netif *pNetif = netif_add(&m_LwipNetif,
+ &addr, &mask,
+ IP_ADDR_ANY, /* gateway */
+ this, /* state */
+ VBoxNetDhcpd::netifInitCB, /* netif_init_fn */
+ tcpip_input); /* netif_input_fn */
+ if (pNetif == NULL)
+ return;
+
+ netif_set_up(pNetif);
+ netif_set_link_up(pNetif);
+
+ m_Dhcp4Pcb = udp_new();
+ if (RT_UNLIKELY(m_Dhcp4Pcb == NULL))
+ return; /* XXX? */
+
+ ip_set_option(m_Dhcp4Pcb, SOF_BROADCAST);
+ udp_recv(m_Dhcp4Pcb, dhcp4RecvCB, this);
+
+ error = udp_bind(m_Dhcp4Pcb, IP_ADDR_ANY, RTNETIPV4_PORT_BOOTPS);
+ if (error != ERR_OK)
+ {
+ udp_remove(m_Dhcp4Pcb);
+ m_Dhcp4Pcb = NULL;
+ return; /* XXX? */
+ }
+}
+
+
+err_t VBoxNetDhcpd::netifInit(netif *pNetif)
+{
+ pNetif->hwaddr_len = sizeof(RTMAC);
+ memcpy(pNetif->hwaddr, &m_Config->getMacAddress(), sizeof(RTMAC));
+
+ pNetif->mtu = 1500;
+
+ pNetif->flags = NETIF_FLAG_BROADCAST
+ | NETIF_FLAG_ETHARP
+ | NETIF_FLAG_ETHERNET;
+
+ pNetif->linkoutput = netifLinkOutputCB;
+ pNetif->output = etharp_output;
+
+ netif_set_default(pNetif);
+ return ERR_OK;
+}
+
+
+void VBoxNetDhcpd::dhcp4Recv(struct udp_pcb *pcb, struct pbuf *p,
+ ip_addr_t *addr, u16_t port)
+{
+ RT_NOREF(pcb, addr, port);
+
+ if (RT_UNLIKELY(p->next != NULL))
+ return; /* XXX: we want it in one chunk */
+
+ bool broadcasted = ip_addr_cmp(ip_current_dest_addr(), &ip_addr_broadcast)
+ || ip_addr_cmp(ip_current_dest_addr(), &ip_addr_any);
+
+ try
+ {
+ DhcpClientMessage *msgIn = DhcpClientMessage::parse(broadcasted, p->payload, p->len);
+ if (msgIn == NULL)
+ return;
+
+ std::unique_ptr<DhcpClientMessage> autoFreeMsgIn(msgIn);
+
+ DhcpServerMessage *msgOut = m_server.process(*msgIn);
+ if (msgOut == NULL)
+ return;
+
+ std::unique_ptr<DhcpServerMessage> autoFreeMsgOut(msgOut);
+
+ ip_addr_t dst = { msgOut->dst().u };
+ if (ip_addr_cmp(&dst, &ip_addr_any))
+ ip_addr_copy(dst, ip_addr_broadcast);
+
+ octets_t data;
+ int rc = msgOut->encode(data);
+ if (RT_FAILURE(rc))
+ return;
+
+ unique_ptr_pbuf q ( pbuf_alloc(PBUF_RAW, (u16_t)data.size(), PBUF_RAM) );
+ if (!q)
+ return;
+
+ err_t error = pbuf_take(q.get(), &data.front(), (u16_t)data.size());
+ if (error != ERR_OK)
+ return;
+
+ error = udp_sendto(pcb, q.get(), &dst, RTNETIPV4_PORT_BOOTPC);
+ if (error != ERR_OK)
+ return;
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRel(("VBoxNetDhcpd::dhcp4Recv: Caught std::bad_alloc!\n"));
+ }
+}
+
+
+
+
+/*
+ * Entry point.
+ */
+extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv)
+{
+ VBoxNetDhcpd Dhcpd;
+ int rc = Dhcpd.main(argc, argv);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+#ifndef VBOX_WITH_HARDENING
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB);
+ if (RT_SUCCESS(rc))
+ return TrustedMain(argc, argv);
+ return RTMsgInitFailure(rc);
+}
+
+
+# ifdef RT_OS_WINDOWS
+/** (We don't want a console usually.) */
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
+{
+ RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
+
+ return main(__argc, __argv);
+}
+# endif /* RT_OS_WINDOWS */
+
+#endif /* !VBOX_WITH_HARDENING */
diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp
new file mode 100644
index 00000000..e29e7e5b
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp
@@ -0,0 +1,35 @@
+/* $Id: VBoxNetDhcpdHardened.cpp $ */
+/** @file
+ * VBoxNetDhcpd - Hardened main().
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/sup.h>
+
+
+int main(int argc, char **argv, char **envp)
+{
+ return SUPR3HardenedMain("VBoxNetDHCP", 0 /* fFlags */, argc, argv, envp);
+}
+
diff --git a/src/VBox/NetworkServices/Dhcpd/lwipopts.h b/src/VBox/NetworkServices/Dhcpd/lwipopts.h
new file mode 100644
index 00000000..2aabc448
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/lwipopts.h
@@ -0,0 +1,191 @@
+/* $Id: lwipopts.h $ */
+/** @file
+ * DHCP server - lwIP configuration options.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Dhcpd_lwipopts_h
+#define VBOX_INCLUDED_SRC_Dhcpd_lwipopts_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/cdefs.h> /* For VBOX_STRICT. */
+#include <iprt/mem.h>
+#include <iprt/alloca.h> /* This may include malloc.h (msc), which is something that has
+ * to be done before redefining any of the functions therein. */
+#include <iprt/rand.h> /* see LWIP_RAND() definition */
+
+/** Make lwIP use the libc malloc, or more precisely (see below) the IPRT
+ * memory allocation functions. */
+#define MEM_LIBC_MALLOC 1
+
+/** Set proper memory alignment. */
+#if HC_ARCH_BITS == 64
+# define MEM_ALIGNMENT 8
+#else
+#define MEM_ALIGNMENT 4
+#endif
+
+/* Padding before Ethernet header to make IP header aligned */
+#define ETH_PAD_SIZE 2
+
+/* IP */
+#define IP_REASSEMBLY 1
+#define IP_REASS_MAX_PBUFS 128
+
+
+
+/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
+ sends a lot of data out of ROM (or other static memory), this
+ should be set high.
+
+ NB: This is for PBUF_ROM and PBUF_REF pbufs only!
+
+ Number of PBUF_POOL pbufs is controlled by PBUF_POOL_SIZE that,
+ somewhat confusingly, breaks MEMP_NUM_* pattern.
+
+ PBUF_RAM pbufs are allocated with mem_malloc (with MEM_LIBC_MALLOC
+ set to 1 this is just system malloc), not memp_malloc. */
+#define MEMP_NUM_PBUF (1024 * 4)
+
+
+/* MEMP_NUM_MLD6_GROUP: Maximum number of IPv6 multicast groups that
+ can be joined.
+
+ We need to be able to join solicited node multicast for each
+ address (potentially different) and two groups for DHCP6. All
+ routers multicast is hardcoded in ip6.c and does not require
+ explicit joining. Provide also for a few extra groups just in
+ case. */
+#define MEMP_NUM_MLD6_GROUP (LWIP_IPV6_NUM_ADDRESSES + /* dhcp6 */ 2 + /* extra */ 8)
+
+
+/* MEMP_NUM_TCPIP_MSG_*: the number of struct tcpip_msg, which is used
+ for sequential API communication and incoming packets. Used in
+ src/api/tcpip.c. */
+#define MEMP_NUM_TCPIP_MSG_API 128
+#define MEMP_NUM_TCPIP_MSG_INPKT 1024
+
+/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
+ per active UDP "connection". */
+#define MEMP_NUM_UDP_PCB 32
+
+/* Pbuf options */
+/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool.
+ This is only for PBUF_POOL pbufs, primarily used by netif drivers.
+
+ This should have been named with the MEMP_NUM_ prefix (cf.
+ MEMP_NUM_PBUF for PBUF_ROM and PBUF_REF) as it controls the size of
+ yet another memp_malloc() pool. */
+#define PBUF_POOL_SIZE (1024 * 4)
+
+/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool.
+ Use default that is based on TCP_MSS and PBUF_LINK_HLEN. */
+#undef PBUF_POOL_BUFSIZE
+
+/** Turn on support for lightweight critical region protection. Leaving this
+ * off uses synchronization code in pbuf.c which is totally polluted with
+ * races. All the other lwip source files would fall back to semaphore-based
+ * synchronization, but pbuf.c is just broken, leading to incorrect allocation
+ * and as a result to assertions due to buffers being double freed. */
+#define SYS_LIGHTWEIGHT_PROT 1
+
+/** Attempt to get rid of htons etc. macro issues. */
+#undef LWIP_PREFIX_BYTEORDER_FUNCS
+
+#define LWIP_TCPIP_CORE_LOCKING_INPUT 0
+#define LWIP_TCPIP_CORE_LOCKING 0
+
+#define LWIP_NETCONN 0
+#define LWIP_SOCKET 0
+#define LWIP_COMPAT_SOCKETS 0
+#define LWIP_COMPAT_MUTEX 1
+
+#define LWIP_TCP 0
+#define LWI_UDP 1
+#define LWIP_ARP 1
+#define ARP_PROXY 0
+#define LWIP_ETHERNET 1
+
+/* accept any->broadcast */
+#define LWIP_IP_ACCEPT_UDP_PORT(port) ((port) == PP_NTOHS(/*DHCP_SERVER_PORT*/ 67))
+
+#define LWIP_IPV6 0
+#define LWIP_IPV6_FORWARD 0
+#define LWIP_ND6_PROXY 0
+
+#define LWIP_ND6_ALLOW_RA_UPDATES (!LWIP_IPV6_FORWARD)
+#define LWIP_IPV6_SEND_ROUTER_SOLICIT (!LWIP_IPV6_FORWARD)
+/* IPv6 autoconfig we don't need in proxy, but it required for very seldom cases
+ * iSCSI over intnet with IPv6
+ */
+#define LWIP_IPV6_AUTOCONFIG 1
+#if LWIP_IPV6_FORWARD /* otherwise use the default from lwip/opt.h */
+#define LWIP_IPV6_DUP_DETECT_ATTEMPTS 0
+#endif
+
+#define LWIP_IPV6_FRAG 1
+
+/**
+ * aka Slirp mode.
+ */
+#define LWIP_CONNECTION_PROXY 0
+#define IP_FORWARD 0
+
+/* MEMP_NUM_SYS_TIMEOUT: the number of simultaneously active
+ timeouts. */
+#define MEMP_NUM_SYS_TIMEOUT 16
+
+
+/* this is required for IPv6 and IGMP needs */
+#define LWIP_RAND() RTRandU32()
+
+/* Debugging stuff. */
+#ifdef DEBUG
+# define LWIP_DEBUG
+# include "lwip-log.h"
+
+# define LWIP_PROXY_DEBUG LWIP_DBG_OFF
+#endif /* DEBUG */
+
+/* printf formatter definitions */
+#define U16_F "hu"
+#define S16_F "hd"
+#define X16_F "hx"
+#define U32_F "u"
+#define S32_F "d"
+#define X32_F "x"
+
+/* Redirect libc memory alloc functions to IPRT. */
+#define malloc(x) RTMemAlloc(x)
+#define realloc(x,y) RTMemRealloc((x), (y))
+#define free(x) RTMemFree(x)
+
+/* Align VBOX_STRICT and LWIP_NOASSERT. */
+#ifndef VBOX_STRICT
+# define LWIP_NOASSERT 1
+#endif
+
+#endif /* !VBOX_INCLUDED_SRC_Dhcpd_lwipopts_h */