summaryrefslogtreecommitdiffstats
path: root/src/VBox/NetworkServices/Dhcpd
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/NetworkServices/Dhcpd')
-rw-r--r--src/VBox/NetworkServices/Dhcpd/ClientId.cpp122
-rw-r--r--src/VBox/NetworkServices/Dhcpd/ClientId.h70
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Config.cpp949
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Config.h110
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DHCPD.cpp286
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DHCPD.h65
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Db.cpp747
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Db.h162
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Defs.h62
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp412
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpMessage.h138
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpOptions.cpp242
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpOptions.h642
-rw-r--r--src/VBox/NetworkServices/Dhcpd/IPv4Pool.cpp138
-rw-r--r--src/VBox/NetworkServices/Dhcpd/IPv4Pool.h126
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Makefile.kmk93
-rw-r--r--src/VBox/NetworkServices/Dhcpd/TimeStamp.cpp36
-rw-r--r--src/VBox/NetworkServices/Dhcpd/TimeStamp.h101
-rw-r--r--src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp805
-rw-r--r--src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.rc55
-rw-r--r--src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp25
-rw-r--r--src/VBox/NetworkServices/Dhcpd/lwipopts.h181
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 */