From f8fe689a81f906d1b91bb3220acde2a4ecb14c5b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 6 May 2024 05:01:46 +0200 Subject: Adding upstream version 6.0.4-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/NetworkServices/Dhcpd/Config.cpp | 949 ++++++++++++++++++++++++++++++ 1 file changed, 949 insertions(+) create mode 100644 src/VBox/NetworkServices/Dhcpd/Config.cpp (limited to 'src/VBox/NetworkServices/Dhcpd/Config.cpp') diff --git a/src/VBox/NetworkServices/Dhcpd/Config.cpp b/src/VBox/NetworkServices/Dhcpd/Config.cpp new file mode 100644 index 00000000..b11e29fd --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Config.cpp @@ -0,0 +1,949 @@ +/* $Id: Config.cpp $ */ +/** @file + * DHCP server - server configuration + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "Config.h" + +#include +#include /* NB: must come before getopt.h */ +#include +#include +#include +#include +#include + +#include + +#include + +class ConfigFileError + : public RTCError +{ +public: + ConfigFileError(const char *pszMessage) + : RTCError(pszMessage) {} + + ConfigFileError(const RTCString &a_rstrMessage) + : RTCError(a_rstrMessage) {} +}; + + +Config::Config() + : m_strHome(), + m_strNetwork(), + m_strBaseName(), + m_strTrunk(), + m_enmTrunkType(kIntNetTrunkType_Invalid), + m_MacAddress(), + m_IPv4Address(), + m_IPv4Netmask(), + m_IPv4PoolFirst(), + m_IPv4PoolLast(), + m_GlobalOptions(), + m_VMMap() +{ + return; +} + + +int Config::init() +{ + int rc; + + rc = homeInit(); + if (RT_FAILURE(rc)) + return rc; + + return VINF_SUCCESS; +} + + +int Config::homeInit() +{ + int rc; + + /* pathname of ~/.VirtualBox or equivalent */ + char szHome[RTPATH_MAX]; + rc = com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome), false); + if (RT_FAILURE(rc)) + { + LogDHCP(("unable to find VirtualBox home directory: %Rrs", rc)); + return rc; + } + + m_strHome.assign(szHome); + return VINF_SUCCESS; +} + + +void Config::setNetwork(const std::string &aStrNetwork) +{ + AssertReturnVoid(m_strNetwork.empty()); + + m_strNetwork = aStrNetwork; + sanitizeBaseName(); +} + + +/* + * 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? + */ +int Config::logInit() +{ + int rc; + size_t cch; + + if (m_strHome.empty() || m_strBaseName.empty()) + return VERR_GENERAL_FAILURE; + + /* default log file name */ + char szLogFile[RTPATH_MAX]; + cch = RTStrPrintf(szLogFile, sizeof(szLogFile), + "%s%c%s-Dhcpd.log", + m_strHome.c_str(), RTPATH_DELIMITER, m_strBaseName.c_str()); + if (cch >= sizeof(szLogFile)) + return VERR_BUFFER_OVERFLOW; + + + /* get a writable copy of the base name */ + char szBaseName[RTPATH_MAX]; + rc = RTStrCopy(szBaseName, sizeof(szBaseName), m_strBaseName.c_str()); + if (RT_FAILURE(rc)) + return rc; + + /* sanitize base name some more to be usable in an environment variable name */ + for (char *p = szBaseName; *p != '\0'; ++p) + { + if ( *p != '_' + && (*p < '0' || '9' < *p) + && (*p < 'a' || 'z' < *p) + && (*p < 'A' || 'Z' < *p)) + { + *p = '_'; + } + } + + + /* name of the environment variable to control logging */ + char szEnvVarBase[128]; + cch = RTStrPrintf(szEnvVarBase, sizeof(szEnvVarBase), + "VBOXDHCP_%s_RELEASE_LOG", szBaseName); + if (cch >= sizeof(szEnvVarBase)) + return VERR_BUFFER_OVERFLOW; + + + rc = com::VBoxLogRelCreate("DHCP Server", + szLogFile, + RTLOGFLAGS_PREFIX_TIME_PROG, + "all all.restrict -default.restrict", + szEnvVarBase, + RTLOGDEST_FILE +#ifdef DEBUG + | RTLOGDEST_STDERR +#endif + , + 32768 /* cMaxEntriesPerGroup */, + 0 /* cHistory */, + 0 /* uHistoryFileTime */, + 0 /* uHistoryFileSize */, + NULL /* pErrInfo */); + + return rc; +} + + +int Config::complete() +{ + int rc; + + if (m_strNetwork.empty()) + { + LogDHCP(("network name is not specified\n")); + return false; + } + + logInit(); + + bool fMACGenerated = false; + if ( m_MacAddress.au16[0] == 0 + && m_MacAddress.au16[1] == 0 + && m_MacAddress.au16[2] == 0) + { + RTUUID Uuid; + RTUuidCreate(&Uuid); + + 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]; + + LogDHCP(("MAC address is not specified: will use generated MAC %RTmac\n", &m_MacAddress)); + fMACGenerated = true; + } + + /* unicast MAC address */ + if (m_MacAddress.au8[0] & 0x01) + { + LogDHCP(("MAC address is not unicast: %RTmac\n", &m_MacAddress)); + return VERR_GENERAL_FAILURE; + } + + /* unicast IP address */ + if ((m_IPv4Address.au8[0] & 0xe0) == 0xe0) + { + LogDHCP(("IP address is not unicast: %RTnaipv4\n", m_IPv4Address.u)); + return VERR_GENERAL_FAILURE; + } + + /* valid netmask */ + int iPrefixLengh; + rc = RTNetMaskToPrefixIPv4(&m_IPv4Netmask, &iPrefixLengh); + if (RT_FAILURE(rc) || iPrefixLengh == 0) + { + LogDHCP(("IP mask is not valid: %RTnaipv4\n", m_IPv4Netmask.u)); + return VERR_GENERAL_FAILURE; + } + + /* first IP is from the same network */ + if ((m_IPv4PoolFirst.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u)) + { + LogDHCP(("first pool address is outside the network %RTnaipv4/%d: %RTnaipv4\n", + (m_IPv4Address.u & m_IPv4Netmask.u), iPrefixLengh, + m_IPv4PoolFirst.u)); + return VERR_GENERAL_FAILURE; + } + + /* last IP is from the same network */ + if ((m_IPv4PoolLast.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u)) + { + LogDHCP(("last pool address is outside the network %RTnaipv4/%d: %RTnaipv4\n", + (m_IPv4Address.u & m_IPv4Netmask.u), iPrefixLengh, + m_IPv4PoolLast.u)); + return VERR_GENERAL_FAILURE; + } + + /* the pool is valid */ + if (RT_N2H_U32(m_IPv4PoolLast.u) < RT_N2H_U32(m_IPv4PoolFirst.u)) + { + LogDHCP(("pool range is invalid: %RTnaipv4 - %RTnaipv4\n", + m_IPv4PoolFirst.u, m_IPv4PoolLast.u)); + return VERR_GENERAL_FAILURE; + } + + /* our own address is not inside the pool */ + if ( RT_N2H_U32(m_IPv4PoolFirst.u) <= RT_N2H_U32(m_IPv4Address.u) + && RT_N2H_U32(m_IPv4Address.u) <= RT_N2H_U32(m_IPv4PoolLast.u)) + { + LogDHCP(("server address inside the pool range %RTnaipv4 - %RTnaipv4: %RTnaipv4\n", + m_IPv4PoolFirst.u, m_IPv4PoolLast.u, m_IPv4Address.u)); + return VERR_GENERAL_FAILURE; + } + + if (!fMACGenerated) + LogDHCP(("MAC address %RTmac\n", &m_MacAddress)); + LogDHCP(("IP address %RTnaipv4/%d\n", m_IPv4Address.u, iPrefixLengh)); + LogDHCP(("address pool %RTnaipv4 - %RTnaipv4\n", m_IPv4PoolFirst.u, m_IPv4PoolLast.u)); + + return VINF_SUCCESS; +} + + +Config *Config::hardcoded() +{ + int rc; + + std::unique_ptr config(new Config()); + rc = config->init(); + if (RT_FAILURE(rc)) + return NULL; + + config->setNetwork("HostInterfaceNetworking-vboxnet0"); + config->m_strTrunk.assign("vboxnet0"); + config->m_enmTrunkType = kIntNetTrunkType_NetFlt; + + config->m_MacAddress.au8[0] = 0x08; + config->m_MacAddress.au8[1] = 0x00; + config->m_MacAddress.au8[2] = 0x27; + config->m_MacAddress.au8[3] = 0xa9; + config->m_MacAddress.au8[4] = 0xcf; + config->m_MacAddress.au8[5] = 0xef; + + + config->m_IPv4Address.u = RT_H2N_U32_C(0xc0a838fe); /* 192.168.56.254 */ + config->m_IPv4Netmask.u = RT_H2N_U32_C(0xffffff00); /* 255.255.255.0 */ + + /* flip to test naks */ +#if 1 + config->m_IPv4PoolFirst.u = RT_H2N_U32_C(0xc0a8385a); /* 192.168.56.90 */ + config->m_IPv4PoolLast.u = RT_H2N_U32_C(0xc0a83863); /* 192.168.56.99 */ +#else + config->m_IPv4PoolFirst.u = RT_H2N_U32_C(0xc0a838c9); /* 192.168.56.201 */ + config->m_IPv4PoolLast.u = RT_H2N_U32_C(0xc0a838dc); /* 192.168.56.220 */ +#endif + + rc = config->complete(); + AssertRCReturn(rc, NULL); + + return config.release(); +} + + +/* compatibility with old VBoxNetDHCP */ +static const RTGETOPTDEF g_aCompatOptions[] = +{ + { "--ip-address", 'i', RTGETOPT_REQ_IPV4ADDR }, + { "--lower-ip", 'l', RTGETOPT_REQ_IPV4ADDR }, + { "--mac-address", 'a', RTGETOPT_REQ_MACADDR }, + { "--need-main", 'M', RTGETOPT_REQ_BOOL }, + { "--netmask", 'm', RTGETOPT_REQ_IPV4ADDR }, + { "--network", 'n', RTGETOPT_REQ_STRING }, + { "--trunk-name", 't', RTGETOPT_REQ_STRING }, + { "--trunk-type", 'T', RTGETOPT_REQ_STRING }, + { "--upper-ip", 'u', RTGETOPT_REQ_IPV4ADDR }, +}; + + +Config *Config::compat(int argc, char **argv) +{ + RTGETOPTSTATE State; + int rc; + + rc = RTGetOptInit(&State, argc, argv, + g_aCompatOptions, RT_ELEMENTS(g_aCompatOptions), 1, + RTGETOPTINIT_FLAGS_NO_STD_OPTS); + AssertRCReturn(rc, NULL); + + std::unique_ptr config(new Config()); + rc = config->init(); + if (RT_FAILURE(rc)) + return NULL; + + for (;;) + { + RTGETOPTUNION Val; + + rc = RTGetOpt(&State, &Val); + if (rc == 0) /* done */ + break; + + switch (rc) + { + case 'a': /* --mac-address */ + if ( config->m_MacAddress.au16[0] != 0 + || config->m_MacAddress.au16[1] != 0 + || config->m_MacAddress.au16[2] != 0) + { + RTMsgError("Duplicate --mac-address option"); + return NULL; + } + config->m_MacAddress = Val.MacAddr; + break; + + case 'i': /* --ip-address */ + if (config->m_IPv4Address.u != 0) + { + RTMsgError("Duplicate --ip-address option"); + return NULL; + } + config->m_IPv4Address = Val.IPv4Addr; + break; + + case 'l': /* --lower-ip */ + if (config->m_IPv4PoolFirst.u != 0) + { + RTMsgError("Duplicate --lower-ip option"); + return NULL; + } + config->m_IPv4PoolFirst = Val.IPv4Addr; + break; + + case 'M': /* --need-main */ + /* for backward compatibility, ignored */ + break; + + case 'm': /* --netmask */ + if (config->m_IPv4Netmask.u != 0) + { + RTMsgError("Duplicate --netmask option"); + return NULL; + } + config->m_IPv4Netmask = Val.IPv4Addr; + break; + + case 'n': /* --network */ + if (!config->m_strNetwork.empty()) + { + RTMsgError("Duplicate --network option"); + return NULL; + } + config->setNetwork(Val.psz); + break; + + case 't': /* --trunk-name */ + if (!config->m_strTrunk.empty()) + { + RTMsgError("Duplicate --trunk-name option"); + return NULL; + } + config->m_strTrunk.assign(Val.psz); + break; + + case 'T': /* --trunk-type */ + if (config->m_enmTrunkType != kIntNetTrunkType_Invalid) + { + RTMsgError("Duplicate --trunk-type option"); + return NULL; + } + else if (strcmp(Val.psz, "none") == 0) + config->m_enmTrunkType = kIntNetTrunkType_None; + else if (strcmp(Val.psz, "whatever") == 0) + config->m_enmTrunkType = kIntNetTrunkType_WhateverNone; + else if (strcmp(Val.psz, "netflt") == 0) + config->m_enmTrunkType = kIntNetTrunkType_NetFlt; + else if (strcmp(Val.psz, "netadp") == 0) + config->m_enmTrunkType = kIntNetTrunkType_NetAdp; + else + { + RTMsgError("Unknown trunk type '%s'", Val.psz); + return NULL; + } + break; + + case 'u': /* --upper-ip */ + if (config->m_IPv4PoolLast.u != 0) + { + RTMsgError("Duplicate --upper-ip option"); + return NULL; + } + config->m_IPv4PoolLast = Val.IPv4Addr; + break; + + case VINF_GETOPT_NOT_OPTION: + RTMsgError("%s: Unexpected command line argument", Val.psz); + return NULL; + + default: + RTGetOptPrintError(rc, &Val); + return NULL; + } + } + + rc = config->complete(); + if (RT_FAILURE(rc)) + return NULL; + + return config.release(); +} + + +#define DHCPD_GETOPT_COMMENT 256 /* No short option for --comment */ +static const RTGETOPTDEF g_aOptions[] = +{ + { "--config", 'c', RTGETOPT_REQ_STRING }, + { "--comment", DHCPD_GETOPT_COMMENT, RTGETOPT_REQ_STRING } +}; + + +Config *Config::create(int argc, char **argv) +{ + RTGETOPTSTATE State; + int rc; + + rc = RTGetOptInit(&State, argc, argv, + g_aOptions, RT_ELEMENTS(g_aOptions), 1, + RTGETOPTINIT_FLAGS_NO_STD_OPTS); + AssertRCReturn(rc, NULL); + + std::unique_ptr config; + for (;;) + { + RTGETOPTUNION Val; + + rc = RTGetOpt(&State, &Val); + if (rc == 0) /* done */ + break; + + switch (rc) + { + case 'c': /* --config */ + if (config.get() != NULL) + { + printf("Duplicate option: --config '%s'\n", Val.psz); + return NULL; + } + + printf("reading config from %s\n", Val.psz); + config.reset(Config::read(Val.psz)); + if (config.get() == NULL) + return NULL; + + break; + + case DHCPD_GETOPT_COMMENT: /* --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. + */ + continue; + + case VINF_GETOPT_NOT_OPTION: + RTMsgError("Unexpected command line argument: '%s'", Val.psz); + return NULL; + + default: + RTGetOptPrintError(rc, &Val); + return NULL; + } + } + + if (config.get() == NULL) + return NULL; + + rc = config->complete(); + if (RT_FAILURE(rc)) + return NULL; + + return config.release(); +} + + +Config *Config::read(const char *pszFileName) +{ + int rc; + + if (pszFileName == NULL || pszFileName[0] == '\0') + return NULL; + + xml::Document doc; + try + { + xml::XmlFileParser parser; + parser.read(pszFileName, doc); + } + catch (const xml::EIPRTFailure &e) + { + LogDHCP(("%s\n", e.what())); + return NULL; + } + catch (const RTCError &e) + { + LogDHCP(("%s\n", e.what())); + return NULL; + } + catch (...) + { + LogDHCP(("Unknown exception while reading and parsing '%s'\n", + pszFileName)); + return NULL; + } + + std::unique_ptr config(new Config()); + rc = config->init(); + if (RT_FAILURE(rc)) + return NULL; + + try + { + config->parseConfig(doc.getRootElement()); + } + catch (const RTCError &e) + { + LogDHCP(("%s\n", e.what())); + return NULL; + } + catch (...) + { + LogDHCP(("Unexpected exception\n")); + return NULL; + } + + return config.release(); +} + + +void Config::parseConfig(const xml::ElementNode *root) +{ + if (root == NULL) + throw ConfigFileError("Empty config file"); + + /* + * XXX: NAMESPACE API IS COMPLETELY BROKEN, SO IGNORE IT FOR NOW + */ + if (!root->nameEquals("DHCPServer")) + { + const char *name = root->getName(); + throw ConfigFileError(RTCStringFmt("Unexpected root element \"%s\"", + name ? name : "(null)")); + } + + parseServer(root); + + /** @todo r=bird: Visual C++ 2010 does not grok this use of 'auto'. */ + // 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; + } +} + + +static void getIPv4AddrAttribute(const xml::ElementNode *pNode, const char *pcszAttrName, + RTNETADDRIPV4 *pAddr) +{ + RTCString strAddr; + bool fHasAttr = pNode->getAttributeValue(pcszAttrName, &strAddr); + if (!fHasAttr) + throw ConfigFileError(RTCStringFmt("%s attribute missing", + pcszAttrName)); + + int rc = RTNetStrToIPv4Addr(strAddr.c_str(), pAddr); + if (RT_FAILURE(rc)) + throw ConfigFileError(RTCStringFmt("%s attribute invalid", + pcszAttrName)); +} + + +void Config::parseServer(const xml::ElementNode *server) +{ + /* + * DHCPServer attributes + */ + RTCString strNetworkName; + bool fHasNetworkName = server->getAttributeValue("networkName", &strNetworkName); + if (!fHasNetworkName) + throw ConfigFileError("DHCPServer/@networkName missing"); + + setNetwork(strNetworkName.c_str()); + + RTCString strTrunkType; + if (!server->getAttributeValue("trunkType", &strTrunkType)) + throw ConfigFileError("DHCPServer/@trunkType missing"); + if (strTrunkType == "none") + m_enmTrunkType = kIntNetTrunkType_None; + else if (strTrunkType == "whatever") + m_enmTrunkType = kIntNetTrunkType_WhateverNone; + else if (strTrunkType == "netflt") + m_enmTrunkType = kIntNetTrunkType_NetFlt; + else if (strTrunkType == "netadp") + m_enmTrunkType = kIntNetTrunkType_NetAdp; + else + throw ConfigFileError(RTCStringFmt("Invalid DHCPServer/@trunkType value: %s", strTrunkType.c_str())); + + if ( m_enmTrunkType == kIntNetTrunkType_NetFlt + || m_enmTrunkType == kIntNetTrunkType_NetAdp) + { + RTCString strTrunk; + if (!server->getAttributeValue("trunkName", &strTrunk)) + throw ConfigFileError("DHCPServer/@trunkName missing"); + m_strTrunk = strTrunk.c_str(); + } + else + m_strTrunk = ""; + + getIPv4AddrAttribute(server, "IPAddress", &m_IPv4Address); + getIPv4AddrAttribute(server, "networkMask", &m_IPv4Netmask); + getIPv4AddrAttribute(server, "lowerIP", &m_IPv4PoolFirst); + getIPv4AddrAttribute(server, "upperIP", &m_IPv4PoolLast); + + /* + * DHCPServer children + */ + xml::NodesLoop it(*server); + const xml::ElementNode *node; + while ((node = it.forAllNodes()) != NULL) + { + /* + * Global options + */ + if (node->nameEquals("Options")) + { + parseGlobalOptions(node); + } + + /* + * Per-VM configuration + */ + else if (node->nameEquals("Config")) + { + parseVMConfig(node); + } + } +} + + +void Config::parseGlobalOptions(const xml::ElementNode *options) +{ + xml::NodesLoop it(*options); + const xml::ElementNode *node; + while ((node = it.forAllNodes()) != NULL) + { + if (node->nameEquals("Option")) + { + parseOption(node, m_GlobalOptions); + } + else + { + throw ConfigFileError(RTCStringFmt("Unexpected element \"%s\"", + node->getName())); + } + } +} + + +/* + * 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). + */ +void Config::parseVMConfig(const xml::ElementNode *config) +{ + RTMAC mac; + int rc; + + RTCString strMac; + bool fHasMac = config->getAttributeValue("MACAddress", &strMac); + if (!fHasMac) + throw ConfigFileError(RTCStringFmt("Config missing MACAddress attribute")); + + rc = parseMACAddress(mac, strMac); + if (RT_FAILURE(rc)) + { + throw ConfigFileError(RTCStringFmt("Malformed MACAddress attribute \"%s\"", + strMac.c_str())); + } + + vmmap_t::iterator vmit( m_VMMap.find(mac) ); + if (vmit != m_VMMap.end()) + { + throw ConfigFileError(RTCStringFmt("Duplicate Config for MACAddress \"%s\"", + strMac.c_str())); + } + + optmap_t &vmopts = m_VMMap[mac]; + + xml::NodesLoop it(*config); + const xml::ElementNode *node; + while ((node = it.forAllNodes()) != NULL) + if (node->nameEquals("Option")) + parseOption(node, vmopts); + else + throw ConfigFileError(RTCStringFmt("Unexpected element \"%s\"", + node->getName())); +} + + +int Config::parseMACAddress(RTMAC &aMac, const RTCString &aStr) +{ + RTMAC mac; + int rc; + + rc = RTNetStrToMacAddr(aStr.c_str(), &mac); + if (RT_FAILURE(rc)) + return rc; + if (rc == VWRN_TRAILING_CHARS) + return VERR_INVALID_PARAMETER; + + aMac = mac; + return VINF_SUCCESS; +} + + +int Config::parseClientId(OptClientId &aId, const RTCString &aStr) +{ + RT_NOREF(aId, aStr); + return VERR_GENERAL_FAILURE; +} + + +/* + * Parse