From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/NetworkServices/.scm-settings | 29 + src/VBox/NetworkServices/Dhcpd/ClientId.cpp | 126 + src/VBox/NetworkServices/Dhcpd/ClientId.h | 93 + src/VBox/NetworkServices/Dhcpd/Config.cpp | 1395 +++++++++++ src/VBox/NetworkServices/Dhcpd/Config.h | 401 ++++ src/VBox/NetworkServices/Dhcpd/DHCPD.cpp | 420 ++++ src/VBox/NetworkServices/Dhcpd/DHCPD.h | 88 + src/VBox/NetworkServices/Dhcpd/Db.cpp | 1060 ++++++++ src/VBox/NetworkServices/Dhcpd/Db.h | 221 ++ src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp | 447 ++++ src/VBox/NetworkServices/Dhcpd/DhcpMessage.h | 161 ++ src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp | 515 ++++ src/VBox/NetworkServices/Dhcpd/DhcpOptions.h | 832 +++++++ src/VBox/NetworkServices/Dhcpd/DhcpdInternal.h | 100 + src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp | 209 ++ src/VBox/NetworkServices/Dhcpd/IPv4Pool.h | 154 ++ src/VBox/NetworkServices/Dhcpd/Makefile.kmk | 108 + src/VBox/NetworkServices/Dhcpd/Timestamp.cpp | 45 + src/VBox/NetworkServices/Dhcpd/Timestamp.h | 122 + src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp | 533 +++++ .../NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp | 35 + src/VBox/NetworkServices/Dhcpd/lwipopts.h | 191 ++ .../IntNetSwitch/IntNetSwitchInternal.h | 109 + src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk | 75 + .../IntNetSwitch/SrvIntNetWrapper.cpp | 63 + .../IntNetSwitch/VBoxIntNetSwitch.cpp | 669 ++++++ .../NetworkServices/IntNetSwitch/darwin/Info.plist | 22 + src/VBox/NetworkServices/Makefile.kmk | 46 + src/VBox/NetworkServices/NAT/Makefile.kmk | 162 ++ src/VBox/NetworkServices/NAT/RTWinPoll.cpp | 164 ++ src/VBox/NetworkServices/NAT/RTWinSocketPair.cpp | 235 ++ src/VBox/NetworkServices/NAT/VBoxNetLwipNAT.cpp | 2352 ++++++++++++++++++ .../NetworkServices/NAT/VBoxNetNATHardened.cpp | 37 + src/VBox/NetworkServices/NAT/dhcp6.h | 61 + src/VBox/NetworkServices/NAT/fwtcp.c | 333 +++ src/VBox/NetworkServices/NAT/fwudp.c | 555 +++++ src/VBox/NetworkServices/NAT/getrawsock.c | 165 ++ src/VBox/NetworkServices/NAT/lwipopts.h | 215 ++ src/VBox/NetworkServices/NAT/mkrawsock.c | 349 +++ src/VBox/NetworkServices/NAT/portfwd.c | 273 +++ src/VBox/NetworkServices/NAT/portfwd.h | 84 + src/VBox/NetworkServices/NAT/proxy.c | 746 ++++++ src/VBox/NetworkServices/NAT/proxy.h | 137 ++ src/VBox/NetworkServices/NAT/proxy_dhcp6ds.c | 327 +++ src/VBox/NetworkServices/NAT/proxy_pollmgr.c | 838 +++++++ src/VBox/NetworkServices/NAT/proxy_pollmgr.h | 95 + src/VBox/NetworkServices/NAT/proxy_rtadvd.c | 427 ++++ src/VBox/NetworkServices/NAT/proxy_tftpd.c | 991 ++++++++ src/VBox/NetworkServices/NAT/pxdns.c | 942 ++++++++ src/VBox/NetworkServices/NAT/pxping.c | 2020 ++++++++++++++++ src/VBox/NetworkServices/NAT/pxping_win.c | 672 ++++++ src/VBox/NetworkServices/NAT/pxremap.c | 333 +++ src/VBox/NetworkServices/NAT/pxremap.h | 62 + src/VBox/NetworkServices/NAT/pxtcp.c | 2524 ++++++++++++++++++++ src/VBox/NetworkServices/NAT/pxtcp.h | 52 + src/VBox/NetworkServices/NAT/pxudp.c | 858 +++++++ src/VBox/NetworkServices/NAT/rtmon_bsd.c | 126 + src/VBox/NetworkServices/NAT/rtmon_linux.c | 259 ++ src/VBox/NetworkServices/NAT/rtmon_win.c | 31 + src/VBox/NetworkServices/NAT/tftp.h | 59 + src/VBox/NetworkServices/NAT/winpoll.h | 63 + src/VBox/NetworkServices/NAT/winutils.h | 220 ++ src/VBox/NetworkServices/NetLib/IntNetIf.cpp | 575 +++++ src/VBox/NetworkServices/NetLib/IntNetIf.h | 112 + src/VBox/NetworkServices/NetLib/Makefile.kup | 0 src/VBox/NetworkServices/NetLib/VBoxNetARP.cpp | 165 ++ .../NetworkServices/NetLib/VBoxNetBaseService.cpp | 858 +++++++ .../NetworkServices/NetLib/VBoxNetBaseService.h | 159 ++ src/VBox/NetworkServices/NetLib/VBoxNetIntIf.cpp | 150 ++ src/VBox/NetworkServices/NetLib/VBoxNetLib.h | 82 + .../NetLib/VBoxNetPortForwardString.cpp | 382 +++ src/VBox/NetworkServices/NetLib/VBoxNetUDP.cpp | 314 +++ .../NetworkServices/NetLib/VBoxPortForwardString.h | 69 + src/VBox/NetworkServices/NetLib/cpp/utils.h | 57 + src/VBox/NetworkServices/NetLib/shared_ptr.h | 112 + 75 files changed, 28071 insertions(+) create mode 100644 src/VBox/NetworkServices/.scm-settings create mode 100644 src/VBox/NetworkServices/Dhcpd/ClientId.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/ClientId.h create mode 100644 src/VBox/NetworkServices/Dhcpd/Config.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/Config.h create mode 100644 src/VBox/NetworkServices/Dhcpd/DHCPD.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/DHCPD.h create mode 100644 src/VBox/NetworkServices/Dhcpd/Db.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/Db.h create mode 100644 src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/DhcpMessage.h create mode 100644 src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/DhcpOptions.h create mode 100644 src/VBox/NetworkServices/Dhcpd/DhcpdInternal.h create mode 100644 src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/IPv4Pool.h create mode 100644 src/VBox/NetworkServices/Dhcpd/Makefile.kmk create mode 100644 src/VBox/NetworkServices/Dhcpd/Timestamp.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/Timestamp.h create mode 100644 src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/lwipopts.h create mode 100644 src/VBox/NetworkServices/IntNetSwitch/IntNetSwitchInternal.h create mode 100644 src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk create mode 100644 src/VBox/NetworkServices/IntNetSwitch/SrvIntNetWrapper.cpp create mode 100644 src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp create mode 100644 src/VBox/NetworkServices/IntNetSwitch/darwin/Info.plist create mode 100644 src/VBox/NetworkServices/Makefile.kmk create mode 100644 src/VBox/NetworkServices/NAT/Makefile.kmk create mode 100644 src/VBox/NetworkServices/NAT/RTWinPoll.cpp create mode 100644 src/VBox/NetworkServices/NAT/RTWinSocketPair.cpp create mode 100644 src/VBox/NetworkServices/NAT/VBoxNetLwipNAT.cpp create mode 100644 src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp create mode 100644 src/VBox/NetworkServices/NAT/dhcp6.h create mode 100644 src/VBox/NetworkServices/NAT/fwtcp.c create mode 100644 src/VBox/NetworkServices/NAT/fwudp.c create mode 100644 src/VBox/NetworkServices/NAT/getrawsock.c create mode 100644 src/VBox/NetworkServices/NAT/lwipopts.h create mode 100644 src/VBox/NetworkServices/NAT/mkrawsock.c create mode 100644 src/VBox/NetworkServices/NAT/portfwd.c create mode 100644 src/VBox/NetworkServices/NAT/portfwd.h create mode 100644 src/VBox/NetworkServices/NAT/proxy.c create mode 100644 src/VBox/NetworkServices/NAT/proxy.h create mode 100644 src/VBox/NetworkServices/NAT/proxy_dhcp6ds.c create mode 100644 src/VBox/NetworkServices/NAT/proxy_pollmgr.c create mode 100644 src/VBox/NetworkServices/NAT/proxy_pollmgr.h create mode 100644 src/VBox/NetworkServices/NAT/proxy_rtadvd.c create mode 100644 src/VBox/NetworkServices/NAT/proxy_tftpd.c create mode 100644 src/VBox/NetworkServices/NAT/pxdns.c create mode 100644 src/VBox/NetworkServices/NAT/pxping.c create mode 100644 src/VBox/NetworkServices/NAT/pxping_win.c create mode 100644 src/VBox/NetworkServices/NAT/pxremap.c create mode 100644 src/VBox/NetworkServices/NAT/pxremap.h create mode 100644 src/VBox/NetworkServices/NAT/pxtcp.c create mode 100644 src/VBox/NetworkServices/NAT/pxtcp.h create mode 100644 src/VBox/NetworkServices/NAT/pxudp.c create mode 100644 src/VBox/NetworkServices/NAT/rtmon_bsd.c create mode 100644 src/VBox/NetworkServices/NAT/rtmon_linux.c create mode 100644 src/VBox/NetworkServices/NAT/rtmon_win.c create mode 100644 src/VBox/NetworkServices/NAT/tftp.h create mode 100644 src/VBox/NetworkServices/NAT/winpoll.h create mode 100644 src/VBox/NetworkServices/NAT/winutils.h create mode 100644 src/VBox/NetworkServices/NetLib/IntNetIf.cpp create mode 100644 src/VBox/NetworkServices/NetLib/IntNetIf.h create mode 100644 src/VBox/NetworkServices/NetLib/Makefile.kup create mode 100644 src/VBox/NetworkServices/NetLib/VBoxNetARP.cpp create mode 100644 src/VBox/NetworkServices/NetLib/VBoxNetBaseService.cpp create mode 100644 src/VBox/NetworkServices/NetLib/VBoxNetBaseService.h create mode 100644 src/VBox/NetworkServices/NetLib/VBoxNetIntIf.cpp create mode 100644 src/VBox/NetworkServices/NetLib/VBoxNetLib.h create mode 100644 src/VBox/NetworkServices/NetLib/VBoxNetPortForwardString.cpp create mode 100644 src/VBox/NetworkServices/NetLib/VBoxNetUDP.cpp create mode 100644 src/VBox/NetworkServices/NetLib/VBoxPortForwardString.h create mode 100644 src/VBox/NetworkServices/NetLib/cpp/utils.h create mode 100644 src/VBox/NetworkServices/NetLib/shared_ptr.h (limited to 'src/VBox/NetworkServices') diff --git a/src/VBox/NetworkServices/.scm-settings b/src/VBox/NetworkServices/.scm-settings new file mode 100644 index 00000000..93fd4b73 --- /dev/null +++ b/src/VBox/NetworkServices/.scm-settings @@ -0,0 +1,29 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for the network services. +# + +# +# Copyright (C) 2019-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +/*.h: --guard-relative-to-dir . + diff --git a/src/VBox/NetworkServices/Dhcpd/ClientId.cpp b/src/VBox/NetworkServices/Dhcpd/ClientId.cpp new file mode 100644 index 00000000..a0846b7b --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/ClientId.cpp @@ -0,0 +1,126 @@ +/* $Id: ClientId.cpp $ */ +/** @file + * DHCP server - client identifier + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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(pvValue); + if (pThis == NULL) + return pfnOutput(pvArgOutput, RT_STR_TUPLE("")); + + size_t cb = 0; + if (pThis->m_id.present()) + { + cb += pfnOutput(pvArgOutput, RT_STR_TUPLE("[")); + + const OptClientId::value_t &idopt = pThis->m_id.value(); + for (size_t i = 0; i < idopt.size(); ++i) + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%s%02x", (i == 0 ? "" : ":"), idopt[i]); + + cb += pfnOutput(pvArgOutput, RT_STR_TUPLE("] (")); + } + + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%RTmac", &pThis->m_mac); + + if (pThis->m_id.present()) + cb += pfnOutput(pvArgOutput, RT_STR_TUPLE(")")); + + return cb; +} + + +bool operator==(const ClientId &l, const ClientId &r) RT_NOEXCEPT +{ + if (l.m_id.present()) + { + if (r.m_id.present()) + return l.m_id.value() == r.m_id.value(); + } + else + { + if (!r.m_id.present()) + return l.m_mac == r.m_mac; + } + + return false; +} + + +bool operator<(const ClientId &l, const ClientId &r) RT_NOEXCEPT +{ + if (l.m_id.present()) + { + if (r.m_id.present()) + return l.m_id.value() < r.m_id.value(); + return false; /* the one with id comes last */ + } + else + { + if (r.m_id.present()) + return true; /* the one with id comes last */ + return l.m_mac < r.m_mac; + } +} + diff --git a/src/VBox/NetworkServices/Dhcpd/ClientId.h b/src/VBox/NetworkServices/Dhcpd/ClientId.h new file mode 100644 index 00000000..3d5bcd16 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/ClientId.h @@ -0,0 +1,93 @@ +/* $Id: ClientId.h $ */ +/** @file + * DHCP server - client identifier + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * 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 +#include "DhcpOptions.h" + +/** + * A client is identified by either the Client ID option it sends or its chaddr, + * i.e. MAC address. + */ +class ClientId +{ + /** The mac address of the client. */ + RTMAC m_mac; + /** The client ID. */ + OptClientId m_id; + +public: + ClientId() + : m_mac(), m_id() + {} + /** @throws std::bad_alloc */ + ClientId(const RTMAC &a_mac, const OptClientId &a_id) + : m_mac(a_mac), m_id(a_id) + {} + /** @throws std::bad_alloc */ + ClientId(const ClientId &a_rThat) + : m_mac(a_rThat.m_mac), m_id(a_rThat.m_id) + {} + /** @throws std::bad_alloc */ + ClientId &operator=(const ClientId &a_rThat) + { + m_mac = a_rThat.m_mac; + m_id = a_rThat.m_id; + return *this; + } + + const RTMAC &mac() const RT_NOEXCEPT { return m_mac; } + const OptClientId &id() const RT_NOEXCEPT { return m_id; } + + /** @name String formatting of %R[id]. + * @{ */ + static void registerFormat() RT_NOEXCEPT; +private: + static DECLCALLBACK(size_t) rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, const char *pszType, + void const *pvValue, int cchWidth, int cchPrecision, unsigned fFlags, void *pvUser); + static bool g_fFormatRegistered; + /** @} */ + + friend bool operator==(const ClientId &l, const ClientId &r) RT_NOEXCEPT; + friend bool operator<(const ClientId &l, const ClientId &r) RT_NOEXCEPT; +}; + +bool operator==(const ClientId &l, const ClientId &r) RT_NOEXCEPT; +bool operator<(const ClientId &l, const ClientId &r) RT_NOEXCEPT; + +inline bool operator!=(const ClientId &l, const ClientId &r) RT_NOEXCEPT +{ + return !(l == r); +} + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_ClientId_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/Config.cpp b/src/VBox/NetworkServices/Dhcpd/Config.cpp new file mode 100644 index 00000000..bbfc9356 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Config.cpp @@ -0,0 +1,1395 @@ +/* $Id: Config.cpp $ */ +/** @file + * DHCP server - server configuration + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "DhcpdInternal.h" + +#include +#include /* NB: must come before getopt.h */ +#include +#include +#include +#include +#include +#include + +#include /* 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 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(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 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) +{ + /* + * 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)); + + /* + * 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 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 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 \ elements found under + * /DHCPServer/Options/, /DHCPServer/Group/ and /DHCPServer/Config/. + * + * @param pElmOption An \ 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 \ and \ 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 \ 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 \ 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 \ 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 \. + * + * @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 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 \ 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 \ 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 &a_rBytes, bool fWildcard) const RT_NOEXCEPT +{ + if (a_fPresent) + { + size_t const cbBytes = a_rBytes.size(); + if (cbBytes > 0) + { + if (a_rBytes[cbBytes - 1] == '\0') + { + uint8_t const *pb = &a_rBytes.front(); + if (!fWildcard) + return m_strValue.equals((const char *)pb); + return RTStrSimplePatternMatch(m_strValue.c_str(), (const char *)pb); + } + + if (cbBytes <= 255) + { + char szTmp[256]; + memcpy(szTmp, &a_rBytes.front(), cbBytes); + szTmp[cbBytes] = '\0'; + if (!fWildcard) + return m_strValue.equals(szTmp); + return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp); + } + } + } + return false; + +} + + +int GroupConditionMAC::initCondition(const char *a_pszValue, bool a_fInclusive) +{ + int vrc = RTNetStrToMacAddr(a_pszValue, &m_MACAddress); + if (RT_SUCCESS(vrc)) + return GroupCondition::initCondition(a_pszValue, a_fInclusive); + return vrc; +} + + +bool GroupConditionMAC::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass, + const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT +{ + RT_NOREF(a_ridVendorClass, a_ridUserClass); + return a_ridClient.mac() == m_MACAddress; +} + + +bool GroupConditionMACWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass, + const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT +{ + RT_NOREF(a_ridVendorClass, a_ridUserClass); + char szTmp[32]; + RTStrPrintf(szTmp, sizeof(szTmp), "%RTmac", &a_ridClient.mac()); + return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp); +} + + +bool GroupConditionVendorClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass, + const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT +{ + RT_NOREF(a_ridClient, a_ridUserClass); + return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value()); +} + + +bool GroupConditionVendorClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass, + const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT +{ + RT_NOREF(a_ridClient, a_ridUserClass); + return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value(), true /*fWildcard*/); +} + + +bool GroupConditionUserClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass, + const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT +{ + RT_NOREF(a_ridClient, a_ridVendorClass); + return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value()); +} + + +bool GroupConditionUserClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass, + const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT +{ + RT_NOREF(a_ridClient, a_ridVendorClass); + return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value(), true /*fWildcard*/); +} + diff --git a/src/VBox/NetworkServices/Dhcpd/Config.h b/src/VBox/NetworkServices/Dhcpd/Config.h new file mode 100644 index 00000000..133dc21c --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Config.h @@ -0,0 +1,401 @@ +/* $Id: Config.h $ */ +/** @file + * DHCP server - server configuration + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * 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 +#include +#include +#include + +#include + +#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 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 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 GroupConfigMap; + /** Host configuration map. */ + typedef std::map 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 HostConfigVec; + int getFixedAddressConfigs(HostConfigVec &a_rRetConfigs) const; + + /** Configuration vector. */ + typedef std::vector ConfigVec; + ConfigVec &getConfigsForClient(ConfigVec &a_rRetConfigs, const ClientId &a_ridClient, + const OptVendorClassId &a_ridVendorClass, + const OptUserClassId &a_ridUserClass) const; + optmap_t &getOptionsForClient(optmap_t &a_rRetOpts, const OptParameterRequest &a_rReqOpts, + ConfigVec &a_rConfigs) const; + +private: + /** @name Configuration file reading and parsing + * @{ */ + static Config *i_read(const char *pszFilename, bool fStrict) RT_NOEXCEPT; + void i_parseConfig(const xml::ElementNode *pElmRoot, bool fStrict); + void i_parseServer(const xml::ElementNode *pElmServer, bool fStrict); + /** @} */ +}; + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_Config_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp b/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp new file mode 100644 index 00000000..d0b3f819 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp @@ -0,0 +1,420 @@ +/* $Id: DHCPD.cpp $ */ +/** @file + * DHCP server - protocol logic + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "DhcpdInternal.h" +#include "DHCPD.h" +#include "DhcpOptions.h" + +#include + + +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 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 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 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 ack(i_createMessage(RTNET_DHCP_MT_ACK, req)); + ack->addOptions(info); + ack->maybeUnicast(req); + return ack.release(); +} + + +/** + * 4.3.3 DHCPDECLINE message + * + * If the server receives a DHCPDECLINE message, the client has discovered + * through some other means that the suggested network address is already in + * use. The server MUST mark the network address as not available and SHOULD + * notify the local system administrator of a possible configuration problem. + * + * @throws nothing + */ +DhcpServerMessage *DHCPD::i_doDecline(const DhcpClientMessage &req) RT_NOEXCEPT +{ + RT_NOREF(req); + return NULL; +} + + +/** + * 4.3.4 DHCPRELEASE message + * + * Upon receipt of a DHCPRELEASE message, the server marks the network address + * as not allocated. The server SHOULD retain a record of the client's + * initialization parameters for possible reuse in response to subsequent + * requests from the client. + * + * @throws nothing + */ +DhcpServerMessage *DHCPD::i_doRelease(const DhcpClientMessage &req) RT_NOEXCEPT +{ + if (req.ciaddr().u != 0) + { + bool fReleased = m_db.releaseBinding(req); + if (fReleased) + i_saveLeases(); + } + + return NULL; +} diff --git a/src/VBox/NetworkServices/Dhcpd/DHCPD.h b/src/VBox/NetworkServices/Dhcpd/DHCPD.h new file mode 100644 index 00000000..5b938868 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DHCPD.h @@ -0,0 +1,88 @@ +/* $Id: DHCPD.h $ */ +/** @file + * DHCP server - protocol logic + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * 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 +#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 &req) RT_NOEXCEPT + { + if (req.get() != NULL) + return process(*req.get()); + return NULL; + } + + DhcpServerMessage *process(DhcpClientMessage &req) RT_NOEXCEPT; + +private: + /** @name DHCP message processing methods + * @{ */ + DhcpServerMessage *i_doDiscover(const DhcpClientMessage &req); + DhcpServerMessage *i_doRequest(const DhcpClientMessage &req); + DhcpServerMessage *i_doInform(const DhcpClientMessage &req); + DhcpServerMessage *i_doDecline(const DhcpClientMessage &req) RT_NOEXCEPT; + DhcpServerMessage *i_doRelease(const DhcpClientMessage &req) RT_NOEXCEPT; + + DhcpServerMessage *i_createMessage(int type, const DhcpClientMessage &req); + /** @} */ + + /** @name Lease database handling + * @{ */ + int i_loadLeases() RT_NOEXCEPT; + void i_saveLeases() RT_NOEXCEPT; + /** @} */ +}; + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_DHCPD_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/Db.cpp b/src/VBox/NetworkServices/Dhcpd/Db.cpp new file mode 100644 index 00000000..f4e9012a --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Db.cpp @@ -0,0 +1,1060 @@ +/* $Id: Db.cpp $ */ +/** @file + * DHCP server - address database + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "DhcpdInternal.h" +#include + +#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(pvValue); + if (b == NULL) + return pfnOutput(pvArgOutput, RT_STR_TUPLE("")); + + 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: 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
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