diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/NetworkServices/Dhcpd | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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..0deda965 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/ClientId.cpp @@ -0,0 +1,126 @@ +/* $Id: ClientId.cpp $ */ +/** @file + * DHCP server - client identifier + */ + +/* + * Copyright (C) 2017-2022 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..b3645b0a --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/ClientId.h @@ -0,0 +1,93 @@ +/* $Id: ClientId.h $ */ +/** @file + * DHCP server - client identifier + */ + +/* + * Copyright (C) 2017-2022 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..a8aa7260 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Config.cpp @@ -0,0 +1,1395 @@ +/* $Id: Config.cpp $ */ +/** @file + * DHCP server - server configuration + */ + +/* + * Copyright (C) 2017-2022 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..a7c1ec15 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Config.h @@ -0,0 +1,401 @@ +/* $Id: Config.h $ */ +/** @file + * DHCP server - server configuration + */ + +/* + * Copyright (C) 2017-2022 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..b44b8e47 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp @@ -0,0 +1,420 @@ +/* $Id: DHCPD.cpp $ */ +/** @file + * DHCP server - protocol logic + */ + +/* + * Copyright (C) 2017-2022 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..541ebb86 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DHCPD.h @@ -0,0 +1,88 @@ +/* $Id: DHCPD.h $ */ +/** @file + * DHCP server - protocol logic + */ + +/* + * Copyright (C) 2017-2022 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..ab93d6ce --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Db.cpp @@ -0,0 +1,1060 @@ +/* $Id: Db.cpp $ */ +/** @file + * DHCP server - address database + */ + +/* + * Copyright (C) 2017-2022 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..88905074 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Db.h @@ -0,0 +1,221 @@ +/* $Id: Db.h $ */ +/** @file + * DHCP server - address database + */ + +/* + * Copyright (C) 2017-2022 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..4697770b --- /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-2022 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..feded2b8 --- /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-2022 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..d8832f97 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp @@ -0,0 +1,515 @@ +/* $Id: DhcpOptions.cpp $ */ +/** @file + * DHCP server - DHCP options + */ + +/* + * Copyright (C) 2017-2022 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..5f93c0fb --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.h @@ -0,0 +1,832 @@ +/* $Id: DhcpOptions.h $ */ +/** @file + * DHCP server - DHCP options + */ + +/* + * Copyright (C) 2017-2022 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..80a73eff --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DhcpdInternal.h @@ -0,0 +1,100 @@ +/* $Id: DhcpdInternal.h $ */ +/** @file + * DHCP server - Internal header. + */ + +/* + * Copyright (C) 2017-2022 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..5a362cb1 --- /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-2022 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..d96252ce --- /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-2022 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..50d5a3c8 --- /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-2022 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..836f44db --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Timestamp.cpp @@ -0,0 +1,45 @@ +/* $Id: Timestamp.cpp $ */ +/** @file + * DHCP server - timestamps + */ + +/* + * Copyright (C) 2017-2022 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..0048babf --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Timestamp.h @@ -0,0 +1,122 @@ +/* $Id: Timestamp.h $ */ +/** @file + * DHCP server - timestamps + */ + +/* + * Copyright (C) 2017-2022 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..7d91f5ef --- /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-2022 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..bc9ec7ae --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp @@ -0,0 +1,35 @@ +/* $Id: VBoxNetDhcpdHardened.cpp $ */ +/** @file + * VBoxNetDhcpd - Hardened main(). + */ + +/* + * Copyright (C) 2009-2022 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..bcc15e07 --- /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-2022 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 */ |