diff options
Diffstat (limited to 'src/VBox/NetworkServices/Dhcpd')
22 files changed, 5567 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/Dhcpd/ClientId.cpp b/src/VBox/NetworkServices/Dhcpd/ClientId.cpp new file mode 100644 index 00000000..3441a4a4 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/ClientId.cpp @@ -0,0 +1,122 @@ +/* $Id: ClientId.cpp $ */ +/** @file + * DHCP server - client identifier + */ + +/* + * 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 <algorithm> + +#include "ClientId.h" + + +bool ClientId::g_fFormatRegistered = false; + + +void ClientId::registerFormat() +{ + if (g_fFormatRegistered) + return; + + int rc = RTStrFormatTypeRegister("id", rtStrFormat, NULL); + AssertRC(rc); + + g_fFormatRegistered = true; +} + + +DECLCALLBACK(size_t) +ClientId::rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + const ClientId *id = static_cast<const ClientId *>(pvValue); + size_t cb = 0; + + AssertReturn(strcmp(pszType, "id") == 0, 0); + RT_NOREF(pszType); + + RT_NOREF(cchWidth, cchPrecision, fFlags); + RT_NOREF(pvUser); + + if (id == NULL) + { + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "<NULL>"); + } + + if (id->m_id.present()) + { + const OptClientId::value_t &idopt = id->m_id.value(); + + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "["); + + for (size_t i = 0; i < idopt.size(); ++i) + { + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "%s%02x", (i == 0 ? "" : ":"), idopt[i]); + } + + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "] ("); + } + + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "%RTmac", &id->m_mac); + + if (id->m_id.present()) + { + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + ")"); + } + + return 0; +} + + +bool operator==(const ClientId &l, const ClientId &r) +{ + 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) +{ + if (l.m_id.present()) + { + if (r.m_id.present()) + return l.m_id.value() < r.m_id.value(); + else + return false; /* the one with id comes last */ + } + else + { + if (r.m_id.present()) + return true; /* the one with id comes last */ + else + 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..430b6e3f --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/ClientId.h @@ -0,0 +1,70 @@ +/* $Id: ClientId.h $ */ +/** @file + * DHCP server - client identifier + */ + +/* + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_ClientId_h +#define VBOX_INCLUDED_SRC_Dhcpd_ClientId_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "Defs.h" +#include <iprt/net.h> +#include "DhcpOptions.h" + +/* + * Client is identified by either the Client ID option it sends or its + * chaddr, i.e. MAC address. + */ +class ClientId +{ + RTMAC m_mac; + OptClientId m_id; + +public: + ClientId() + : m_mac(), m_id() {} + ClientId(const RTMAC &macParam, const OptClientId &idParam) + : m_mac(macParam), m_id(idParam) {} + + const RTMAC &mac() const { return m_mac; } + const OptClientId &id() const { return m_id; } + +public: + static void registerFormat(); /* %R[id] */ + +private: + static bool g_fFormatRegistered; + static DECLCALLBACK(size_t) rtStrFormat( + PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser); + +private: + friend bool operator==(const ClientId &l, const ClientId &r); + friend bool operator<(const ClientId &l, const ClientId &r); +}; + +bool operator==(const ClientId &l, const ClientId &r); +bool operator<(const ClientId &l, const ClientId &r); + +inline bool operator!=(const ClientId &l, const ClientId &r) +{ + 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..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 <iprt/types.h> +#include <iprt/net.h> /* NB: must come before getopt.h */ +#include <iprt/getopt.h> +#include <iprt/path.h> +#include <iprt/message.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#include <VBox/com/com.h> + +#include <iostream> + +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> 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> 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> 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> 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<DhcpOption> opt(it->second); + + octets_t data; + opt->encode(data); + + bool space = false; + for (octets_t::const_iterator itData = data.begin(); itData != data.end(); ++itData) { + uint8_t c = *itData; + if (space) + std::cout << " "; + else + space = true; + std::cout << (int)c; + } + std::cout << std::endl; + } +} + + +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 <Option/> element and add the option to the specified optmap. + */ +void Config::parseOption(const xml::ElementNode *option, optmap_t &optmap) +{ + int rc; + + uint8_t u8Opt; + RTCString strName; + bool fHasName = option->getAttributeValue("name", &strName); + if (fHasName) + { + const char *pcszName = strName.c_str(); + + rc = RTStrToUInt8Full(pcszName, 10, &u8Opt); + if (rc != VINF_SUCCESS) /* no warnings either */ + throw ConfigFileError(RTCStringFmt("Bad option \"%s\"", pcszName)); + + } + else + throw ConfigFileError("missing option name"); + + + uint32_t u32Enc = 0; /* XXX: DhcpOptEncoding_Legacy */ + RTCString strEncoding; + bool fHasEncoding = option->getAttributeValue("encoding", &strEncoding); + if (fHasEncoding) + { + const char *pcszEnc = strEncoding.c_str(); + + rc = RTStrToUInt32Full(pcszEnc, 10, &u32Enc); + if (rc != VINF_SUCCESS) /* no warnings either */ + throw ConfigFileError(RTCStringFmt("Bad encoding \"%s\"", pcszEnc)); + + switch (u32Enc) + { + case 0: /* XXX: DhcpOptEncoding_Legacy */ + case 1: /* XXX: DhcpOptEncoding_Hex */ + break; + default: + throw ConfigFileError(RTCStringFmt("Unknown encoding \"%s\"", pcszEnc)); + } + } + + + /* value may be omitted for OptNoValue options like rapid commit */ + RTCString strValue; + option->getAttributeValue("value", &strValue); + + /* XXX: TODO: encoding, handle hex */ + DhcpOption *opt = DhcpOption::parse(u8Opt, u32Enc, strValue.c_str()); + if (opt == NULL) + throw ConfigFileError(RTCStringFmt("Bad option \"%s\"", strName.c_str())); + + optmap << opt; +} + + +/* + * Set m_strBaseName to sanitized version of m_strNetwork that can be + * used in a path component. + */ +void Config::sanitizeBaseName() +{ + int rc; + + if (m_strNetwork.empty()) + return; + + char szBaseName[RTPATH_MAX]; + rc = RTStrCopy(szBaseName, sizeof(szBaseName), m_strNetwork.c_str()); + if (RT_FAILURE(rc)) + return; + + for (char *p = szBaseName; *p != '\0'; ++p) + { + if (RTPATH_IS_SEP(*p)) + { + *p = '_'; + } + } + + m_strBaseName.assign(szBaseName); +} + + +optmap_t Config::getOptions(const OptParameterRequest &reqOpts, + const ClientId &id, + const OptVendorClassId &vendor) const +{ + optmap_t optmap; + + const optmap_t *vmopts = NULL; + vmmap_t::const_iterator vmit( m_VMMap.find(id.mac()) ); + if (vmit != m_VMMap.end()) + vmopts = &vmit->second; + + RT_NOREF(vendor); /* not yet */ + + + optmap << new OptSubnetMask(m_IPv4Netmask); + + const OptParameterRequest::value_t& reqValue = reqOpts.value(); + for (octets_t::const_iterator itOptReq = reqValue.begin(); itOptReq != reqValue.end(); ++itOptReq) + { + uint8_t optreq = *itOptReq; + std::cout << ">>> requested option " << (int)optreq << std::endl; + + if (optreq == OptSubnetMask::optcode) + { + std::cout << "... always supplied" << std::endl; + continue; + } + + if (vmopts != NULL) + { + optmap_t::const_iterator it( vmopts->find(optreq) ); + if (it != vmopts->end()) + { + optmap << it->second; + std::cout << "... found in VM options" << std::endl; + continue; + } + } + + optmap_t::const_iterator it( m_GlobalOptions.find(optreq) ); + if (it != m_GlobalOptions.end()) + { + optmap << it->second; + std::cout << "... found in global options" << std::endl; + continue; + } + + // std::cout << "... not found" << std::endl; + } + + + /* XXX: testing ... */ + if (vmopts != NULL) + { + for (optmap_t::const_iterator it = vmopts->begin(); it != vmopts->end(); ++it) { + std::shared_ptr<DhcpOption> opt(it->second); + if (optmap.count(opt->optcode()) == 0 && opt->optcode() > 127) + { + optmap << opt; + std::cout << "... forcing VM option " << (int)opt->optcode() << std::endl; + } + } + } + + for (optmap_t::const_iterator it = m_GlobalOptions.begin(); it != m_GlobalOptions.end(); ++it) { + std::shared_ptr<DhcpOption> opt(it->second); + if (optmap.count(opt->optcode()) == 0 && opt->optcode() > 127) + { + optmap << opt; + std::cout << "... forcing global option " << (int)opt->optcode() << std::endl; + } + } + + return optmap; +} diff --git a/src/VBox/NetworkServices/Dhcpd/Config.h b/src/VBox/NetworkServices/Dhcpd/Config.h new file mode 100644 index 00000000..04d35959 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Config.h @@ -0,0 +1,110 @@ +/* $Id: Config.h $ */ +/** @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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_Config_h +#define VBOX_INCLUDED_SRC_Dhcpd_Config_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <iprt/net.h> +#include <iprt/cpp/xml.h> + +#include <VBox/intnet.h> + +#include <string> + +#include "Defs.h" +#include "DhcpOptions.h" +#include "ClientId.h" + + +class Config +{ + /* XXX: TODO: also store fixed address assignments, etc? */ + typedef std::map<RTMAC, optmap_t> vmmap_t; + + std::string m_strHome; /* path of ~/.VirtualBox or equivalent */ + + std::string m_strNetwork; + std::string m_strBaseName; /* m_strNetwork sanitized to be usable in a path component */ + + std::string m_strTrunk; + INTNETTRUNKTYPE m_enmTrunkType; + + RTMAC m_MacAddress; + + RTNETADDRIPV4 m_IPv4Address; + RTNETADDRIPV4 m_IPv4Netmask; + + RTNETADDRIPV4 m_IPv4PoolFirst; + RTNETADDRIPV4 m_IPv4PoolLast; + + optmap_t m_GlobalOptions; + vmmap_t m_VMMap; + +private: + Config(); + + int init(); + int homeInit(); + int logInit(); + int complete(); + +public: /* factory methods */ + static Config *hardcoded(); /* for testing */ + static Config *create(int argc, char **argv); /* --config */ + static Config *compat(int argc, char **argv); /* old VBoxNetDHCP flags */ + +public: /* accessors */ + const std::string &getHome() const { return m_strHome; } + + const std::string &getNetwork() const { return m_strNetwork; } + void setNetwork(const std::string &aStrNetwork); + + const std::string &getBaseName() const { return m_strBaseName; } + const std::string &getTrunk() const { return m_strTrunk; } + INTNETTRUNKTYPE getTrunkType() const { return m_enmTrunkType; } + + const RTMAC &getMacAddress() const { return m_MacAddress; } + + RTNETADDRIPV4 getIPv4Address() const { return m_IPv4Address; } + RTNETADDRIPV4 getIPv4Netmask() const { return m_IPv4Netmask; } + + RTNETADDRIPV4 getIPv4PoolFirst() const { return m_IPv4PoolFirst; } + RTNETADDRIPV4 getIPv4PoolLast() const { return m_IPv4PoolLast; } + +public: + optmap_t getOptions(const OptParameterRequest &reqOpts, const ClientId &id, + const OptVendorClassId &vendor = OptVendorClassId()) const; + +private: + static Config *read(const char *pszFileName); + void parseConfig(const xml::ElementNode *root); + void parseServer(const xml::ElementNode *server); + void parseGlobalOptions(const xml::ElementNode *options); + void parseVMConfig(const xml::ElementNode *config); + void parseOption(const xml::ElementNode *option, optmap_t &optmap); + + int parseMACAddress(RTMAC &aMac, const RTCString &aStr); + int parseClientId(OptClientId &aId, const RTCString &aStr); + + void sanitizeBaseName(); +}; + +#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..5ded7b38 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp @@ -0,0 +1,286 @@ +/* $Id: DHCPD.cpp $ */ +/** @file + * DHCP server - protocol logic + */ + +/* + * 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 "DHCPD.h" +#include "DhcpOptions.h" + +#include <iprt/path.h> + + +DHCPD::DHCPD() + : m_pConfig(NULL), m_db() +{ +} + + +int DHCPD::init(const Config *pConfig) +{ + int rc; + + if (m_pConfig != NULL) + return VERR_INVALID_STATE; + + /* leases file name */ + m_strLeasesFileName = pConfig->getHome(); + m_strLeasesFileName += RTPATH_DELIMITER; + m_strLeasesFileName += pConfig->getBaseName(); + m_strLeasesFileName += "-Dhcpd.leases"; + + rc = m_db.init(pConfig); + if (RT_FAILURE(rc)) + return rc; + + loadLeases(); + + m_pConfig = pConfig; + return VINF_SUCCESS; +} + + +void DHCPD::loadLeases() +{ + m_db.loadLeases(m_strLeasesFileName); +} + + +void DHCPD::saveLeases() +{ + m_db.expire(); + m_db.writeLeases(m_strLeasesFileName); +} + + +DhcpServerMessage *DHCPD::process(DhcpClientMessage &req) +{ + DhcpServerMessage *reply = NULL; + + req.dump(); + + OptServerId sid(req); + if (sid.present() && sid.value().u != m_pConfig->getIPv4Address().u) + { + if (req.broadcasted() && req.messageType() == RTNET_DHCP_MT_REQUEST) + m_db.cancelOffer(req); + + return NULL; + } + + switch (req.messageType()) + { + /* + * Requests that require server's reply. + */ + case RTNET_DHCP_MT_DISCOVER: + reply = doDiscover(req); + break; + + case RTNET_DHCP_MT_REQUEST: + reply = doRequest(req); + break; + + case RTNET_DHCP_MT_INFORM: + reply = doInform(req); + break; + + /* + * Requests that don't have a reply. + */ + case RTNET_DHCP_MT_DECLINE: + doDecline(req); + break; + + case RTNET_DHCP_MT_RELEASE: + doRelease(req); + break; + + /* + * Unexpected or unknown message types. + */ + case RTNET_DHCP_MT_OFFER: /* FALLTHROUGH */ + case RTNET_DHCP_MT_ACK: /* FALLTHROUGH */ + case RTNET_DHCP_MT_NAC: /* FALLTHROUGH */ + default: + break; + } + + return reply; +} + + +DhcpServerMessage *DHCPD::createMessage(int type, DhcpClientMessage &req) +{ + return new DhcpServerMessage(req, type, m_pConfig->getIPv4Address()); +} + + +DhcpServerMessage *DHCPD::doDiscover(DhcpClientMessage &req) +{ + /* + * 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; + + Binding *b = m_db.allocateBinding(req); + if (b == NULL) + return NULL; + + + std::unique_ptr<DhcpServerMessage> reply; + + bool fRapidCommit = OptRapidCommit(req).present(); + if (!fRapidCommit) + { + reply.reset(createMessage(RTNET_DHCP_MT_OFFER, req)); + + if (b->state() < Binding::OFFERED) + b->setState(Binding::OFFERED); + + /* use small lease time internally to quickly free unclaimed offers? */ + } + else + { + reply.reset(createMessage(RTNET_DHCP_MT_ACK, req)); + reply->addOption(OptRapidCommit(true)); + + b->setState(Binding::ACKED); + saveLeases(); + } + + reply->setYiaddr(b->addr()); + reply->addOption(OptLeaseTime(b->leaseTime())); + + + OptParameterRequest optlist(req); + reply->addOptions(m_pConfig->getOptions(optlist, req.clientId())); + + // reply->maybeUnicast(req); /* XXX: we reject ciaddr != 0 above */ + return reply.release(); +} + + +DhcpServerMessage *DHCPD::doRequest(DhcpClientMessage &req) +{ + OptRequestedAddress reqAddr(req); + if (req.ciaddr().u != 0 && reqAddr.present() && reqAddr.value().u != req.ciaddr().u) + { + std::unique_ptr<DhcpServerMessage> nak ( + createMessage(RTNET_DHCP_MT_NAC, req) + ); + nak->addOption(OptMessage("Requested address does not match ciaddr")); + return nak.release(); + } + + + Binding *b = m_db.allocateBinding(req); + if (b == NULL) + { + return createMessage(RTNET_DHCP_MT_NAC, req); + } + + + std::unique_ptr<DhcpServerMessage> ack ( + createMessage(RTNET_DHCP_MT_ACK, req) + ); + + b->setState(Binding::ACKED); + saveLeases(); + + ack->setYiaddr(b->addr()); + ack->addOption(OptLeaseTime(b->leaseTime())); + + OptParameterRequest optlist(req); + ack->addOptions(m_pConfig->getOptions(optlist, req.clientId())); + + ack->addOption(OptMessage("Ok, ok, here it is")); + + 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. + */ +DhcpServerMessage *DHCPD::doInform(DhcpClientMessage &req) +{ + if (req.ciaddr().u == 0) + return NULL; + + const OptParameterRequest params(req); + if (!params.present()) + return NULL; + + optmap_t info(m_pConfig->getOptions(params, req.clientId())); + if (info.empty()) + return NULL; + + std::unique_ptr<DhcpServerMessage> ack ( + 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. + */ +DhcpServerMessage *DHCPD::doDecline(DhcpClientMessage &req) +{ + 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. + */ +DhcpServerMessage *DHCPD::doRelease(DhcpClientMessage &req) +{ + if (req.ciaddr().u == 0) + return NULL; + + bool released = m_db.releaseBinding(req); + if (released) + 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..5857da78 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DHCPD.h @@ -0,0 +1,65 @@ +/* $Id: DHCPD.h $ */ +/** @file + * DHCP server - protocol logic + */ + +/* + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_DHCPD_h +#define VBOX_INCLUDED_SRC_Dhcpd_DHCPD_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "Defs.h" +#include "Config.h" +#include "DhcpMessage.h" +#include "Db.h" + + +class DHCPD +{ + const Config *m_pConfig; + std::string m_strLeasesFileName; + Db m_db; + +public: + DHCPD(); + + int init(const Config *); + + DhcpServerMessage *process(const std::unique_ptr<DhcpClientMessage> &req) + { + if (req.get() == NULL) + return NULL; + + return process(*req.get()); + } + + DhcpServerMessage *process(DhcpClientMessage &req); + +private: + DhcpServerMessage *doDiscover(DhcpClientMessage &req); + DhcpServerMessage *doRequest(DhcpClientMessage &req); + DhcpServerMessage *doInform(DhcpClientMessage &req); + + DhcpServerMessage *doDecline(DhcpClientMessage &req); + DhcpServerMessage *doRelease(DhcpClientMessage &req); + + DhcpServerMessage *createMessage(int type, DhcpClientMessage &req); + + void loadLeases(); + void saveLeases(); +}; + +#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..dfafc784 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Db.cpp @@ -0,0 +1,747 @@ +/* $Id: Db.cpp $ */ +/** @file + * DHCP server - address database + */ + +/* + * 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 <iprt/errcore.h> +#include <iprt/stream.h> + +#include "Db.h" + + +Db::Db() + : m_pConfig(NULL) +{ + return; +} + + +Db::~Db() +{ + /** @todo free bindings */ +} + + +int Db::init(const Config *pConfig) +{ + Binding::registerFormat(); + + m_pConfig = pConfig; + + m_pool.init(pConfig->getIPv4PoolFirst(), + pConfig->getIPv4PoolLast()); + + return VINF_SUCCESS; +} + + +bool Binding::g_fFormatRegistered = false; + + +void Binding::registerFormat() +{ + if (g_fFormatRegistered) + return; + + int rc = RTStrFormatTypeRegister("binding", rtStrFormat, NULL); + AssertRC(rc); + + g_fFormatRegistered = true; +} + + +DECLCALLBACK(size_t) +Binding::rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + const Binding *b = static_cast<const Binding *>(pvValue); + size_t cb = 0; + + AssertReturn(strcmp(pszType, "binding") == 0, 0); + RT_NOREF(pszType); + + RT_NOREF(cchWidth, cchPrecision, fFlags); + RT_NOREF(pvUser); + + if (b == NULL) + { + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "<NULL>"); + } + + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "%RTnaipv4", b->m_addr.u); + + if (b->m_state == Binding::FREE) + { + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + " free"); + } + else + { + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + " to %R[id], %s, valid from ", + &b->m_id, b->stateName()); + + TimeStamp tsIssued = b->issued(); + cb += tsIssued.absStrFormat(pfnOutput, pvArgOutput); + + cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + " for %ds until ", + b->leaseTime()); + + TimeStamp tsValid = b->issued(); + tsValid.addSeconds(b->leaseTime()); + cb += tsValid.absStrFormat(pfnOutput, pvArgOutput); + } + + return cb; +} + +const char *Binding::stateName() const +{ + switch (m_state) { + case FREE: + return "free"; + case RELEASED: + return "released"; + case EXPIRED: + return "expired"; + case OFFERED: + return "offered"; + case ACKED: + return "acked"; + default: + return "released"; + } +} + + +Binding &Binding::setState(const char *pszStateName) +{ + 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 + m_state = Binding::RELEASED; + + return *this; +} + + +bool Binding::expire(TimeStamp deadline) +{ + if (m_state <= Binding::EXPIRED) + return false; + + TimeStamp t = m_issued; + t.addSeconds(m_secLease); + + if (t < deadline) + { + if (m_state == Binding::OFFERED) + setState(Binding::FREE); + else + setState(Binding::EXPIRED); + } + return true; +} + + +int Binding::toXML(xml::ElementNode *ndParent) const +{ + int rc; + + /* + * Lease + */ + xml::ElementNode *ndLease = ndParent->createChild("Lease"); + if (ndLease == NULL) + return VERR_GENERAL_FAILURE; + + /* XXX: arrange for lease to get deleted if anything below fails */ + + + ndLease->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]; + rc = RTStrPrintHexBytes(pszId, cbStrId, + &m_id.id().value().front(), m_id.id().value().size(), + 0); + ndLease->setAttribute("id", pszId); + delete[] pszId; + } + + /* unused but we need it to keep the old code happy */ + ndLease->setAttribute("network", "0.0.0.0"); + + ndLease->setAttribute("state", stateName()); + + + /* + * Lease/Address + */ + xml::ElementNode *ndAddr = ndLease->createChild("Address"); + ndAddr->setAttribute("value", RTCStringFmt("%RTnaipv4", m_addr.u)); + + + /* + * Lease/Time + */ + xml::ElementNode *ndTime = ndLease->createChild("Time"); + ndTime->setAttribute("issued", m_issued.getAbsSeconds()); + ndTime->setAttribute("expiration", m_secLease); + + return VINF_SUCCESS; +} + + +Binding *Binding::fromXML(const xml::ElementNode *ndLease) +{ + int rc; + + /* Lease/@network seems to always have bogus value, ignore it. */ + + /* + * Lease/@mac + */ + RTCString strMac; + bool fHasMac = ndLease->getAttributeValue("mac", &strMac); + if (!fHasMac) + return NULL; + + RTMAC mac; + rc = RTNetStrToMacAddr(strMac.c_str(), &mac); + if (RT_FAILURE(rc)) + return NULL; + + OptClientId id; + RTCString strId; + bool fHasId = ndLease->getAttributeValue("id", &strId); + if (fHasId) + { + /* + * Decode from "de:ad:be:ef". + * XXX: RTStrConvertHexBytes() doesn't grok colons + */ + size_t cbBytes = strId.length() / 2; + uint8_t *pBytes = new uint8_t[cbBytes]; + rc = RTStrConvertHexBytes(strId.c_str(), pBytes, cbBytes, 0); + if (RT_SUCCESS(rc)) + { + std::vector<uint8_t> rawopt(pBytes, pBytes + cbBytes); + id = OptClientId(rawopt); + } + delete[] pBytes; + } + + /* + * Lease/@state - not present in old leases file. We will try to + * infer from lease time below. + */ + RTCString strState; + bool fHasState = ndLease->getAttributeValue("state", &strState); + + /* + * Lease/Address + */ + const xml::ElementNode *ndAddress = ndLease->findChildElement("Address"); + if (ndAddress == NULL) + return NULL; + + /* + * Lease/Address/@value + */ + RTCString strAddress; + bool fHasValue = ndAddress->getAttributeValue("value", &strAddress); + if (!fHasValue) + return NULL; + + RTNETADDRIPV4 addr; + rc = RTNetStrToIPv4Addr(strAddress.c_str(), &addr); + if (RT_FAILURE(rc)) + return NULL; + + /* + * Lease/Time + */ + const xml::ElementNode *ndTime = ndLease->findChildElement("Time"); + if (ndTime == NULL) + return NULL; + + /* + * Lease/Time/@issued + */ + int64_t issued; + bool fHasIssued = ndTime->getAttributeValue("issued", &issued); + if (!fHasIssued) + return NULL; + + /* + * Lease/Time/@expiration + */ + uint32_t duration; + bool fHasExpiration = ndTime->getAttributeValue("expiration", &duration); + if (!fHasExpiration) + return NULL; + + std::unique_ptr<Binding> b(new Binding(addr)); + b->m_id = ClientId(mac, id); + + if (fHasState) + { + b->m_issued = TimeStamp::absSeconds(issued); + b->m_secLease = duration; + b->setState(strState.c_str()); + } + else + { /* XXX: old code wrote timestamps instead of absolute time. */ + /* pretend that lease has just ended */ + TimeStamp fakeIssued = TimeStamp::now(); + fakeIssued.subSeconds(duration); + b->m_issued = fakeIssued; + b->m_secLease = duration; + b->m_state = Binding::EXPIRED; + } + + return b.release(); +} + + +void Db::expire() +{ + const TimeStamp now = TimeStamp::now(); + + for (bindings_t::iterator it = m_bindings.begin(); + it != m_bindings.end(); ++it) + { + Binding *b = *it; + b->expire(now); + } +} + + +Binding *Db::createBinding(const ClientId &id) +{ + RTNETADDRIPV4 addr = m_pool.allocate(); + if (addr.u == 0) + return NULL; + + Binding *b = new Binding(addr, id); + m_bindings.push_front(b); + return b; +} + + +Binding *Db::createBinding(RTNETADDRIPV4 addr, const ClientId &id) +{ + bool fAvailable = m_pool.allocate(addr); + if (!fAvailable) + { + /* + * XXX: this should not happen. If the address is from the + * pool, which we have verified before, then either it's in + * the free pool or there's an binding (possibly free) for it. + */ + return NULL; + } + + Binding *b = new Binding(addr, id); + m_bindings.push_front(b); + return b; +} + + +Binding *Db::allocateAddress(const ClientId &id, RTNETADDRIPV4 addr) +{ + Assert(addr.u == 0 || addressBelongs(addr)); + + Binding *addrBinding = NULL; + Binding *freeBinding = NULL; + Binding *reuseBinding = NULL; + + if (addr.u != 0) + LogDHCP(("> allocateAddress %RTnaipv4 to client %R[id]\n", addr.u, &id)); + else + LogDHCP(("> allocateAddress to client %R[id]\n", &id)); + + /* + * Allocate existing address if client has one. Ignore requested + * address in that case. While here, look for free addresses and + * addresses that can be reused. + */ + const TimeStamp now = TimeStamp::now(); + for (bindings_t::iterator it = m_bindings.begin(); + it != m_bindings.end(); ++it) + { + Binding *b = *it; + b->expire(now); + + /* + * We've already seen this client, give it its old binding. + */ + if (b->m_id == id) + { + LogDHCP(("> ... found existing binding %R[binding]\n", b)); + return b; + } + + if (addr.u != 0 && b->m_addr.u == addr.u) + { + Assert(addrBinding == NULL); + addrBinding = b; + LogDHCP(("> .... noted existing binding %R[binding]\n", addrBinding)); + } + + /* if we haven't found a free binding yet, keep looking */ + if (freeBinding == NULL) + { + if (b->m_state == Binding::FREE) + { + freeBinding = b; + LogDHCP(("> .... noted free binding %R[binding]\n", freeBinding)); + continue; + } + + /* still no free binding, can this one be reused? */ + if (b->m_state == Binding::RELEASED) + { + if ( reuseBinding == NULL + /* released binding is better than an expired one */ + || reuseBinding->m_state == Binding::EXPIRED) + { + reuseBinding = b; + LogDHCP(("> .... noted released binding %R[binding]\n", reuseBinding)); + } + } + else if (b->m_state == Binding::EXPIRED) + { + if ( reuseBinding == NULL + /* long expired binding is bettern than a recent one */ + /* || (reuseBinding->m_state == Binding::EXPIRED && b->olderThan(reuseBinding)) */) + { + reuseBinding = b; + LogDHCP(("> .... noted expired binding %R[binding]\n", reuseBinding)); + } + } + } + } + + /* + * Allocate requested address if we can. + */ + if (addr.u != 0) + { + if (addrBinding == NULL) + { + addrBinding = createBinding(addr, id); + Assert(addrBinding != NULL); + LogDHCP(("> .... creating new binding for this address %R[binding]\n", + addrBinding)); + return addrBinding; + } + + if (addrBinding->m_state <= Binding::EXPIRED) /* not in use */ + { + LogDHCP(("> .... reusing %s binding for this address\n", + addrBinding->stateName())); + addrBinding->giveTo(id); + return addrBinding; + } + else + { + LogDHCP(("> .... cannot reuse %s binding for this address\n", + addrBinding->stateName())); + } + } + + /* + * Allocate new (or reuse). + */ + Binding *idBinding = NULL; + if (freeBinding != NULL) + { + idBinding = freeBinding; + LogDHCP(("> .... reusing free binding\n")); + } + else + { + idBinding = createBinding(); + if (idBinding != NULL) + { + LogDHCP(("> .... creating new binding\n")); + } + else + { + idBinding = reuseBinding; + LogDHCP(("> .... reusing %s binding %R[binding]\n", + reuseBinding->stateName(), reuseBinding)); + } + } + + if (idBinding == NULL) + { + LogDHCP(("> .... failed to allocate binding\n")); + return NULL; + } + + idBinding->giveTo(id); + LogDHCP(("> .... allocated %R[binding]\n", idBinding)); + + return idBinding; +} + + + +Binding *Db::allocateBinding(const DhcpClientMessage &req) +{ + /** @todo XXX: handle fixed address assignments */ + OptRequestedAddress reqAddr(req); + if (reqAddr.present() && !addressBelongs(reqAddr.value())) + { + if (req.messageType() == RTNET_DHCP_MT_DISCOVER) + { + LogDHCP(("DISCOVER: ignoring invalid requested address\n")); + reqAddr = OptRequestedAddress(); + } + else + { + LogDHCP(("rejecting invalid requested address\n")); + return NULL; + } + } + + const ClientId &id(req.clientId()); + + Binding *b = allocateAddress(id, reqAddr.value()); + if (b == NULL) + return NULL; + + Assert(b->id() == id); + + /* + * XXX: handle requests for specific lease time! + * XXX: old lease might not have expired yet? + */ + // OptLeaseTime reqLeaseTime(req); + b->setLeaseTime(1200); + return b; +} + + +int Db::addBinding(Binding *newb) +{ + if (!addressBelongs(newb->m_addr)) + { + LogDHCP(("Binding for out of range address %RTnaipv4 ignored\n", + newb->m_addr.u)); + return VERR_INVALID_PARAMETER; + } + + for (bindings_t::iterator it = m_bindings.begin(); + it != m_bindings.end(); ++it) + { + Binding *b = *it; + + if (newb->m_addr.u == b->m_addr.u) + { + LogDHCP(("> ADD: %R[binding]\n", newb)); + LogDHCP(("> .... duplicate ip: %R[binding]\n", b)); + return VERR_INVALID_PARAMETER; + } + + if (newb->m_id == b->m_id) + { + LogDHCP(("> ADD: %R[binding]\n", newb)); + LogDHCP(("> .... duplicate id: %R[binding]\n", b)); + return VERR_INVALID_PARAMETER; + } + } + + bool ok = m_pool.allocate(newb->m_addr); + if (!ok) + { + LogDHCP(("> ADD: failed to claim IP %R[binding]\n", newb)); + return VERR_INVALID_PARAMETER; + } + + m_bindings.push_back(newb); + return VINF_SUCCESS; +} + + +void Db::cancelOffer(const DhcpClientMessage &req) +{ + const OptRequestedAddress reqAddr(req); + if (!reqAddr.present()) + return; + + const RTNETADDRIPV4 addr = reqAddr.value(); + const ClientId &id(req.clientId()); + + for (bindings_t::iterator it = m_bindings.begin(); + it != m_bindings.end(); ++it) + { + Binding *b = *it; + + if (b->addr().u == addr.u && b->id() == id) + { + if (b->state() == Binding::OFFERED) + { + b->setLeaseTime(0); + b->setState(Binding::RELEASED); + } + return; + } + } +} + + +bool Db::releaseBinding(const DhcpClientMessage &req) +{ + const RTNETADDRIPV4 addr = req.ciaddr(); + const ClientId &id(req.clientId()); + + for (bindings_t::iterator it = m_bindings.begin(); + it != m_bindings.end(); ++it) + { + Binding *b = *it; + + if (b->addr().u == addr.u && b->id() == id) + { + b->setState(Binding::RELEASED); + return true; + } + } + + return false; +} + + +int Db::writeLeases(const std::string &strFileName) const +{ + LogDHCP(("writing leases to %s\n", strFileName.c_str())); + + xml::Document doc; + + xml::ElementNode *root = doc.createRootElement("Leases"); + if (root == NULL) + return VERR_INTERNAL_ERROR; + + root->setAttribute("version", "1.0"); + + for (bindings_t::const_iterator it = m_bindings.begin(); + it != m_bindings.end(); ++it) + { + const Binding *b = *it; + b->toXML(root); + } + + try { + xml::XmlFileWriter writer(doc); + writer.write(strFileName.c_str(), true); + } + catch (const xml::EIPRTFailure &e) + { + LogDHCP(("%s\n", e.what())); + return e.rc(); + } + catch (const RTCError &e) + { + LogDHCP(("%s\n", e.what())); + return VERR_GENERAL_FAILURE; + } + catch (...) + { + LogDHCP(("Unknown exception while writing '%s'\n", + strFileName.c_str())); + return VERR_GENERAL_FAILURE; + } + + return VINF_SUCCESS; +} + + +int Db::loadLeases(const std::string &strFileName) +{ + LogDHCP(("loading leases from %s\n", strFileName.c_str())); + + xml::Document doc; + try + { + xml::XmlFileParser parser; + parser.read(strFileName.c_str(), doc); + } + catch (const xml::EIPRTFailure &e) + { + LogDHCP(("%s\n", e.what())); + return e.rc(); + } + catch (const RTCError &e) + { + LogDHCP(("%s\n", e.what())); + return VERR_GENERAL_FAILURE; + } + catch (...) + { + LogDHCP(("Unknown exception while reading and parsing '%s'\n", + strFileName.c_str())); + return VERR_GENERAL_FAILURE; + } + + xml::ElementNode *ndRoot = doc.getRootElement(); + if (ndRoot == NULL || !ndRoot->nameEquals("Leases")) + { + return VERR_NOT_FOUND; + } + + xml::NodesLoop it(*ndRoot); + const xml::ElementNode *node; + while ((node = it.forAllNodes()) != NULL) + { + if (!node->nameEquals("Lease")) + continue; + + loadLease(node); + } + + return VINF_SUCCESS; +} + + +void Db::loadLease(const xml::ElementNode *ndLease) +{ + Binding *b = Binding::fromXML(ndLease); + bool expired = b->expire(); + + if (!expired) + LogDHCP(("> LOAD: lease %R[binding]\n", b)); + else + LogDHCP(("> LOAD: EXPIRED lease %R[binding]\n", b)); + + addBinding(b); +} diff --git a/src/VBox/NetworkServices/Dhcpd/Db.h b/src/VBox/NetworkServices/Dhcpd/Db.h new file mode 100644 index 00000000..793101a7 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Db.h @@ -0,0 +1,162 @@ +/* $Id: Db.h $ */ +/** @file + * DHCP server - address database + */ + +/* + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_Db_h +#define VBOX_INCLUDED_SRC_Dhcpd_Db_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/net.h> + +#include <iprt/cpp/xml.h> + +#include <list> + +#include "Defs.h" +#include "TimeStamp.h" +#include "ClientId.h" +#include "IPv4Pool.h" +#include "Config.h" +#include "DhcpMessage.h" + + +class Binding +{ + friend class Db; + +public: + enum State { FREE, RELEASED, EXPIRED, OFFERED, ACKED }; + +private: + const RTNETADDRIPV4 m_addr; + State m_state; + ClientId m_id; + TimeStamp m_issued; + uint32_t m_secLease; + +public: + Binding(); + Binding(const Binding &); + + explicit Binding(RTNETADDRIPV4 addrParam) + : m_addr(addrParam), m_state(FREE), + m_issued(), m_secLease() {} + + Binding(RTNETADDRIPV4 addrParam, const ClientId &idParam) + : m_addr(addrParam), m_state(FREE), m_id(idParam), + m_issued(), m_secLease() {} + + + RTNETADDRIPV4 addr() const { return m_addr; } + + State state() const { return m_state; } + const char *stateName() const; + + const ClientId &id() const { return m_id; } + + uint32_t leaseTime() const { return m_secLease; } + TimeStamp issued() const { return m_issued; } + + Binding &setState(State stateParam) + { + m_state = stateParam; + return *this; + } + + Binding &setState(const char *pszStateName); + + Binding &setLeaseTime(uint32_t secLease) + { + m_issued = TimeStamp::now(); + m_secLease = secLease; + return *this; + } + + Binding &giveTo(const ClientId &idParam) + { + m_id = idParam; + m_state = FREE; + return *this; + } + + void free() + { + m_id = ClientId(); + m_state = FREE; + } + + bool expire(TimeStamp deadline); + bool expire() { return expire(TimeStamp::now()); } + + static Binding *fromXML(const xml::ElementNode *ndLease); + int toXML(xml::ElementNode *ndParent) const; + +public: + static void registerFormat(); /* %R[binding] */ + +private: + static bool g_fFormatRegistered; + static DECLCALLBACK(size_t) rtStrFormat( + PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser); +}; + + +class Db +{ +private: + typedef std::list<Binding *> bindings_t; + + const Config *m_pConfig; + bindings_t m_bindings; + IPv4Pool m_pool; + +public: + Db(); + ~Db(); + + int init(const Config *pConfig); + + bool addressBelongs(RTNETADDRIPV4 addr) const { return m_pool.contains(addr); } + + Binding *allocateBinding(const DhcpClientMessage &req); + bool releaseBinding(const DhcpClientMessage &req); + + void cancelOffer(const DhcpClientMessage &req); + + void expire(); + +public: + int loadLeases(const std::string &strFileName); + void loadLease(const xml::ElementNode *ndLease); + + int writeLeases(const std::string &strFileName) const; + +private: + Binding *createBinding(const ClientId &id = ClientId()); + Binding *createBinding(RTNETADDRIPV4 addr, const ClientId &id = ClientId()); + + Binding *allocateAddress(const ClientId &id, RTNETADDRIPV4 addr); + + /* add binding e.g. from the leases file */ + int addBinding(Binding *b); +}; + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_Db_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/Defs.h b/src/VBox/NetworkServices/Dhcpd/Defs.h new file mode 100644 index 00000000..ee1e0b95 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Defs.h @@ -0,0 +1,62 @@ +/* $Id: Defs.h $ */ +/** @file + * DHCP server - common definitions + */ + +/* + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_Defs_h +#define VBOX_INCLUDED_SRC_Dhcpd_Defs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/stdint.h> +#include <iprt/string.h> +#include <VBox/log.h> + +#include <map> +#include <vector> + +#if __cplusplus >= 199711 +#include <memory> +using std::shared_ptr; +#else +#include <tr1/memory> +using std::tr1::shared_ptr; +#endif + +typedef std::vector<uint8_t> octets_t; + +typedef std::map<uint8_t, octets_t> rawopts_t; + +class DhcpOption; +typedef std::map<uint8_t, std::shared_ptr<DhcpOption> > optmap_t; + +inline bool operator==(const RTMAC &l, const RTMAC &r) +{ + return memcmp(&l, &r, sizeof(RTMAC)) == 0; +} + +inline bool operator<(const RTMAC &l, const RTMAC &r) +{ + return memcmp(&l, &r, sizeof(RTMAC)) < 0; +} + +#if 1 +#define LogDHCP LogRel +#else +#define LogDHCP(args) RTPrintf args +#endif + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_Defs_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp new file mode 100644 index 00000000..2165959f --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp @@ -0,0 +1,412 @@ +/* $Id: DhcpMessage.cpp $ */ +/** @file + * DHCP Message and its de/serialization. + */ + +/* + * 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 "DhcpMessage.h" +#include "DhcpOptions.h" + +#include <iprt/string.h> +#include <iprt/stream.h> + + + +DhcpMessage::DhcpMessage() + : m_xid(0), m_flags(0), + m_ciaddr(), m_yiaddr(), m_siaddr(), m_giaddr(), + m_sname(), m_file(), + m_optMessageType() +{ +} + + +/* static */ +DhcpClientMessage *DhcpClientMessage::parse(bool broadcasted, const void *buf, size_t buflen) +{ + if (buflen < RT_OFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts)) + { + RTPrintf("%s: %zu bytes datagram is too short\n", __FUNCTION__, buflen); + return NULL; + } + + PCRTNETBOOTP bp = (PCRTNETBOOTP)buf; + + if (bp->bp_op != RTNETBOOTP_OP_REQUEST) + { + RTPrintf("%s: bad opcode: %d\n", __FUNCTION__, bp->bp_op); + return NULL; + } + + if (bp->bp_htype != RTNET_ARP_ETHER) + { + RTPrintf("%s: unsupported htype %d\n", __FUNCTION__, bp->bp_htype); + return NULL; + } + + if (bp->bp_hlen != sizeof(RTMAC)) + { + RTPrintf("%s: unexpected hlen %d\n", __FUNCTION__, bp->bp_hlen); + return NULL; + } + + if ( (bp->bp_chaddr.Mac.au8[0] & 0x01) != 0 + && (bp->bp_flags & RTNET_DHCP_FLAG_BROADCAST) == 0) + { + RTPrintf("%s: multicast chaddr %RTmac without broadcast flag\n", + __FUNCTION__, &bp->bp_chaddr.Mac); + } + + /* we don't want to deal with forwarding */ + if (bp->bp_giaddr.u != 0) + { + RTPrintf("%s: giaddr %RTnaipv4\n", __FUNCTION__, bp->bp_giaddr.u); + return NULL; + } + + if (bp->bp_hops != 0) + { + RTPrintf("%s: non-zero hops %d\n", __FUNCTION__, bp->bp_hops); + return NULL; + } + + std::unique_ptr<DhcpClientMessage> msg(new DhcpClientMessage()); + + msg->m_broadcasted = broadcasted; + + msg->m_xid = bp->bp_xid; + msg->m_flags = bp->bp_flags; + + msg->m_mac = bp->bp_chaddr.Mac; + + msg->m_ciaddr = bp->bp_ciaddr; + msg->m_yiaddr = bp->bp_yiaddr; + msg->m_siaddr = bp->bp_siaddr; + msg->m_giaddr = bp->bp_giaddr; + + if (bp->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32_C(RTNET_DHCP_COOKIE)) + { + RTPrintf("bad cookie\n"); + return NULL; + } + + int overload; + overload = msg->parseOptions(&bp->bp_vend.Dhcp.dhcp_opts, + buflen - RT_OFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts)); + if (overload < 0) + return NULL; + + /* "The 'file' field MUST be interpreted next ..." */ + if (overload & DHCP_OPTION_OVERLOAD_FILE) { + int status = msg->parseOptions(bp->bp_file, sizeof(bp->bp_file)); + if (status != 0) + return NULL; + } + else if (bp->bp_file[0] != '\0') + { + /* must be zero terminated, ignore if not */ + const char *pszFile = (const char *)bp->bp_file; + size_t len = RTStrNLen(pszFile, sizeof(bp->bp_file)); + if (len < sizeof(bp->bp_file)) + msg->m_file.assign(pszFile, len); + } + + /* "... followed by the 'sname' field." */ + if (overload & DHCP_OPTION_OVERLOAD_SNAME) { + int status = msg->parseOptions(bp->bp_sname, sizeof(bp->bp_sname)); + if (status != 0) /* NB: this includes "nested" Option Overload */ + return NULL; + } + else if (bp->bp_sname[0] != '\0') + { + /* must be zero terminated, ignore if not */ + const char *pszSName = (const char *)bp->bp_sname; + size_t len = RTStrNLen(pszSName, sizeof(bp->bp_sname)); + if (len < sizeof(bp->bp_sname)) + msg->m_sname.assign(pszSName, len); + } + + msg->m_optMessageType = OptMessageType(*msg); + if (!msg->m_optMessageType.present()) + return NULL; + + msg->m_id = ClientId(msg->m_mac, OptClientId(*msg)); + + return msg.release(); +} + + +int DhcpClientMessage::parseOptions(const void *buf, size_t buflen) +{ + uint8_t opt, optlen; + const uint8_t *data; + int overload; + + overload = 0; + + data = static_cast<const uint8_t *>(buf); + while (buflen > 0) { + opt = *data++; + --buflen; + + if (opt == RTNET_DHCP_OPT_PAD) { + continue; + } + + if (opt == RTNET_DHCP_OPT_END) { + break; + } + + if (buflen == 0) { + RTPrintf("option %d has no length field\n", opt); + return -1; + } + + optlen = *data++; + --buflen; + + if (optlen > buflen) { + RTPrintf("option %d truncated (length %d, but only %lu bytes left)\n", + opt, optlen, (unsigned long)buflen); + return -1; + } + +#if 0 + rawopts_t::const_iterator it(m_optmap.find(opt)); + if (it != m_optmap.cend()) + return -1; +#endif + if (opt == RTNET_DHCP_OPT_OPTION_OVERLOAD) { + if (optlen != 1) { + RTPrintf("Overload Option (option %d) has invalid length %d\n", + opt, optlen); + return -1; + } + + overload = *data; + + if ((overload & ~DHCP_OPTION_OVERLOAD_MASK) != 0) { + RTPrintf("Overload Option (option %d) has invalid value 0x%x\n", + opt, overload); + return -1; + } + } + else + { + m_rawopts.insert(std::make_pair(opt, octets_t(data, data + optlen))); + } + + data += optlen; + buflen -= optlen; + } + + return overload; +} + + +void DhcpClientMessage::dump() const +{ + switch (m_optMessageType.value()) + { + case RTNET_DHCP_MT_DISCOVER: + LogDHCP(("DISCOVER")); + break; + + case RTNET_DHCP_MT_REQUEST: + LogDHCP(("REQUEST")); + break; + + case RTNET_DHCP_MT_INFORM: + LogDHCP(("INFORM")); + break; + + case RTNET_DHCP_MT_DECLINE: + LogDHCP(("DECLINE")); + break; + + case RTNET_DHCP_MT_RELEASE: + LogDHCP(("RELEASE")); + break; + + default: + LogDHCP(("<Unknown Mesage Type %d>", m_optMessageType.value())); + break; + } + + if (OptRapidCommit(*this).present()) + LogDHCP((" (rapid commit)")); + + + const OptServerId sid(*this); + if (sid.present()) + LogDHCP((" for server %RTnaipv4", sid.value().u)); + + LogDHCP((" xid 0x%08x", m_xid)); + LogDHCP((" chaddr %RTmac\n", &m_mac)); + + const OptClientId cid(*this); + if (cid.present()) { + if (cid.value().size() > 0) + LogDHCP((" client id: %.*Rhxs\n", cid.value().size(), &cid.value().front())); + else + LogDHCP((" client id: <empty>\n")); + } + + LogDHCP((" ciaddr %RTnaipv4", m_ciaddr.u)); + if (m_yiaddr.u != 0) + LogDHCP((" yiaddr %RTnaipv4", m_yiaddr.u)); + if (m_siaddr.u != 0) + LogDHCP((" siaddr %RTnaipv4", m_siaddr.u)); + if (m_giaddr.u != 0) + LogDHCP((" giaddr %RTnaipv4", m_giaddr.u)); + LogDHCP(("%s\n", broadcast() ? "broadcast" : "")); + + + const OptRequestedAddress reqAddr(*this); + if (reqAddr.present()) + LogDHCP((" requested address %RTnaipv4", reqAddr.value().u)); + const OptLeaseTime reqLeaseTime(*this); + if (reqLeaseTime.present()) + LogDHCP((" requested lease time %d", reqAddr.value())); + if (reqAddr.present() || reqLeaseTime.present()) + LogDHCP(("\n")); + + const OptParameterRequest params(*this); + if (params.present()) + { + LogDHCP((" params {")); + typedef OptParameterRequest::value_t::const_iterator it_t; + for (it_t it = params.value().begin(); it != params.value().end(); ++it) + LogDHCP((" %d", *it)); + LogDHCP((" }\n")); + } + + bool fHeader = true; + for (rawopts_t::const_iterator it = m_rawopts.begin(); + it != m_rawopts.end(); ++it) + { + const uint8_t optcode = (*it).first; + switch (optcode) { + case OptMessageType::optcode: /* FALLTHROUGH */ + case OptClientId::optcode: /* FALLTHROUGH */ + case OptRequestedAddress::optcode: /* FALLTHROUGH */ + case OptLeaseTime::optcode: /* FALLTHROUGH */ + case OptParameterRequest::optcode: /* FALLTHROUGH */ + case OptRapidCommit::optcode: + break; + + default: + if (fHeader) + { + LogDHCP((" other options:")); + fHeader = false; + } + LogDHCP((" %d", optcode)); + break; + } + } + if (!fHeader) + LogDHCP(("\n")); +} + + +DhcpServerMessage::DhcpServerMessage(const DhcpClientMessage &req, + uint8_t messageTypeParam, RTNETADDRIPV4 serverAddr) + : DhcpMessage(), + m_optServerId(serverAddr) +{ + m_dst.u = 0xffffffff; /* broadcast */ + + m_optMessageType = OptMessageType(messageTypeParam); + + /* copy values from the request (cf. RFC2131 Table 3) */ + m_xid = req.xid(); + m_flags = req.flags(); + m_giaddr = req.giaddr(); + m_mac = req.mac(); + + if (req.messageType() == RTNET_DHCP_MT_REQUEST) + m_ciaddr = req.ciaddr(); +} + + +void DhcpServerMessage::maybeUnicast(const DhcpClientMessage &req) +{ + if (!req.broadcast() && req.ciaddr().u != 0) + setDst(req.ciaddr()); +} + + +void DhcpServerMessage::addOption(DhcpOption *opt) +{ + m_optmap << opt; +} + + +void DhcpServerMessage::addOptions(const optmap_t &optmap) +{ + for (optmap_t::const_iterator it ( optmap.begin() ); + it != optmap.end(); ++it) + { + m_optmap << it->second; + } +} + + +int DhcpServerMessage::encode(octets_t &data) +{ + /* + * Header, including DHCP cookie. + */ + RTNETBOOTP bp; + RT_ZERO(bp); + + bp.bp_op = RTNETBOOTP_OP_REPLY; + bp.bp_htype = RTNET_ARP_ETHER; + bp.bp_hlen = sizeof(RTMAC); + + bp.bp_xid = m_xid; + + bp.bp_ciaddr = m_ciaddr; + bp.bp_yiaddr = m_yiaddr; + bp.bp_siaddr = m_siaddr; + bp.bp_giaddr = m_giaddr; + + bp.bp_chaddr.Mac = m_mac; + + bp.bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32_C(RTNET_DHCP_COOKIE); + + data.insert(data.end(), (uint8_t *)&bp, (uint8_t *)&bp.bp_vend.Dhcp.dhcp_opts); + + /* + * Options + */ + data << m_optServerId + << m_optMessageType; + + for (optmap_t::const_iterator it ( m_optmap.begin() ); + it != m_optmap.end(); ++it) + { + RTPrintf("encoding option %d\n", it->first); + DhcpOption &opt = *it->second; + data << opt; + } + + data << OptEnd(); + + if (data.size() < 548) /* XXX */ + data.resize(548); + + return VINF_SUCCESS; +} diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpMessage.h b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.h new file mode 100644 index 00000000..303f352c --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.h @@ -0,0 +1,138 @@ +/* $Id: DhcpMessage.h $ */ +/** @file + * DHCP Message and its de/serialization. + */ + +/* + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_DhcpMessage_h +#define VBOX_INCLUDED_SRC_Dhcpd_DhcpMessage_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "Defs.h" +#include <iprt/net.h> +#include <string> +#include "ClientId.h" +#include "DhcpOptions.h" + + +/* move to <iptr/net.h>? */ +#define DHCP_OPTION_OVERLOAD_MASK 0x3 +#define DHCP_OPTION_OVERLOAD_FILE 0x1 +#define DHCP_OPTION_OVERLOAD_SNAME 0x2 + + +class DhcpMessage +{ +protected: + uint32_t m_xid; + uint16_t m_flags; + + RTMAC m_mac; + + RTNETADDRIPV4 m_ciaddr; + RTNETADDRIPV4 m_yiaddr; + RTNETADDRIPV4 m_siaddr; + RTNETADDRIPV4 m_giaddr; + + std::string m_sname; + std::string m_file; + + OptMessageType m_optMessageType; + +public: + DhcpMessage(); + + + uint32_t xid() const { return m_xid; } + + uint16_t flags() const { return m_flags; } + bool broadcast() const { return (m_flags & RTNET_DHCP_FLAG_BROADCAST) != 0; } + + const RTMAC &mac() const { return m_mac; } + + RTNETADDRIPV4 ciaddr() const { return m_ciaddr; } + RTNETADDRIPV4 yiaddr() const { return m_yiaddr; } + RTNETADDRIPV4 siaddr() const { return m_siaddr; } + RTNETADDRIPV4 giaddr() const { return m_giaddr; } + + void setCiaddr(RTNETADDRIPV4 addr) { m_ciaddr = addr; } + void setYiaddr(RTNETADDRIPV4 addr) { m_yiaddr = addr; } + void setSiaddr(RTNETADDRIPV4 addr) { m_siaddr = addr; } + void setGiaddr(RTNETADDRIPV4 addr) { m_giaddr = addr; } + + uint8_t messageType() const + { + Assert(m_optMessageType.present()); + return m_optMessageType.value(); + } +}; + + +class DhcpClientMessage + : public DhcpMessage +{ +protected: + rawopts_t m_rawopts; + ClientId m_id; + bool m_broadcasted; + +public: + static DhcpClientMessage *parse(bool broadcasted, const void *buf, size_t buflen); + + bool broadcasted() const { return m_broadcasted; } + + const rawopts_t &rawopts() const { return m_rawopts; } + const ClientId &clientId() const { return m_id; } + + void dump() const; + +protected: + int parseOptions(const void *buf, size_t buflen); +}; + + + +class DhcpServerMessage + : public DhcpMessage +{ +protected: + RTNETADDRIPV4 m_dst; + + OptServerId m_optServerId; + + optmap_t m_optmap; + +public: + DhcpServerMessage(const DhcpClientMessage &req, + uint8_t messageType, RTNETADDRIPV4 serverAddr); + + RTNETADDRIPV4 dst() const { return m_dst; } + void setDst(RTNETADDRIPV4 aDst) { m_dst = aDst; } + + void maybeUnicast(const DhcpClientMessage &req); + + void addOption(DhcpOption *opt); + void addOption(const DhcpOption &opt) + { + addOption(opt.clone()); + } + + void addOptions(const optmap_t &optmap); + + int encode(octets_t &data); +}; + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_DhcpMessage_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp new file mode 100644 index 00000000..03820569 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp @@ -0,0 +1,242 @@ +/* $Id: DhcpOptions.cpp $ */ +/** @file + * DHCP server - DHCP options + */ + +/* + * 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 "DhcpOptions.h" +#include "DhcpMessage.h" + + +optmap_t &operator<<(optmap_t &optmap, DhcpOption *option) +{ + if (option == NULL) + return optmap; + + if (option->present()) + optmap[option->optcode()] = std::shared_ptr<DhcpOption>(option); + else + optmap.erase(option->optcode()); + + return optmap; +} + + +optmap_t &operator<<(optmap_t &optmap, const std::shared_ptr<DhcpOption> &option) +{ + if (!option) + return optmap; + + if (option->present()) + optmap[option->optcode()] = option; + else + optmap.erase(option->optcode()); + + return optmap; +} + + +int DhcpOption::encode(octets_t &dst) const +{ + if (!m_fPresent) + return VERR_INVALID_STATE; + + size_t cbOrig = dst.size(); + + append(dst, m_OptCode); + appendLength(dst, 0); /* placeholder */ + + ssize_t cbValue = encodeValue(dst); + if (cbValue < 0 || UINT8_MAX <= cbValue) + { + dst.resize(cbOrig); /* undo */ + return VERR_INVALID_PARAMETER; + } + + dst[cbOrig+1] = cbValue; + return VINF_SUCCESS; +} + + +/* static */ +const octets_t *DhcpOption::findOption(const rawopts_t &aOptMap, uint8_t aOptCode) +{ + rawopts_t::const_iterator it(aOptMap.find(aOptCode)); + if (it == aOptMap.end()) + return NULL; + + return &it->second; +} + + +int DhcpOption::decode(const rawopts_t &map) +{ + const octets_t *rawopt = DhcpOption::findOption(map, m_OptCode); + if (rawopt == NULL) + return VERR_NOT_FOUND; + + int rc = decodeValue(*rawopt, rawopt->size()); + if (RT_FAILURE(rc)) + return VERR_INVALID_PARAMETER; + + return VINF_SUCCESS; +} + + +int DhcpOption::decode(const DhcpClientMessage &req) +{ + return decode(req.rawopts()); +} + + +int DhcpOption::parse1(uint8_t &aValue, const char *pcszValue) +{ + int rc = RTStrToUInt8Full(RTStrStripL(pcszValue), 10, &aValue); + + if (rc == VERR_TRAILING_SPACES) + rc = VINF_SUCCESS; + return rc; +} + + +int DhcpOption::parse1(uint16_t &aValue, const char *pcszValue) +{ + int rc = RTStrToUInt16Full(RTStrStripL(pcszValue), 10, &aValue); + + if (rc == VERR_TRAILING_SPACES) + rc = VINF_SUCCESS; + return rc; +} + + +int DhcpOption::parse1(uint32_t &aValue, const char *pcszValue) +{ + int rc = RTStrToUInt32Full(RTStrStripL(pcszValue), 10, &aValue); + + if (rc == VERR_TRAILING_SPACES) + rc = VINF_SUCCESS; + return rc; +} + + +int DhcpOption::parse1(RTNETADDRIPV4 &aValue, const char *pcszValue) +{ + return RTNetStrToIPv4Addr(pcszValue, &aValue); +} + + +int DhcpOption::parseList(std::vector<RTNETADDRIPV4> &aList, const char *pcszValue) +{ + std::vector<RTNETADDRIPV4> l; + int rc; + + pcszValue = RTStrStripL(pcszValue); + do { + RTNETADDRIPV4 Addr; + char *pszNext; + + rc = RTNetStrToIPv4AddrEx(pcszValue, &Addr, &pszNext); + if (RT_FAILURE(rc)) + return VERR_INVALID_PARAMETER; + + if (rc == VWRN_TRAILING_CHARS) + { + pcszValue = RTStrStripL(pszNext); + if (pcszValue == pszNext) /* garbage after address */ + return VERR_INVALID_PARAMETER; + } + + l.push_back(Addr); + + /* + * If we got VINF_SUCCESS or VWRN_TRAILING_SPACES then this + * was the last address and we are done. + */ + } while (rc == VWRN_TRAILING_CHARS); + + aList.swap(l); + return VINF_SUCCESS; +} + + +/* + * XXX: See DHCPServer::encodeOption() + */ +int DhcpOption::parseHex(octets_t &aRawValue, const char *pcszValue) +{ + octets_t data; + char *pszNext; + int rc; + + if (pcszValue == NULL || *pcszValue == '\0') + return VERR_INVALID_PARAMETER; + + while (*pcszValue != '\0') + { + if (data.size() > UINT8_MAX) + return VERR_INVALID_PARAMETER; + + uint8_t u8Byte; + rc = RTStrToUInt8Ex(pcszValue, &pszNext, 16, &u8Byte); + if (!RT_SUCCESS(rc)) + return rc; + + if (*pszNext == ':') + ++pszNext; + else if (*pszNext != '\0') + return VERR_PARSE_ERROR; + + data.push_back(u8Byte); + pcszValue = pszNext; + } + + aRawValue.swap(data); + return VINF_SUCCESS; +} + + +DhcpOption *DhcpOption::parse(uint8_t aOptCode, int aEnc, const char *pcszValue) +{ + switch (aEnc) + { + case 0: /* DhcpOptEncoding_Legacy */ + switch (aOptCode) + { +#define HANDLE(_OptClass) \ + case _OptClass::optcode: \ + return _OptClass::parse(pcszValue); + + HANDLE(OptSubnetMask); + HANDLE(OptRouter); + HANDLE(OptDNS); + HANDLE(OptHostName); + HANDLE(OptDomainName); + HANDLE(OptRootPath); + HANDLE(OptLeaseTime); + HANDLE(OptRenewalTime); + HANDLE(OptRebindingTime); + +#undef HANDLE + default: + return NULL; + } + break; + + case 1: + return RawOption::parse(aOptCode, pcszValue); + + default: + return NULL; + } +} diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpOptions.h b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.h new file mode 100644 index 00000000..b16782a4 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DhcpOptions.h @@ -0,0 +1,642 @@ +/* $Id: DhcpOptions.h $ */ +/** @file + * DHCP server - DHCP options + */ + +/* + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_DhcpOptions_h +#define VBOX_INCLUDED_SRC_Dhcpd_DhcpOptions_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "Defs.h" + +#include <string.h> + +#include <iprt/err.h> +#include <iprt/types.h> +#include <iprt/asm.h> +#include <iprt/stdint.h> +#include <iprt/net.h> + +#include <string> + +class DhcpClientMessage; + + +class DhcpOption +{ + protected: + uint8_t m_OptCode; + bool m_fPresent; + + public: + explicit DhcpOption(uint8_t aOptCode) + : m_OptCode(aOptCode), m_fPresent(true) {} + + DhcpOption(uint8_t aOptCode, bool fPresent) + : m_OptCode(aOptCode), m_fPresent(fPresent) {} + + virtual DhcpOption *clone() const = 0; + + virtual ~DhcpOption() {} + + public: + static DhcpOption *parse(uint8_t aOptCode, int aEnc, const char *pcszValue); + + public: + uint8_t optcode() const { return m_OptCode; } + bool present() const { return m_fPresent; } + + public: + int encode(octets_t &dst) const; + + int decode(const rawopts_t &map); + int decode(const DhcpClientMessage &req); + + protected: + virtual ssize_t encodeValue(octets_t &dst) const = 0; + virtual int decodeValue(const octets_t &src, size_t cb) = 0; + + protected: + static const octets_t *findOption(const rawopts_t &aOptMap, uint8_t aOptCode); + + protected: + /* + * Serialization + */ + static void append(octets_t &aDst, uint8_t aValue) + { + aDst.push_back(aValue); + } + + static void append(octets_t &aDst, uint16_t aValue) + { + RTUINT16U u16 = { RT_H2N_U16(aValue) }; + aDst.insert(aDst.end(), u16.au8, u16.au8 + sizeof(aValue)); + } + + static void append(octets_t &aDst, uint32_t aValue) + { + RTUINT32U u32 = { RT_H2N_U32(aValue) }; + aDst.insert(aDst.end(), u32.au8, u32.au8 + sizeof(aValue)); + } + + static void append(octets_t &aDst, RTNETADDRIPV4 aIPv4) + { + aDst.insert(aDst.end(), aIPv4.au8, aIPv4.au8 + sizeof(aIPv4)); + } + + static void append(octets_t &aDst, const char *pszString, size_t cb) + { + aDst.insert(aDst.end(), pszString, pszString + cb); + } + + static void append(octets_t &aDst, const std::string &str) + { + append(aDst, str.c_str(), str.size()); + } + + /* non-overloaded name to avoid ambiguity */ + static void appendLength(octets_t &aDst, size_t cb) + { + append(aDst, static_cast<uint8_t>(cb)); + } + + + /* + * Deserialization + */ + static void extract(uint8_t &aValue, octets_t::const_iterator &pos) + { + aValue = *pos; + pos += sizeof(uint8_t); + } + + static void extract(uint16_t &aValue, octets_t::const_iterator &pos) + { + RTUINT16U u16; + memcpy(u16.au8, &pos[0], sizeof(uint16_t)); + aValue = RT_N2H_U16(u16.u); + pos += sizeof(uint16_t); + } + + static void extract(uint32_t &aValue, octets_t::const_iterator &pos) + { + RTUINT32U u32; + memcpy(u32.au8, &pos[0], sizeof(uint32_t)); + aValue = RT_N2H_U32(u32.u); + pos += sizeof(uint32_t); + } + + static void extract(RTNETADDRIPV4 &aValue, octets_t::const_iterator &pos) + { + memcpy(aValue.au8, &pos[0], sizeof(RTNETADDRIPV4)); + pos += sizeof(RTNETADDRIPV4); + } + + static void extract(std::string &aString, octets_t::const_iterator &pos, size_t cb) + { + aString.replace(aString.begin(), aString.end(), &pos[0], &pos[cb]); + pos += cb; + } + + + /* + * Parse textual representation (e.g. in config file) + */ + static int parse1(uint8_t &aValue, const char *pcszValue); + static int parse1(uint16_t &aValue, const char *pcszValue); + static int parse1(uint32_t &aValue, const char *pcszValue); + static int parse1(RTNETADDRIPV4 &aValue, const char *pcszValue); + + static int parseList(std::vector<RTNETADDRIPV4> &aList, const char *pcszValue); + + static int parseHex(octets_t &aRawValue, const char *pcszValue); +}; + + +inline octets_t &operator<<(octets_t &dst, const DhcpOption &option) +{ + option.encode(dst); + return dst; +} + + +optmap_t &operator<<(optmap_t &optmap, DhcpOption *option); +optmap_t &operator<<(optmap_t &optmap, const std::shared_ptr<DhcpOption> &option); + + + +/* + * Only for << OptEnd() syntactic sugar... + */ +struct OptEnd {}; +inline octets_t &operator<<(octets_t &dst, const OptEnd &end) +{ + RT_NOREF(end); + + dst.push_back(RTNET_DHCP_OPT_END); + return dst; +} + + + +/* + * Option that has no value + */ +class OptNoValueBase + : public DhcpOption +{ + public: + explicit OptNoValueBase(uint8_t aOptCode) + : DhcpOption(aOptCode, false) {} + + OptNoValueBase(uint8_t aOptCode, bool fPresent) + : DhcpOption(aOptCode, fPresent) {} + + OptNoValueBase(uint8_t aOptCode, const DhcpClientMessage &req) + : DhcpOption(aOptCode, false) + { + decode(req); + } + + virtual OptNoValueBase *clone() const + { + return new OptNoValueBase(*this); + } + + protected: + virtual ssize_t encodeValue(octets_t &dst) const + { + RT_NOREF(dst); + return 0; + } + + public: + static bool isLengthValid(size_t cb) + { + return cb == 0; + } + + virtual int decodeValue(const octets_t &src, size_t cb) + { + RT_NOREF(src); + + if (!isLengthValid(cb)) + return VERR_INVALID_PARAMETER; + + m_fPresent = true; + return VINF_SUCCESS; + } +}; + +template <uint8_t _OptCode> +class OptNoValue + : public OptNoValueBase +{ + public: + static const uint8_t optcode = _OptCode; + + OptNoValue() + : OptNoValueBase(optcode) {} + + explicit OptNoValue(bool fPresent) /* there's no overloaded ctor with value */ + : OptNoValueBase(optcode, fPresent) {} + + explicit OptNoValue(const DhcpClientMessage &req) + : OptNoValueBase(optcode, req) {} +}; + + + +/* + * Option that contains single value of fixed-size type T + */ +template <typename T> +class OptValueBase + : public DhcpOption +{ + public: + typedef T value_t; + + protected: + T m_Value; + + explicit OptValueBase(uint8_t aOptCode) + : DhcpOption(aOptCode, false), m_Value() {} + + OptValueBase(uint8_t aOptCode, const T &aOptValue) + : DhcpOption(aOptCode), m_Value(aOptValue) {} + + OptValueBase(uint8_t aOptCode, const DhcpClientMessage &req) + : DhcpOption(aOptCode, false), m_Value() + { + decode(req); + } + + public: + virtual OptValueBase *clone() const + { + return new OptValueBase(*this); + } + + public: + T &value() { return m_Value; } + const T &value() const { return m_Value; } + + protected: + virtual ssize_t encodeValue(octets_t &dst) const + { + append(dst, m_Value); + return sizeof(T); + } + + public: + static bool isLengthValid(size_t cb) + { + return cb == sizeof(T); + } + + virtual int decodeValue(const octets_t &src, size_t cb) + { + if (!isLengthValid(cb)) + return VERR_INVALID_PARAMETER; + + octets_t::const_iterator pos(src.begin()); + extract(m_Value, pos); + + m_fPresent = true; + return VINF_SUCCESS; + } +}; + +template<uint8_t _OptCode, typename T> +class OptValue + : public OptValueBase<T> +{ + public: + using typename OptValueBase<T>::value_t; + + public: + static const uint8_t optcode = _OptCode; + + OptValue() + : OptValueBase<T>(optcode) {} + + explicit OptValue(const T &aOptValue) + : OptValueBase<T>(optcode, aOptValue) {} + + explicit OptValue(const DhcpClientMessage &req) + : OptValueBase<T>(optcode, req) {} + + static OptValue *parse(const char *pcszValue) + { + typename OptValueBase<T>::value_t v; + int rc = DhcpOption::parse1(v, pcszValue); + if (RT_FAILURE(rc)) + return NULL; + return new OptValue(v); + } +}; + + + +/* + * Option that contains a string. + */ +class OptStringBase + : public DhcpOption +{ + public: + typedef std::string value_t; + + protected: + std::string m_String; + + explicit OptStringBase(uint8_t aOptCode) + : DhcpOption(aOptCode, false), m_String() {} + + OptStringBase(uint8_t aOptCode, const std::string &aOptString) + : DhcpOption(aOptCode), m_String(aOptString) {} + + OptStringBase(uint8_t aOptCode, const DhcpClientMessage &req) + : DhcpOption(aOptCode, false), m_String() + { + decode(req); + } + + public: + virtual OptStringBase *clone() const + { + return new OptStringBase(*this); + } + + public: + std::string &value() { return m_String; } + const std::string &value() const { return m_String; } + + protected: + virtual ssize_t encodeValue(octets_t &dst) const + { + if (!isLengthValid(m_String.size())) + return -1; + + append(dst, m_String); + return m_String.size(); + } + + public: + static bool isLengthValid(size_t cb) + { + return cb <= UINT8_MAX; + } + + virtual int decodeValue(const octets_t &src, size_t cb) + { + if (!isLengthValid(cb)) + return VERR_INVALID_PARAMETER; + + octets_t::const_iterator pos(src.begin()); + extract(m_String, pos, cb); + m_fPresent = true; + return VINF_SUCCESS; + } +}; + +template<uint8_t _OptCode> +class OptString + : public OptStringBase +{ + public: + static const uint8_t optcode = _OptCode; + + OptString() + : OptStringBase(optcode) {} + + explicit OptString(const std::string &aOptString) + : OptStringBase(optcode, aOptString) {} + + explicit OptString(const DhcpClientMessage &req) + : OptStringBase(optcode, req) {} + + static OptString *parse(const char *pcszValue) + { + return new OptString(pcszValue); + } +}; + + + +/* + * Option that contains a list of values of type T + */ +template <typename T> +class OptListBase + : public DhcpOption +{ + public: + typedef std::vector<T> value_t; + + protected: + std::vector<T> m_List; + + explicit OptListBase(uint8_t aOptCode) + : DhcpOption(aOptCode, false), m_List() {} + + OptListBase(uint8_t aOptCode, const T &aOptSingle) + : DhcpOption(aOptCode), m_List(1, aOptSingle) {} + + OptListBase(uint8_t aOptCode, const std::vector<T> &aOptList) + : DhcpOption(aOptCode), m_List(aOptList) {} + + OptListBase(uint8_t aOptCode, const DhcpClientMessage &req) + : DhcpOption(aOptCode, false), m_List() + { + decode(req); + } + + public: + virtual OptListBase *clone() const + { + return new OptListBase(*this); + } + + public: + std::vector<T> &value() { return m_List; } + const std::vector<T> &value() const { return m_List; } + + protected: + virtual ssize_t encodeValue(octets_t &dst) const + { + const size_t cbItem = sizeof(T); + size_t cbValue = 0; + + for (size_t i = 0; i < m_List.size(); ++i) + { + if (cbValue + cbItem > UINT8_MAX) + break; + + append(dst, m_List[i]); + cbValue += cbItem; + } + + return cbValue; + } + + public: + static bool isLengthValid(size_t cb) + { + return cb % sizeof(T) == 0; + } + + virtual int decodeValue(const octets_t &src, size_t cb) + { + if (!isLengthValid(cb)) + return VERR_INVALID_PARAMETER; + + m_List.erase(m_List.begin(), m_List.end()); + + octets_t::const_iterator pos(src.begin()); + for (size_t i = 0; i < cb / sizeof(T); ++i) + { + T item; + extract(item, pos); + m_List.push_back(item); + } + m_fPresent = true; + return VINF_SUCCESS; + } +}; + +template<uint8_t _OptCode, typename T> +class OptList + : public OptListBase<T> + +{ + public: + using typename OptListBase<T>::value_t; + + public: + static const uint8_t optcode = _OptCode; + + OptList() + : OptListBase<T>(optcode) {} + + explicit OptList(const T &aOptSingle) + : OptListBase<T>(optcode, aOptSingle) {} + + explicit OptList(const std::vector<T> &aOptList) + : OptListBase<T>(optcode, aOptList) {} + + explicit OptList(const DhcpClientMessage &req) + : OptListBase<T>(optcode, req) {} + + static OptList *parse(const char *pcszValue) + { + typename OptListBase<T>::value_t v; + int rc = DhcpOption::parseList(v, pcszValue); + if (RT_FAILURE(rc) || v.empty()) + return NULL; + return new OptList(v); + } +}; + + +/* + * Options specified by raw binary data that we don't know how to + * interpret. + */ +class RawOption + : public DhcpOption +{ + protected: + octets_t m_Data; + + public: + explicit RawOption(uint8_t aOptCode) + : DhcpOption(aOptCode, false), m_Data() {} + + RawOption(uint8_t aOptCode, const octets_t &aSrc) + : DhcpOption(aOptCode), m_Data(aSrc) {} + + public: + virtual RawOption *clone() const + { + return new RawOption(*this); + } + + + protected: + virtual ssize_t encodeValue(octets_t &dst) const + { + dst.insert(dst.end(), m_Data.begin(), m_Data.end()); + return m_Data.size(); + } + + virtual int decodeValue(const octets_t &src, size_t cb) + { + octets_t::const_iterator beg(src.begin()); + octets_t data(beg, beg + cb); + m_Data.swap(data); + + m_fPresent = true; + return VINF_SUCCESS; + } + + public: + static RawOption *parse(uint8_t aOptCode, const char *pcszValue) + { + octets_t data; + int rc = DhcpOption::parseHex(data, pcszValue); + if (RT_FAILURE(rc)) + return NULL; + return new RawOption(aOptCode, data); + } +}; + + + +/* + * Define the DHCP options we want to use. + */ +typedef OptValue<1, RTNETADDRIPV4> OptSubnetMask; +typedef OptValue<2, uint32_t> OptTimeOffset; +typedef OptList<3, RTNETADDRIPV4> OptRouter; +typedef OptList<4, RTNETADDRIPV4> OptTimeServer; +typedef OptList<6, RTNETADDRIPV4> OptDNS; +typedef OptString<12> OptHostName; +typedef OptString<15> OptDomainName; +typedef OptString<17> OptRootPath; + +/* DHCP related options */ +typedef OptList<43, uint8_t> OptVendorSpecificInfo; +typedef OptValue<50, RTNETADDRIPV4> OptRequestedAddress; +typedef OptValue<51, uint32_t> OptLeaseTime; +/* 52 - option overload is syntactic and handled internally */ +typedef OptValue<53, uint8_t> OptMessageType; +typedef OptValue<54, RTNETADDRIPV4> OptServerId; +typedef OptList<55, uint8_t> OptParameterRequest; +typedef OptString<56> OptMessage; +typedef OptValue<57, uint16_t> OptMaxDHCPMessageSize; +typedef OptValue<58, uint32_t> OptRenewalTime; +typedef OptValue<59, uint32_t> OptRebindingTime; +typedef OptList<60, uint8_t> OptVendorClassId; +typedef OptList<61, uint8_t> OptClientId; +typedef OptString<66> OptTFTPServer; /* when overloaded */ +typedef OptString<67> OptBootFileName; /* when overloaded */ +typedef OptNoValue<80> OptRapidCommit; /* RFC4039 */ + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_DhcpOptions_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp b/src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp new file mode 100644 index 00000000..f43fbccf --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp @@ -0,0 +1,138 @@ +/* $Id: IPv4Pool.cpp $ */ +/** @file + * DHCP server - a pool of IPv4 addresses + */ + +/* + * 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 <iprt/errcore.h> +#include <iprt/stream.h> + +#include "IPv4Pool.h" + + +int IPv4Pool::init(const IPv4Range &aRange) +{ + if (!aRange.isValid()) + return VERR_INVALID_PARAMETER; + + m_range = aRange; + m_pool.insert(m_range); + return VINF_SUCCESS; +} + + +int IPv4Pool::init(RTNETADDRIPV4 aFirstAddr, RTNETADDRIPV4 aLastAddr) +{ + IPv4Range range(aFirstAddr, aLastAddr); + + if (!range.isValid()) + return VERR_INVALID_PARAMETER; + + m_range = range; + m_pool.insert(m_range); + return VINF_SUCCESS; +} + + +int IPv4Pool::insert(const IPv4Range &range) +{ + if (!m_range.isValid()) + return VERR_INVALID_PARAMETER; + + if (!m_range.contains(range)) + return VERR_INVALID_PARAMETER; + + it_t it = m_pool.upper_bound(IPv4Range(range.LastAddr)); /* successor */ + if (it != m_pool.begin()) + { + it_t prev(it); + --prev; + if (range.FirstAddr <= prev->LastAddr) { +#if 1 /* XXX */ + RTPrintf("%08x-%08x conflicts with %08x-%08x\n", + range.FirstAddr, range.LastAddr, + prev->FirstAddr, prev->LastAddr); +#endif + return VERR_INVALID_PARAMETER; + } + } + + m_pool.insert(it, range); + return VINF_SUCCESS; +} + + +RTNETADDRIPV4 IPv4Pool::allocate() +{ + if (m_pool.empty()) + { + RTNETADDRIPV4 res = { 0 }; + return res; + } + + it_t beg = m_pool.begin(); + ip_haddr_t addr = beg->FirstAddr; + + if (beg->FirstAddr == beg->LastAddr) + { + m_pool.erase(beg); + } + else + { + IPv4Range trimmed = *beg; + ++trimmed.FirstAddr; + m_pool.erase(beg); + m_pool.insert(trimmed); + } + + RTNETADDRIPV4 res = { RT_H2N_U32(addr) }; + return res; +} + + +bool IPv4Pool::allocate(RTNETADDRIPV4 addr) +{ + it_t it = m_pool.lower_bound(IPv4Range(addr)); /* candidate range */ + if (it == m_pool.end()) + return false; + + Assert(RT_N2H_U32(addr.u) <= it->LastAddr); /* by definition of < and lower_bound */ + + if (!it->contains(addr)) + return false; + + const ip_haddr_t haddr = RT_N2H_U32(addr.u); + ip_haddr_t first = it->FirstAddr; + ip_haddr_t last = it->LastAddr; + + m_pool.erase(it); + if (first != last) + { + if (haddr == first) + { + insert(++first, last); + } + else if (haddr == last) + { + insert(first, --last); + } + else + { + insert(first, haddr - 1); + insert(haddr + 1, last); + } + } + + return true; +} diff --git a/src/VBox/NetworkServices/Dhcpd/IPv4Pool.h b/src/VBox/NetworkServices/Dhcpd/IPv4Pool.h new file mode 100644 index 00000000..dec9c6ac --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/IPv4Pool.h @@ -0,0 +1,126 @@ +/* $Id: IPv4Pool.h $ */ +/** @file + * DHCP server - a pool of IPv4 addresses + */ + +/* + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_IPv4Pool_h +#define VBOX_INCLUDED_SRC_Dhcpd_IPv4Pool_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/asm.h> +#include <iprt/stdint.h> +#include <iprt/net.h> +#include <set> + +typedef uint32_t ip_haddr_t; /* in host order */ + + +/* + * A range of IPv4 addresses (in host order). + */ +struct IPv4Range +{ + ip_haddr_t FirstAddr; + ip_haddr_t LastAddr; /* inclusive */ + + IPv4Range() + : FirstAddr(), LastAddr() {} + + explicit IPv4Range(ip_haddr_t aSingleAddr) + : FirstAddr(aSingleAddr), LastAddr(aSingleAddr) {} + + IPv4Range(ip_haddr_t aFirstAddr, ip_haddr_t aLastAddr) + : FirstAddr(aFirstAddr), LastAddr(aLastAddr) {} + + explicit IPv4Range(RTNETADDRIPV4 aSingleAddr) + : FirstAddr(RT_N2H_U32(aSingleAddr.u)), LastAddr(RT_N2H_U32(aSingleAddr.u)) {} + + IPv4Range(RTNETADDRIPV4 aFirstAddr, RTNETADDRIPV4 aLastAddr) + : FirstAddr(RT_N2H_U32(aFirstAddr.u)), LastAddr(RT_N2H_U32(aLastAddr.u)) {} + + bool isValid() const + { + return FirstAddr <= LastAddr; + } + + bool contains(ip_haddr_t addr) const + { + return FirstAddr <= addr && addr <= LastAddr; + } + + bool contains(RTNETADDRIPV4 addr) const + { + return contains(RT_N2H_U32(addr.u)); + } + + bool contains(const IPv4Range &range) const + { + return range.isValid() && FirstAddr <= range.FirstAddr && range.LastAddr <= LastAddr; + } +}; + + +inline bool operator==(const IPv4Range &l, const IPv4Range &r) +{ + return l.FirstAddr == r.FirstAddr && l.LastAddr == r.LastAddr; +} + + +inline bool operator<(const IPv4Range &l, const IPv4Range &r) +{ + return l.LastAddr < r.FirstAddr; +} + + +class IPv4Pool +{ + typedef std::set<IPv4Range> set_t; + typedef set_t::iterator it_t; + + IPv4Range m_range; + set_t m_pool; + +public: + IPv4Pool() {} + + int init(const IPv4Range &aRange); + int init(RTNETADDRIPV4 aFirstAddr, RTNETADDRIPV4 aLastAddr); + + bool contains(RTNETADDRIPV4 addr) const + { return m_range.contains(addr); } + + int insert(const IPv4Range &range); + +#if 0 + int insert(ip_haddr_t single) + { return insert(IPv4Range(single)); } +#endif + + int insert(ip_haddr_t first, ip_haddr_t last) + { return insert(IPv4Range(first, last)); } + + int insert(RTNETADDRIPV4 single) + { return insert(IPv4Range(single)); } + + int insert(RTNETADDRIPV4 first, RTNETADDRIPV4 last) + { return insert(IPv4Range(first, last)); } + + RTNETADDRIPV4 allocate(); + bool allocate(RTNETADDRIPV4); +}; + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_IPv4Pool_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk new file mode 100644 index 00000000..48e51ffa --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk @@ -0,0 +1,93 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-makefile for the DHCP server +# + +# +# Copyright (C) 2006-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. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk +# ifdef VBOX_WITH_... + + VBOX_PATH_Dhcpd_SRC := $(PATH_SUB_CURRENT) + + # XXX: do not depend on order + ifndef LWIP_SOURCES + include $(PATH_SUB_CURRENT)/../../Devices/Network/lwip-new/Config.kmk + endif + + ifdef VBOX_WITH_HARDENING + PROGRAMS += VBoxNetDhcpdHardened + DLLS += VBoxNetDhcpd + else + PROGRAMS += VBoxNetDhcpd + endif + + VBoxNetDhcpdHardened_TEMPLATE = VBOXR3HARDENEDEXE + VBoxNetDhcpdHardened_NAME = VBoxNetDHCP + VBoxNetDhcpdHardened_DEFS = SERVICE_NAME=\"VBoxNetDhcpd\" + VBoxNetDhcpdHardened_SOURCES = VBoxNetDhcpdHardened.cpp + VBoxNetDhcpdHardened_SOURCES.win = $(VBoxNetDhcpd_0_OUTDIR)/VBoxNetDhcpd-icon.rc + VBoxNetDhcpdHardened_LDFLAGS.win = /SUBSYSTEM:windows + + VBoxNetDhcpd_TEMPLATE := $(if-expr defined(VBOX_WITH_HARDENING),VBoxR3Dll,VBOXR3EXE) + VBoxNetDhcpd_NAME = VBoxNetDHCP + # VBoxNetDhcpd_DEFS = IPv6 + # VBoxNetDhcpd_DEFS.linux = WITH_VALGRIND + #VBoxNetDhcpd_DEFS.win = VBOX_COM_OUTOFPROC_MODULE _WIN32_WINNT=0x501 # Windows XP + + # (current dir is for for lwipopts.h) + VBoxNetDhcpd_INCS += . $(addprefix ../../Devices/Network/lwip-new/,$(LWIP_INCS)) + + VBoxNetDhcpd_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\" + ifneq ($(KBUILD_TARGET),win) + VBoxNetDhcpd_DEFS += VBOX_WITH_XPCOM + VBoxNetDhcpd_INCS += $(VBOX_XPCOM_INCS) + ifneq ($(KBUILD_TARGET),darwin) + # We want -std=c++11 for 4.7 and newer compilers, and -std=c++0x for older ones. + VBoxNetDhcpd_CXXFLAGS += -std=$(if $(VBOX_GCC_VERSION_CXX),$(if $(VBOX_GCC_VERSION_CXX) < 40700,c++0x,c++11),c++0x) + endif + endif + VBoxNetDhcpd_SOURCES = ../../Main/glue/VBoxLogRelCreate.cpp \ + ../../Main/glue/GetVBoxUserHomeDirectory.cpp \ + ClientId.cpp \ + Config.cpp \ + DHCPD.cpp \ + Db.cpp \ + DhcpMessage.cpp \ + DhcpOptions.cpp \ + IPv4Pool.cpp \ + TimeStamp.cpp \ + VBoxNetDhcpd.cpp \ + $(addprefix ../../Devices/Network/lwip-new/,$(LWIP_SOURCES)) + + VBoxNetDhcpd_LIBS = $(LIB_RUNTIME) + + VBoxNetDhcpd_LIBS.solaris += socket nsl + VBoxNetDhcpd_LDFLAGS.win = /SUBSYSTEM:windows + + ifeq ($(KBUILD_TARGET),win) + # Icon include file. + VBoxNetDhcpd_SOURCES += VBoxNetDhcpd.rc + VBoxNetDhcpd.rc_INCS = $(VBoxNetDhcpd_0_OUTDIR) + VBoxNetDhcpd.rc_DEPS = $(VBoxNetDhcpd_0_OUTDIR)/VBoxNetDhcpd-icon.rc + VBoxNetDhcpd.rc_CLEAN = $(VBoxNetDhcpd_0_OUTDIR)/VBoxNetDhcpd-icon.rc + + $$(VBoxNetDhcpd_0_OUTDIR)/VBoxNetDhcpd-icon.rc: $(VBOX_WINDOWS_ICON_FILE) \ + $$(VBoxNetDhcpd_DEFPATH)/Makefile.kmk | $$(dir $$@) + $(RM) -f $@ + $(APPEND) $@ 'IDI_VIRTUALBOX ICON DISCARDABLE "$(subst /,\\,$(VBOX_WINDOWS_ICON_FILE))"' + endif # win + +# endif # VBOX_WITH_... +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/NetworkServices/Dhcpd/TimeStamp.cpp b/src/VBox/NetworkServices/Dhcpd/TimeStamp.cpp new file mode 100644 index 00000000..0a390ef8 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/TimeStamp.cpp @@ -0,0 +1,36 @@ +/* $Id: TimeStamp.cpp $ */ +/** @file + * DHCP server - timestamps + */ + +/* + * 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 "TimeStamp.h" + +#include <iprt/string.h> + + +size_t TimeStamp::absStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput) const +{ + RTTIMESPEC Spec; + getAbsTimeSpec(&Spec); + + RTTIME Time; + RTTimeExplode(&Time, &Spec); + + size_t cb = RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "%RI32-%02u-%02uT%02u:%02u:%02uZ", + Time.i32Year, Time.u8Month, Time.u8MonthDay, + Time.u8Hour, Time.u8Minute, Time.u8Second); + return cb; +} diff --git a/src/VBox/NetworkServices/Dhcpd/TimeStamp.h b/src/VBox/NetworkServices/Dhcpd/TimeStamp.h new file mode 100644 index 00000000..9f95bc47 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/TimeStamp.h @@ -0,0 +1,101 @@ +/* $Id: TimeStamp.h $ */ +/** @file + * DHCP server - timestamps + */ + +/* + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_TimeStamp_h +#define VBOX_INCLUDED_SRC_Dhcpd_TimeStamp_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/string.h> +#include <iprt/time.h> + + +/* + * Timestamp API uses unsigned time, but we need to be able to refer + * to events in the past. Hide the ugly convertions. + */ +class TimeStamp +{ + int64_t m_ns; + +public: + TimeStamp() + : m_ns(0) {} + + TimeStamp(uint64_t ns) + : m_ns(static_cast<int64_t>(ns)) {} + + static TimeStamp now() + { + return TimeStamp(RTTimeNanoTS()); + } + + static TimeStamp absSeconds(int64_t sec) + { + RTTIMESPEC delta; + RTTimeNow(&delta); + RTTimeSpecSubSeconds(&delta, sec); + + uint64_t stampNow = RTTimeNanoTS(); + return TimeStamp(stampNow - RTTimeSpecGetNano(&delta)); + } + + TimeStamp &addSeconds(int64_t sec) + { + m_ns += sec * RT_NS_1SEC; + return *this; + } + + TimeStamp &subSeconds(int64_t sec) + { + m_ns -= sec * RT_NS_1SEC; + return *this; + } + + + RTTIMESPEC *getAbsTimeSpec(RTTIMESPEC *pTime) const + { + RTTimeNow(pTime); + + uint64_t stampNow = RTTimeNanoTS(); + uint64_t delta = stampNow - m_ns; + RTTimeSpecSubNano(pTime, delta); + return pTime; + } + + int64_t getAbsSeconds() const + { + RTTIMESPEC time; + return RTTimeSpecGetSeconds(getAbsTimeSpec(&time)); + } + + size_t absStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput) const; + + friend bool operator<(const TimeStamp &l, const TimeStamp &r); + friend bool operator>(const TimeStamp &l, const TimeStamp &r); + friend bool operator<=(const TimeStamp &l, const TimeStamp &r); + friend bool operator>=(const TimeStamp &l, const TimeStamp &r); +}; + + +inline bool operator<(const TimeStamp &l, const TimeStamp &r) { return l.m_ns < r.m_ns; } +inline bool operator>(const TimeStamp &l, const TimeStamp &r) { return l.m_ns > r.m_ns; } +inline bool operator<=(const TimeStamp &l, const TimeStamp &r) { return l.m_ns <= r.m_ns; } +inline bool operator>=(const TimeStamp &l, const TimeStamp &r) { return l.m_ns >= r.m_ns; } + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_TimeStamp_h */ diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp new file mode 100644 index 00000000..9f180410 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp @@ -0,0 +1,805 @@ +/* $Id: VBoxNetDhcpd.cpp $ */ +/** @file + * VBoxNetDhcpd - DHCP server for host-only and NAT networks. + */ + +/* + * Copyright (C) 2009-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 <iprt/cdefs.h> + +/* + * Need to get host/network order conversion stuff from Windows headers, + * so we do not define them in LWIP and then try to re-define them in + * Windows headers. + */ +#ifdef RT_OS_WINDOWS +# include <iprt/win/winsock2.h> +#endif + + +#include <iprt/param.h> +#include <iprt/errcore.h> + +#include <iprt/initterm.h> +#include <iprt/message.h> + +#include <iprt/net.h> +#include <iprt/path.h> +#include <iprt/stream.h> + +#include <VBox/sup.h> +#include <VBox/vmm/vmm.h> +#include <VBox/vmm/pdmnetinline.h> +#include <VBox/intnet.h> +#include <VBox/intnetinline.h> + +#include "VBoxLwipCore.h" +#include "Config.h" +#include "DHCPD.h" +#include "DhcpMessage.h" + +extern "C" +{ +#include "lwip/sys.h" +#include "lwip/pbuf.h" +#include "lwip/netif.h" +#include "lwip/tcpip.h" +#include "lwip/udp.h" +#include "netif/etharp.h" +} + +#include <string> +#include <vector> +#include <memory> + +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#endif + +struct delete_pbuf +{ + delete_pbuf() {} + void operator()(struct pbuf *p) const { pbuf_free(p); } +}; + +typedef std::unique_ptr<pbuf, delete_pbuf> unique_ptr_pbuf; + + +#define CALL_VMMR0(op, req) \ + (SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, (op), 0, &(req).Hdr)) + + +class VBoxNetDhcpd +{ + DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(VBoxNetDhcpd); + +private: + PRTLOGGER m_pStderrReleaseLogger; + + /* intnet plumbing */ + PSUPDRVSESSION m_pSession; + INTNETIFHANDLE m_hIf; + PINTNETBUF m_pIfBuf; + + /* lwip stack connected to the intnet */ + struct netif m_LwipNetif; + + Config *m_Config; + + /* listening pcb */ + struct udp_pcb *m_Dhcp4Pcb; + + DHCPD m_server; + +public: + VBoxNetDhcpd(); + ~VBoxNetDhcpd(); + + int main(int argc, char **argv); + +private: + int logInitStderr(); + + /* + * Boilerplate code. + */ + int r3Init(); + void r3Fini(); + + int vmmInit(); + + int ifInit(const std::string &strNetwork, + const std::string &strTrunk = std::string(), + INTNETTRUNKTYPE enmTrunkType = kIntNetTrunkType_WhateverNone); + int ifOpen(const std::string &strNetwork, + const std::string &strTrunk, + INTNETTRUNKTYPE enmTrunkType); + int ifGetBuf(); + int ifActivate(); + + int ifWait(uint32_t cMillies = RT_INDEFINITE_WAIT); + int ifProcessInput(); + int ifFlush(); + + int ifClose(); + + void ifPump(); + int ifInput(void *pvSegFrame, uint32_t cbSegFrame); + + int ifOutput(PCINTNETSEG paSegs, size_t cSegs, size_t cbFrame); + + + /* + * lwIP callbacks + */ + static DECLCALLBACK(void) lwipInitCB(void *pvArg); + void lwipInit(); + + static err_t netifInitCB(netif *pNetif); + err_t netifInit(netif *pNetif); + + static err_t netifLinkOutputCB(netif *pNetif, pbuf *pPBuf); + err_t netifLinkOutput(pbuf *pPBuf); + + static void dhcp4RecvCB(void *arg, struct udp_pcb *pcb, struct pbuf *p, + ip_addr_t *addr, u16_t port); + void dhcp4Recv(struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port); +}; + + +VBoxNetDhcpd::VBoxNetDhcpd() + : m_pStderrReleaseLogger(NULL), + m_pSession(NIL_RTR0PTR), + m_hIf(INTNET_HANDLE_INVALID), + m_pIfBuf(NULL), + m_LwipNetif(), + m_Config(NULL), + m_Dhcp4Pcb(NULL) +{ + int rc; + + logInitStderr(); + + rc = r3Init(); + if (RT_FAILURE(rc)) + return; + + vmmInit(); +} + + +VBoxNetDhcpd::~VBoxNetDhcpd() +{ + ifClose(); + r3Fini(); +} + + +/* + * We don't know the name of the release log file until we parse our + * configuration because we use network name as basename. To get + * early logging to work, start with stderr-only release logger. + * + * We disable "sup" for this logger to avoid spam from SUPR3Init(). + */ +int VBoxNetDhcpd::logInitStderr() +{ + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + + PRTLOGGER pLogger; + int rc; + + uint32_t fFlags = 0; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + fFlags |= RTLOGFLAGS_USECRLF; +#endif + + rc = RTLogCreate(&pLogger, fFlags, + "all -sup all.restrict -default.restrict", + NULL, /* environment base */ + RT_ELEMENTS(s_apszGroups), s_apszGroups, + RTLOGDEST_STDERR, NULL); + if (RT_FAILURE(rc)) + { + RTPrintf("Failed to init stderr logger: %Rrs\n", rc); + return rc; + } + + m_pStderrReleaseLogger = pLogger; + RTLogRelSetDefaultInstance(m_pStderrReleaseLogger); + + return VINF_SUCCESS; +} + + +int VBoxNetDhcpd::r3Init() +{ + AssertReturn(m_pSession == NIL_RTR0PTR, VERR_GENERAL_FAILURE); + + int rc = SUPR3Init(&m_pSession); + return rc; +} + + +void VBoxNetDhcpd::r3Fini() +{ + if (m_pSession == NIL_RTR0PTR) + return; + + SUPR3Term(); + m_pSession = NIL_RTR0PTR; +} + + +int VBoxNetDhcpd::vmmInit() +{ + int rc; + try { + std::vector<char> vExecDir(RTPATH_MAX); + rc = RTPathExecDir(&vExecDir.front(), vExecDir.size()); + if (RT_FAILURE(rc)) + return rc; + std::string strPath(&vExecDir.front()); + strPath.append("/VMMR0.r0"); + + rc = SUPR3LoadVMM(strPath.c_str()); + if (RT_FAILURE(rc)) + return rc; + + rc = VINF_SUCCESS; + } + catch (...) + { + rc = VERR_GENERAL_FAILURE; + } + + return rc; +} + + +int VBoxNetDhcpd::ifInit(const std::string &strNetwork, + const std::string &strTrunk, + INTNETTRUNKTYPE enmTrunkType) +{ + int rc; + + rc = ifOpen(strNetwork, strTrunk, enmTrunkType); + if (RT_FAILURE(rc)) + return rc; + + rc = ifGetBuf(); + if (RT_FAILURE(rc)) + return rc; + + rc = ifActivate(); + if (RT_FAILURE(rc)) + return rc; + + return VINF_SUCCESS; +} + + +int VBoxNetDhcpd::ifOpen(const std::string &strNetwork, + const std::string &strTrunk, + INTNETTRUNKTYPE enmTrunkType) +{ + AssertReturn(m_pSession != NIL_RTR0PTR, VERR_GENERAL_FAILURE); + AssertReturn(m_hIf == INTNET_HANDLE_INVALID, VERR_GENERAL_FAILURE); + + INTNETOPENREQ OpenReq; + int rc; + + OpenReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + OpenReq.Hdr.cbReq = sizeof(OpenReq); + OpenReq.pSession = m_pSession; + + strncpy(OpenReq.szNetwork, strNetwork.c_str(), sizeof(OpenReq.szNetwork)); + OpenReq.szNetwork[sizeof(OpenReq.szNetwork) - 1] = '\0'; + + strncpy(OpenReq.szTrunk, strTrunk.c_str(), sizeof(OpenReq.szTrunk)); + OpenReq.szTrunk[sizeof(OpenReq.szTrunk) - 1] = '\0'; + + if (enmTrunkType != kIntNetTrunkType_Invalid) + OpenReq.enmTrunkType = enmTrunkType; + else + OpenReq.enmTrunkType = kIntNetTrunkType_WhateverNone; + + OpenReq.fFlags = 0; + OpenReq.cbSend = 128 * _1K; + OpenReq.cbRecv = 256 * _1K; + + OpenReq.hIf = INTNET_HANDLE_INVALID; + + rc = CALL_VMMR0(VMMR0_DO_INTNET_OPEN, OpenReq); + if (RT_FAILURE(rc)) + return rc; + + m_hIf = OpenReq.hIf; + AssertReturn(m_hIf != INTNET_HANDLE_INVALID, VERR_GENERAL_FAILURE); + + return VINF_SUCCESS; +} + + +int VBoxNetDhcpd::ifGetBuf() +{ + AssertReturn(m_pSession != NIL_RTR0PTR, VERR_GENERAL_FAILURE); + AssertReturn(m_hIf != INTNET_HANDLE_INVALID, VERR_GENERAL_FAILURE); + AssertReturn(m_pIfBuf == NULL, VERR_GENERAL_FAILURE); + + INTNETIFGETBUFFERPTRSREQ GetBufferPtrsReq; + int rc; + + GetBufferPtrsReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + GetBufferPtrsReq.Hdr.cbReq = sizeof(GetBufferPtrsReq); + GetBufferPtrsReq.pSession = m_pSession; + GetBufferPtrsReq.hIf = m_hIf; + + GetBufferPtrsReq.pRing0Buf = NIL_RTR0PTR; + GetBufferPtrsReq.pRing3Buf = NULL; + + rc = CALL_VMMR0(VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, GetBufferPtrsReq); + if (RT_FAILURE(rc)) + return rc; + + m_pIfBuf = GetBufferPtrsReq.pRing3Buf; + AssertReturn(m_pIfBuf != NULL, VERR_GENERAL_FAILURE); + + return VINF_SUCCESS; +} + + +int VBoxNetDhcpd::ifActivate() +{ + AssertReturn(m_pSession != NIL_RTR0PTR, VERR_GENERAL_FAILURE); + AssertReturn(m_hIf != INTNET_HANDLE_INVALID, VERR_GENERAL_FAILURE); + AssertReturn(m_pIfBuf != NULL, VERR_GENERAL_FAILURE); + + INTNETIFSETACTIVEREQ ActiveReq; + int rc; + + ActiveReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + ActiveReq.Hdr.cbReq = sizeof(ActiveReq); + ActiveReq.pSession = m_pSession; + ActiveReq.hIf = m_hIf; + + ActiveReq.fActive = 1; + + rc = CALL_VMMR0(VMMR0_DO_INTNET_IF_SET_ACTIVE, ActiveReq); + return rc; +} + + +void VBoxNetDhcpd::ifPump() +{ + for (;;) + { + int rc = ifWait(); + + if (RT_UNLIKELY(rc == VERR_INTERRUPTED)) + continue; + +#if 0 /* we wait indefinitely */ + if (rc == VERR_TIMEOUT) + ...; +#endif + + if (RT_FAILURE(rc)) + return; + + ifProcessInput(); + } +} + + +int VBoxNetDhcpd::ifWait(uint32_t cMillies) +{ + AssertReturn(m_pSession != NIL_RTR0PTR, VERR_GENERAL_FAILURE); + AssertReturn(m_hIf != INTNET_HANDLE_INVALID, VERR_GENERAL_FAILURE); + + INTNETIFWAITREQ WaitReq; + int rc; + + WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + WaitReq.Hdr.cbReq = sizeof(WaitReq); + WaitReq.pSession = m_pSession; + WaitReq.hIf = m_hIf; + + WaitReq.cMillies = cMillies; + + rc = CALL_VMMR0(VMMR0_DO_INTNET_IF_WAIT, WaitReq); + return rc; +} + + +int VBoxNetDhcpd::ifProcessInput() +{ + AssertReturn(m_pSession != NIL_RTR0PTR, VERR_GENERAL_FAILURE); + AssertReturn(m_hIf != INTNET_HANDLE_INVALID, VERR_GENERAL_FAILURE); + AssertReturn(m_pIfBuf != NULL, VERR_GENERAL_FAILURE); + + for (PCINTNETHDR pHdr; + (pHdr = IntNetRingGetNextFrameToRead(&m_pIfBuf->Recv)) != NULL; + IntNetRingSkipFrame(&m_pIfBuf->Recv)) + { + const uint8_t u8Type = pHdr->u8Type; + void *pvSegFrame; + uint32_t cbSegFrame; + + if (u8Type == INTNETHDR_TYPE_FRAME) + { + pvSegFrame = IntNetHdrGetFramePtr(pHdr, m_pIfBuf); + cbSegFrame = pHdr->cbFrame; + + ifInput(pvSegFrame, cbSegFrame); + } + else if (u8Type == INTNETHDR_TYPE_GSO) + { + PCPDMNETWORKGSO pGso; + size_t cbGso = pHdr->cbFrame; + size_t cbFrame = cbGso - sizeof(PDMNETWORKGSO); + + pGso = IntNetHdrGetGsoContext(pHdr, m_pIfBuf); + if (!PDMNetGsoIsValid(pGso, cbGso, cbFrame)) + continue; + + const uint32_t cSegs = PDMNetGsoCalcSegmentCount(pGso, cbFrame); + for (uint32_t i = 0; i < cSegs; ++i) + { + uint8_t abHdrScratch[256]; + pvSegFrame = PDMNetGsoCarveSegmentQD(pGso, (uint8_t *)(pGso + 1), cbFrame, + abHdrScratch, + i, cSegs, + &cbSegFrame); + ifInput(pvSegFrame, (uint32_t)cbFrame); + } + } + } + + return VINF_SUCCESS; +} + + +/* + * Got a frame from the internal network, feed it to the lwIP stack. + */ +int VBoxNetDhcpd::ifInput(void *pvFrame, uint32_t cbFrame) +{ + if (pvFrame == NULL) + return VERR_INVALID_PARAMETER; + + if ( cbFrame <= sizeof(RTNETETHERHDR) + || cbFrame > UINT16_MAX - ETH_PAD_SIZE) + return VERR_INVALID_PARAMETER; + + struct pbuf *p = pbuf_alloc(PBUF_RAW, (u16_t)cbFrame + ETH_PAD_SIZE, PBUF_POOL); + if (RT_UNLIKELY(p == NULL)) + return VERR_NO_MEMORY; + + /* + * The code below is inlined version of: + * + * pbuf_header(p, -ETH_PAD_SIZE); // hide padding + * pbuf_take(p, pvFrame, cbFrame); + * pbuf_header(p, ETH_PAD_SIZE); // reveal padding + */ + struct pbuf *q = p; + uint8_t *pu8Chunk = (uint8_t *)pvFrame; + do { + uint8_t *payload = (uint8_t *)q->payload; + size_t len = q->len; + +#if ETH_PAD_SIZE + if (RT_LIKELY(q == p)) /* single pbuf is large enough */ + { + payload += ETH_PAD_SIZE; + len -= ETH_PAD_SIZE; + } +#endif + memcpy(payload, pu8Chunk, len); + pu8Chunk += len; + q = q->next; + } while (RT_UNLIKELY(q != NULL)); + + m_LwipNetif.input(p, &m_LwipNetif); + return VINF_SUCCESS; +} + + +/* + * Got a frame from the lwIP stack, feed it to the internal network. + */ +err_t VBoxNetDhcpd::netifLinkOutput(pbuf *pPBuf) +{ + PINTNETHDR pHdr; + void *pvFrame; + u16_t cbFrame; + int rc; + + if (pPBuf->tot_len < sizeof(struct eth_hdr)) /* includes ETH_PAD_SIZE */ + return ERR_ARG; + + cbFrame = pPBuf->tot_len - ETH_PAD_SIZE; + rc = IntNetRingAllocateFrame(&m_pIfBuf->Send, cbFrame, &pHdr, &pvFrame); + if (RT_FAILURE(rc)) + return ERR_MEM; + + pbuf_copy_partial(pPBuf, pvFrame, cbFrame, ETH_PAD_SIZE); + IntNetRingCommitFrameEx(&m_pIfBuf->Send, pHdr, cbFrame); + + ifFlush(); + return ERR_OK; +} + + +int VBoxNetDhcpd::ifFlush() +{ + INTNETIFSENDREQ SendReq; + int rc; + + SendReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + SendReq.Hdr.cbReq = sizeof(SendReq); + SendReq.pSession = m_pSession; + + SendReq.hIf = m_hIf; + + rc = CALL_VMMR0(VMMR0_DO_INTNET_IF_SEND, SendReq); + return rc; +} + + +int VBoxNetDhcpd::ifClose() +{ + if (m_hIf == INTNET_HANDLE_INVALID) + return VINF_SUCCESS; + + INTNETIFCLOSEREQ CloseReq; + + CloseReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + CloseReq.Hdr.cbReq = sizeof(CloseReq); + CloseReq.pSession = m_pSession; + + CloseReq.hIf = m_hIf; + + m_hIf = INTNET_HANDLE_INVALID; + m_pIfBuf = NULL; + + CALL_VMMR0(VMMR0_DO_INTNET_IF_CLOSE, CloseReq); + return VINF_SUCCESS; +} + + +/* static */ DECLCALLBACK(void) VBoxNetDhcpd::lwipInitCB(void *pvArg) +{ + AssertPtrReturnVoid(pvArg); + + VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(pvArg); + self->lwipInit(); +} + + +/* static */ err_t VBoxNetDhcpd::netifInitCB(netif *pNetif) +{ + AssertPtrReturn(pNetif, ERR_ARG); + + VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(pNetif->state); + return self->netifInit(pNetif); +} + + +/* static */ err_t VBoxNetDhcpd::netifLinkOutputCB(netif *pNetif, pbuf *pPBuf) +{ + AssertPtrReturn(pNetif, ERR_ARG); + AssertPtrReturn(pPBuf, ERR_ARG); + + VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(pNetif->state); + AssertPtrReturn(self, ERR_IF); + + return self->netifLinkOutput(pPBuf); +} + + +/* static */ void VBoxNetDhcpd::dhcp4RecvCB(void *arg, struct udp_pcb *pcb, + struct pbuf *p, + ip_addr_t *addr, u16_t port) +{ + AssertPtrReturnVoid(arg); + + VBoxNetDhcpd *self = static_cast<VBoxNetDhcpd *>(arg); + self->dhcp4Recv(pcb, p, addr, port); + pbuf_free(p); +} + + + + + +int VBoxNetDhcpd::main(int argc, char **argv) +{ + int rc; + + ClientId::registerFormat(); + + /* XXX: We no longer need hardcoded and compat methods. We should remove them soon. */ + if (argc < 2) + m_Config = Config::hardcoded(); + else if ( strcmp(argv[1], "--config") == 0 + || strcmp(argv[1], "--comment") == 0) + m_Config = Config::create(argc, argv); + else + m_Config = Config::compat(argc, argv); + + if (m_Config == NULL) + return VERR_GENERAL_FAILURE; + + rc = m_server.init(m_Config); + + /* connect to the intnet */ + rc = ifInit(m_Config->getNetwork(), + m_Config->getTrunk(), + m_Config->getTrunkType()); + if (RT_FAILURE(rc)) + return rc; + + /* setup lwip */ + rc = vboxLwipCoreInitialize(lwipInitCB, this); + if (RT_FAILURE(rc)) + return rc; + + ifPump(); + return VINF_SUCCESS; +} + + +void VBoxNetDhcpd::lwipInit() +{ + err_t error; + + ip_addr_t addr, mask; + ip4_addr_set_u32(&addr, m_Config->getIPv4Address().u); + ip4_addr_set_u32(&mask, m_Config->getIPv4Netmask().u); + + netif *pNetif = netif_add(&m_LwipNetif, + &addr, &mask, + IP_ADDR_ANY, /* gateway */ + this, /* state */ + VBoxNetDhcpd::netifInitCB, /* netif_init_fn */ + tcpip_input); /* netif_input_fn */ + if (pNetif == NULL) + return; + + netif_set_up(pNetif); + netif_set_link_up(pNetif); + + m_Dhcp4Pcb = udp_new(); + if (RT_UNLIKELY(m_Dhcp4Pcb == NULL)) + return; /* XXX? */ + + ip_set_option(m_Dhcp4Pcb, SOF_BROADCAST); + udp_recv(m_Dhcp4Pcb, dhcp4RecvCB, this); + + error = udp_bind(m_Dhcp4Pcb, IP_ADDR_ANY, RTNETIPV4_PORT_BOOTPS); + if (error != ERR_OK) + { + udp_remove(m_Dhcp4Pcb); + m_Dhcp4Pcb = NULL; + return; /* XXX? */ + } +} + + +err_t VBoxNetDhcpd::netifInit(netif *pNetif) +{ + pNetif->hwaddr_len = sizeof(RTMAC); + memcpy(pNetif->hwaddr, &m_Config->getMacAddress(), sizeof(RTMAC)); + + pNetif->mtu = 1500; + + pNetif->flags = NETIF_FLAG_BROADCAST + | NETIF_FLAG_ETHARP + | NETIF_FLAG_ETHERNET; + + pNetif->linkoutput = netifLinkOutputCB; + pNetif->output = etharp_output; + + netif_set_default(pNetif); + return ERR_OK; +} + + +void VBoxNetDhcpd::dhcp4Recv(struct udp_pcb *pcb, struct pbuf *p, + ip_addr_t *addr, u16_t port) +{ + err_t error; + int rc; + + RT_NOREF(pcb, addr, port); + + if (RT_UNLIKELY(p->next != NULL)) + return; /* XXX: we want it in one chunk */ + + bool broadcasted = ip_addr_cmp(ip_current_dest_addr(), &ip_addr_broadcast) + || ip_addr_cmp(ip_current_dest_addr(), &ip_addr_any); + + DhcpClientMessage *msgIn = DhcpClientMessage::parse(broadcasted, p->payload, p->len); + if (msgIn == NULL) + return; + + std::unique_ptr<DhcpClientMessage> autoFreeMsgIn(msgIn); + + DhcpServerMessage *msgOut = m_server.process(*msgIn); + if (msgOut == NULL) + return; + + std::unique_ptr<DhcpServerMessage> autoFreeMsgOut(msgOut); + + ip_addr_t dst = { msgOut->dst().u }; + if (ip_addr_cmp(&dst, &ip_addr_any)) + ip_addr_copy(dst, ip_addr_broadcast); + + octets_t data; + rc = msgOut->encode(data); + if (RT_FAILURE(rc)) + return; + + unique_ptr_pbuf q ( pbuf_alloc(PBUF_RAW, (u16_t)data.size(), PBUF_RAM) ); + if (!q) + return; + + error = pbuf_take(q.get(), &data.front(), (u16_t)data.size()); + if (error != ERR_OK) + return; + + error = udp_sendto(pcb, q.get(), &dst, RTNETIPV4_PORT_BOOTPC); + if (error != ERR_OK) + return; +} + + + + +/* + * Entry point. + */ +extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv) +{ + VBoxNetDhcpd Dhcpd; + int rc = Dhcpd.main(argc, argv); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +#ifndef VBOX_WITH_HARDENING + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + return TrustedMain(argc, argv); +} + + +# ifdef RT_OS_WINDOWS +/** (We don't want a console usually.) */ +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nCmdShow); + + return main(__argc, __argv); +} +# endif /* RT_OS_WINDOWS */ + +#endif /* !VBOX_WITH_HARDENING */ diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.rc b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.rc new file mode 100644 index 00000000..fa6b3105 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.rc @@ -0,0 +1,55 @@ +/* $Id: VBoxNetDhcpd.rc $ */ +/** @file + * VBoxNetDHCP - Resource file containing version info. + */ + +/* + * Copyright (C) 2015-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 <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileDescription", "VirtualBox DHCP Server\0" + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "InternalName", "VBoxNetDhcpd\0" + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "OriginalFilename", "VBoxNetDhcpd.dll\0" + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +/* Creates the application icon. */ +#include "VBoxNetDhcpd-icon.rc" + diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp new file mode 100644 index 00000000..26689e20 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp @@ -0,0 +1,25 @@ +/* $Id: VBoxNetDhcpdHardened.cpp $ */ +/** @file + * VBoxNetDhcpd - Hardened main(). + */ + +/* + * Copyright (C) 2009-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 <VBox/sup.h> + + +int main(int argc, char **argv, char **envp) +{ + return SUPR3HardenedMain("VBoxNetDHCP", 0 /* fFlags */, argc, argv, envp); +} + diff --git a/src/VBox/NetworkServices/Dhcpd/lwipopts.h b/src/VBox/NetworkServices/Dhcpd/lwipopts.h new file mode 100644 index 00000000..2fd714b0 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/lwipopts.h @@ -0,0 +1,181 @@ +/* $Id: lwipopts.h $ */ +/** @file + * DHCP server - lwIP configuration options. + */ + +/* + * Copyright (C) 2013-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. + */ + +#ifndef VBOX_INCLUDED_SRC_Dhcpd_lwipopts_h +#define VBOX_INCLUDED_SRC_Dhcpd_lwipopts_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> /* For VBOX_STRICT. */ +#include <iprt/mem.h> +#include <iprt/alloca.h> /* This may include malloc.h (msc), which is something that has + * to be done before redefining any of the functions therein. */ +#include <iprt/rand.h> /* see LWIP_RAND() definition */ + +/** Make lwIP use the libc malloc, or more precisely (see below) the IPRT + * memory allocation functions. */ +#define MEM_LIBC_MALLOC 1 + +/** Set proper memory alignment. */ +#if HC_ARCH_BITS == 64 +# define MEM_ALIGNMENT 8 +#else +#define MEM_ALIGNMENT 4 +#endif + +/* Padding before Ethernet header to make IP header aligned */ +#define ETH_PAD_SIZE 2 + +/* IP */ +#define IP_REASSEMBLY 1 +#define IP_REASS_MAX_PBUFS 128 + + + +/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application + sends a lot of data out of ROM (or other static memory), this + should be set high. + + NB: This is for PBUF_ROM and PBUF_REF pbufs only! + + Number of PBUF_POOL pbufs is controlled by PBUF_POOL_SIZE that, + somewhat confusingly, breaks MEMP_NUM_* pattern. + + PBUF_RAM pbufs are allocated with mem_malloc (with MEM_LIBC_MALLOC + set to 1 this is just system malloc), not memp_malloc. */ +#define MEMP_NUM_PBUF (1024 * 4) + + +/* MEMP_NUM_MLD6_GROUP: Maximum number of IPv6 multicast groups that + can be joined. + + We need to be able to join solicited node multicast for each + address (potentially different) and two groups for DHCP6. All + routers multicast is hardcoded in ip6.c and does not require + explicit joining. Provide also for a few extra groups just in + case. */ +#define MEMP_NUM_MLD6_GROUP (LWIP_IPV6_NUM_ADDRESSES + /* dhcp6 */ 2 + /* extra */ 8) + + +/* MEMP_NUM_TCPIP_MSG_*: the number of struct tcpip_msg, which is used + for sequential API communication and incoming packets. Used in + src/api/tcpip.c. */ +#define MEMP_NUM_TCPIP_MSG_API 128 +#define MEMP_NUM_TCPIP_MSG_INPKT 1024 + +/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One + per active UDP "connection". */ +#define MEMP_NUM_UDP_PCB 32 + +/* Pbuf options */ +/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. + This is only for PBUF_POOL pbufs, primarily used by netif drivers. + + This should have been named with the MEMP_NUM_ prefix (cf. + MEMP_NUM_PBUF for PBUF_ROM and PBUF_REF) as it controls the size of + yet another memp_malloc() pool. */ +#define PBUF_POOL_SIZE (1024 * 4) + +/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. + Use default that is based on TCP_MSS and PBUF_LINK_HLEN. */ +#undef PBUF_POOL_BUFSIZE + +/** Turn on support for lightweight critical region protection. Leaving this + * off uses synchronization code in pbuf.c which is totally polluted with + * races. All the other lwip source files would fall back to semaphore-based + * synchronization, but pbuf.c is just broken, leading to incorrect allocation + * and as a result to assertions due to buffers being double freed. */ +#define SYS_LIGHTWEIGHT_PROT 1 + +/** Attempt to get rid of htons etc. macro issues. */ +#undef LWIP_PREFIX_BYTEORDER_FUNCS + +#define LWIP_TCPIP_CORE_LOCKING_INPUT 0 +#define LWIP_TCPIP_CORE_LOCKING 0 + +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 +#define LWIP_COMPAT_SOCKETS 0 +#define LWIP_COMPAT_MUTEX 1 + +#define LWIP_TCP 0 +#define LWI_UDP 1 +#define LWIP_ARP 1 +#define ARP_PROXY 0 +#define LWIP_ETHERNET 1 + +/* accept any->broadcast */ +#define LWIP_IP_ACCEPT_UDP_PORT(port) ((port) == PP_NTOHS(/*DHCP_SERVER_PORT*/ 67)) + +#define LWIP_IPV6 0 +#define LWIP_IPV6_FORWARD 0 +#define LWIP_ND6_PROXY 0 + +#define LWIP_ND6_ALLOW_RA_UPDATES (!LWIP_IPV6_FORWARD) +#define LWIP_IPV6_SEND_ROUTER_SOLICIT (!LWIP_IPV6_FORWARD) +/* IPv6 autoconfig we don't need in proxy, but it required for very seldom cases + * iSCSI over intnet with IPv6 + */ +#define LWIP_IPV6_AUTOCONFIG 1 +#if LWIP_IPV6_FORWARD /* otherwise use the default from lwip/opt.h */ +#define LWIP_IPV6_DUP_DETECT_ATTEMPTS 0 +#endif + +#define LWIP_IPV6_FRAG 1 + +/** + * aka Slirp mode. + */ +#define LWIP_CONNECTION_PROXY 0 +#define IP_FORWARD 0 + +/* MEMP_NUM_SYS_TIMEOUT: the number of simultaneously active + timeouts. */ +#define MEMP_NUM_SYS_TIMEOUT 16 + + +/* this is required for IPv6 and IGMP needs */ +#define LWIP_RAND() RTRandU32() + +/* Debugging stuff. */ +#ifdef DEBUG +# define LWIP_DEBUG +# include "lwip-log.h" + +# define LWIP_PROXY_DEBUG LWIP_DBG_OFF +#endif /* DEBUG */ + +/* printf formatter definitions */ +#define U16_F "hu" +#define S16_F "hd" +#define X16_F "hx" +#define U32_F "u" +#define S32_F "d" +#define X32_F "x" + +/* Redirect libc memory alloc functions to IPRT. */ +#define malloc(x) RTMemAlloc(x) +#define realloc(x,y) RTMemRealloc((x), (y)) +#define free(x) RTMemFree(x) + +/* Align VBOX_STRICT and LWIP_NOASSERT. */ +#ifndef VBOX_STRICT +# define LWIP_NOASSERT 1 +#endif + +#endif /* !VBOX_INCLUDED_SRC_Dhcpd_lwipopts_h */ |