diff options
Diffstat (limited to 'src/VBox/NetworkServices/DHCP')
-rw-r--r-- | src/VBox/NetworkServices/DHCP/ClientDataInt.h | 70 | ||||
-rw-r--r-- | src/VBox/NetworkServices/DHCP/Config.cpp | 1493 | ||||
-rw-r--r-- | src/VBox/NetworkServices/DHCP/Config.h | 845 | ||||
-rw-r--r-- | src/VBox/NetworkServices/DHCP/Makefile.kmk | 72 | ||||
-rw-r--r-- | src/VBox/NetworkServices/DHCP/NetworkManagerDhcp.cpp | 189 | ||||
-rw-r--r-- | src/VBox/NetworkServices/DHCP/README.customoptions | 23 | ||||
-rw-r--r-- | src/VBox/NetworkServices/DHCP/VBoxNetDHCP.cpp | 885 | ||||
-rw-r--r-- | src/VBox/NetworkServices/DHCP/VBoxNetDHCP.rc | 55 | ||||
-rw-r--r-- | src/VBox/NetworkServices/DHCP/VBoxNetDHCPHardened.cpp | 25 |
9 files changed, 3657 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/DHCP/ClientDataInt.h b/src/VBox/NetworkServices/DHCP/ClientDataInt.h new file mode 100644 index 00000000..74879523 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/ClientDataInt.h @@ -0,0 +1,70 @@ +/* $Id: ClientDataInt.h $ */ +/** @file + * Config.h + */ + +/* + * 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_DHCP_ClientDataInt_h +#define VBOX_INCLUDED_SRC_DHCP_ClientDataInt_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +class ClientData +{ +public: + ClientData() + { + m_address.u = 0; + m_network.u = 0; + fHasLease = false; + fHasClient = false; + fBinding = true; + u64TimestampBindingStarted = 0; + u64TimestampLeasingStarted = 0; + u32LeaseExpirationPeriod = 0; + u32BindExpirationPeriod = 0; + pCfg = NULL; + + } + ~ClientData(){} + + /* client information */ + RTNETADDRIPV4 m_address; + RTNETADDRIPV4 m_network; + RTMAC m_mac; + + bool fHasClient; + + /* Lease part */ + bool fHasLease; + /** lease isn't commited */ + bool fBinding; + + /** Timestamp when lease commited. */ + uint64_t u64TimestampLeasingStarted; + /** Period when lease is expired in secs. */ + uint32_t u32LeaseExpirationPeriod; + + /** timestamp when lease was bound */ + uint64_t u64TimestampBindingStarted; + /* Period when binding is expired in secs. */ + uint32_t u32BindExpirationPeriod; + + MapOptionId2RawOption options; + + NetworkConfigEntity *pCfg; +}; + +#endif /* !VBOX_INCLUDED_SRC_DHCP_ClientDataInt_h */ diff --git a/src/VBox/NetworkServices/DHCP/Config.cpp b/src/VBox/NetworkServices/DHCP/Config.cpp new file mode 100644 index 00000000..b1d09c78 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/Config.cpp @@ -0,0 +1,1493 @@ +/* $Id: Config.cpp $ */ +/** @file + * Configuration for DHCP. + */ + +/* + * 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. + */ + + +/** + * XXX: license. + */ + +#include <iprt/asm.h> +#include <iprt/getopt.h> +#include <iprt/net.h> +#include <iprt/time.h> + +#include <VBox/sup.h> +#include <VBox/intnet.h> +#include <VBox/intnetinline.h> +#include <VBox/vmm/vmm.h> +#include <VBox/version.h> + +#include <VBox/com/array.h> +#include <VBox/com/string.h> + +#include <iprt/cpp/xml.h> + +#define BASE_SERVICES_ONLY +#include "../NetLib/VBoxNetBaseService.h" +#include "../NetLib/VBoxNetLib.h" +#include "../NetLib/shared_ptr.h" + +#include <list> +#include <vector> +#include <map> +#include <string> + +#include "Config.h" +#include "ClientDataInt.h" + +bool operator== (const Lease& lhs, const Lease& rhs) +{ + return (lhs.m.get() == rhs.m.get()); +} + + +bool operator!= (const Lease& lhs, const Lease& rhs) +{ + return !(lhs == rhs); +} + + +bool operator< (const Lease& lhs, const Lease& rhs) +{ + return ( (lhs.getAddress() < rhs.getAddress()) + || (lhs.issued() < rhs.issued())); +} +/* consts */ + +const NullConfigEntity *g_NullConfig = new NullConfigEntity(); +RootConfigEntity *g_RootConfig = new RootConfigEntity(std::string("ROOT"), 1200 /* 20 min. */); +const ClientMatchCriteria *g_AnyClient = new AnyClientMatchCriteria(); + +static ConfigurationManager *g_ConfigurationManager = ConfigurationManager::getConfigurationManager(); + +NetworkManager *NetworkManager::g_NetworkManager; + +bool MACClientMatchCriteria::check(const Client& client) const +{ + return (client == m_mac); +} + + +int BaseConfigEntity::match(Client& client, BaseConfigEntity **cfg) +{ + int iMatch = (m_criteria && m_criteria->check(client) ? m_MatchLevel : 0); + if (m_children.empty()) + { + if (iMatch > 0) + { + *cfg = this; + return iMatch; + } + } + else + { + *cfg = this; + /* XXX: hack */ + BaseConfigEntity *matching = this; + int matchingLevel = m_MatchLevel; + + for (std::vector<BaseConfigEntity *>::iterator it = m_children.begin(); + it != m_children.end(); + ++it) + { + iMatch = (*it)->match(client, &matching); + if (iMatch > matchingLevel) + { + *cfg = matching; + matchingLevel = iMatch; + } + } + return matchingLevel; + } + return iMatch; +} + +/* Client */ +/* Configs + NetworkConfigEntity(std::string name, + ConfigEntity* pCfg, + ClientMatchCriteria* criteria, + RTNETADDRIPV4& networkID, + RTNETADDRIPV4& networkMask) +*/ +static const RTNETADDRIPV4 g_AnyIpv4 = {0}; +static const RTNETADDRIPV4 g_AllIpv4 = {0xffffffff}; +RootConfigEntity::RootConfigEntity(std::string name, uint32_t expPeriod): + NetworkConfigEntity(name, g_NullConfig, g_AnyClient, g_AnyIpv4, g_AllIpv4) +{ + m_MatchLevel = 2; + m_u32ExpirationPeriod = expPeriod; +} + +/* Configuration Manager */ +struct ConfigurationManager::Data +{ + Data():fFileExists(false){} + + MapLease2Ip4Address m_allocations; + Ipv4AddressContainer m_nameservers; + Ipv4AddressContainer m_routers; + + std::string m_domainName; + VecClient m_clients; + com::Utf8Str m_leaseStorageFilename; + bool fFileExists; +}; + +ConfigurationManager *ConfigurationManager::getConfigurationManager() +{ + if (!g_ConfigurationManager) + + + { + g_ConfigurationManager = new ConfigurationManager(); + g_ConfigurationManager->init(); + } + + return g_ConfigurationManager; +} + + +const std::string tagXMLLeases = "Leases"; +const std::string tagXMLLeasesAttributeVersion = "version"; +const std::string tagXMLLeasesVersion_1_0 = "1.0"; +const std::string tagXMLLease = "Lease"; +const std::string tagXMLLeaseAttributeMac = "mac"; +const std::string tagXMLLeaseAttributeNetwork = "network"; +const std::string tagXMLLeaseAddress = "Address"; +const std::string tagXMLAddressAttributeValue = "value"; +const std::string tagXMLLeaseTime = "Time"; +const std::string tagXMLTimeAttributeIssued = "issued"; +const std::string tagXMLTimeAttributeExpiration = "expiration"; +const std::string tagXMLLeaseOptions = "Options"; + +/** + * @verbatim + <Leases version="1.0"> + <Lease mac="" network=""/> + <Address value=""/> + <Time issued="" expiration=""/> + <options> + <option name="" type=""/> + </option> + </options> + </Lease> + </Leases> + @endverbatim + */ +int ConfigurationManager::loadFromFile(const com::Utf8Str& leaseStorageFileName) +{ + m->m_leaseStorageFilename = leaseStorageFileName; + + xml::XmlFileParser parser; + xml::Document doc; + + try { + parser.read(m->m_leaseStorageFilename.c_str(), doc); + } + catch (...) + { + return VINF_SUCCESS; + } + + /* XML parsing */ + xml::ElementNode *root = doc.getRootElement(); + + if (!root || !root->nameEquals(tagXMLLeases.c_str())) + { + m->fFileExists = false; + return VERR_NOT_FOUND; + } + + com::Utf8Str version; + if (root) + root->getAttributeValue(tagXMLLeasesAttributeVersion.c_str(), version); + + /* XXX: version check */ + xml::NodesLoop leases(*root); + + const xml::ElementNode *lease; + while ((lease = leases.forAllNodes())) + { + if (!lease->nameEquals(tagXMLLease.c_str())) + continue; + + ClientData *data = new ClientData(); + Lease l(data); + if (l.fromXML(lease)) + { + + m->m_allocations.insert(MapLease2Ip4AddressPair(l, l.getAddress())); + + + NetworkConfigEntity *pNetCfg = NULL; + Client c(data); + int rc = g_RootConfig->match(c, (BaseConfigEntity **)&pNetCfg); + Assert(rc >= 0 && pNetCfg); RT_NOREF(rc); + + l.setConfig(pNetCfg); + + m->m_clients.push_back(c); + } + } + + return VINF_SUCCESS; +} + + +int ConfigurationManager::saveToFile() +{ + if (m->m_leaseStorageFilename.isEmpty()) + return VINF_SUCCESS; + + xml::Document doc; + + xml::ElementNode *root = doc.createRootElement(tagXMLLeases.c_str()); + if (!root) + return VERR_INTERNAL_ERROR; + + root->setAttribute(tagXMLLeasesAttributeVersion.c_str(), tagXMLLeasesVersion_1_0.c_str()); + + for(MapLease2Ip4AddressConstIterator it = m->m_allocations.begin(); + it != m->m_allocations.end(); ++it) + { + xml::ElementNode *lease = root->createChild(tagXMLLease.c_str()); + if (!it->first.toXML(lease)) + { + /* XXX: todo logging + error handling */ + } + } + + try { + xml::XmlFileWriter writer(doc); + writer.write(m->m_leaseStorageFilename.c_str(), true); + } catch(...){} + + return VINF_SUCCESS; +} + + +int ConfigurationManager::extractRequestList(PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg, RawOption& rawOpt) +{ + return ConfigurationManager::findOption(RTNET_DHCP_OPT_PARAM_REQ_LIST, pDhcpMsg, cbDhcpMsg, rawOpt); +} + + +Client ConfigurationManager::getClientByDhcpPacket(const RTNETBOOTP *pDhcpMsg, size_t cbDhcpMsg) +{ + + VecClientIterator it; + bool fDhcpValid = false; + uint8_t uMsgType = 0; + + fDhcpValid = RTNetIPv4IsDHCPValid(NULL, pDhcpMsg, cbDhcpMsg, &uMsgType); + AssertReturn(fDhcpValid, Client::NullClient); + + LogFlowFunc(("dhcp:mac:%RTmac\n", &pDhcpMsg->bp_chaddr.Mac)); + /* 1st. client IDs */ + for ( it = m->m_clients.begin(); + it != m->m_clients.end(); + ++it) + { + if ((*it) == pDhcpMsg->bp_chaddr.Mac) + { + LogFlowFunc(("client:mac:%RTmac\n", it->getMacAddress())); + /* check timestamp that request wasn't expired. */ + return (*it); + } + } + + if (it == m->m_clients.end()) + { + /* We hasn't got any session for this client */ + Client c; + c.initWithMac(pDhcpMsg->bp_chaddr.Mac); + m->m_clients.push_back(c); + return m->m_clients.back(); + } + + return Client::NullClient; +} + +/** + * Finds an option. + * + * @returns On success, a pointer to the first byte in the option data (no none + * then it'll be the byte following the 0 size field) and *pcbOpt set + * to the option length. + * On failure, NULL is returned and *pcbOpt unchanged. + * + * @param uOption The option to search for. + * @param pDhcpMsg The DHCP message. + * that this is adjusted if the option length is larger + * than the message buffer. + * @param cbDhcpMsg Size of the DHCP message. + * @param opt The actual option we found. + */ +int +ConfigurationManager::findOption(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg, RawOption& opt) +{ + Assert(uOption != RTNET_DHCP_OPT_PAD); + Assert(uOption != RTNET_DHCP_OPT_END); + + /* + * Validate the DHCP bits and figure the max size of the options in the vendor field. + */ + if (cbDhcpMsg <= RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts)) + return VERR_INVALID_PARAMETER; + + if (pDhcpMsg->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32_C(RTNET_DHCP_COOKIE)) + return VERR_INVALID_PARAMETER; + + size_t cbLeft = cbDhcpMsg - RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts); + if (cbLeft > RTNET_DHCP_OPT_SIZE) + cbLeft = RTNET_DHCP_OPT_SIZE; + + /* + * Search the vendor field. + */ + uint8_t const *pb = &pDhcpMsg->bp_vend.Dhcp.dhcp_opts[0]; + while (pb && cbLeft > 0) + { + uint8_t uCur = *pb; + if (uCur == RTNET_DHCP_OPT_PAD) + { + cbLeft--; + pb++; + } + else if (uCur == RTNET_DHCP_OPT_END) + break; + else if (cbLeft <= 1) + break; + else + { + uint8_t cbCur = pb[1]; + if (cbCur > cbLeft - 2) + cbCur = (uint8_t)(cbLeft - 2); + if (uCur == uOption) + { + opt.u8OptId = uCur; + memcpy(opt.au8RawOpt, pb+2, cbCur); + opt.cbRawOpt = cbCur; + return VINF_SUCCESS; + } + pb += cbCur + 2; + cbLeft -= cbCur + 2; + } + } + + /** @todo search extended dhcp option field(s) when present */ + + return VERR_NOT_FOUND; +} + + +/** + * We bind lease for client till it continue with it on DHCPREQUEST. + */ +Lease ConfigurationManager::allocateLease4Client(const Client& client, PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg) +{ + { + /** + * This mean that client has already bound or commited lease. + * If we've it happens it means that we received DHCPDISCOVER twice. + */ + const Lease l = client.lease(); + if (l != Lease::NullLease) + { + /* Here we should take lease from the m_allocation which was feed with leases + * on start + */ + if (l.isExpired()) + { + expireLease4Client(const_cast<Client&>(client)); + if (!l.isExpired()) + return l; + } + else + { + AssertReturn(l.getAddress().u != 0, Lease::NullLease); + return l; + } + } + } + + RTNETADDRIPV4 hintAddress; + RawOption opt; + NetworkConfigEntity *pNetCfg; + + Client cl(client); + AssertReturn(g_RootConfig->match(cl, (BaseConfigEntity **)&pNetCfg) > 0, Lease::NullLease); + + /* DHCPDISCOVER MAY contain request address */ + hintAddress.u = 0; + int rc = findOption(RTNET_DHCP_OPT_REQ_ADDR, pDhcpMsg, cbDhcpMsg, opt); + if (RT_SUCCESS(rc)) + { + hintAddress.u = *(uint32_t *)opt.au8RawOpt; + if ( RT_H2N_U32(hintAddress.u) < RT_H2N_U32(pNetCfg->lowerIp().u) + || RT_H2N_U32(hintAddress.u) > RT_H2N_U32(pNetCfg->upperIp().u)) + hintAddress.u = 0; /* clear hint */ + } + + if ( hintAddress.u + && !isAddressTaken(hintAddress)) + { + Lease l(cl); + l.setConfig(pNetCfg); + l.setAddress(hintAddress); + m->m_allocations.insert(MapLease2Ip4AddressPair(l, hintAddress)); + return l; + } + + uint32_t u32 = 0; + for(u32 = RT_H2N_U32(pNetCfg->lowerIp().u); + u32 <= RT_H2N_U32(pNetCfg->upperIp().u); + ++u32) + { + RTNETADDRIPV4 address; + address.u = RT_H2N_U32(u32); + if (!isAddressTaken(address)) + { + Lease l(cl); + l.setConfig(pNetCfg); + l.setAddress(address); + m->m_allocations.insert(MapLease2Ip4AddressPair(l, address)); + return l; + } + } + + return Lease::NullLease; +} + + +int ConfigurationManager::commitLease4Client(Client& client) +{ + Lease l = client.lease(); + AssertReturn(l != Lease::NullLease, VERR_INTERNAL_ERROR); + + l.bindingPhase(false); + const NetworkConfigEntity *pCfg = l.getConfig(); + + AssertPtr(pCfg); + l.setExpiration(pCfg->expirationPeriod()); + l.phaseStart(RTTimeMilliTS()); + + saveToFile(); + + return VINF_SUCCESS; +} + + +int ConfigurationManager::expireLease4Client(Client& client) +{ + Lease l = client.lease(); + AssertReturn(l != Lease::NullLease, VERR_INTERNAL_ERROR); + + if (l.isInBindingPhase()) + { + + MapLease2Ip4AddressIterator it = m->m_allocations.find(l); + AssertReturn(it != m->m_allocations.end(), VERR_NOT_FOUND); + + /* + * XXX: perhaps it better to keep this allocation ???? + */ + m->m_allocations.erase(it); + + l.expire(); + return VINF_SUCCESS; + } + + l = Lease(client); /* re-new */ + return VINF_SUCCESS; +} + + +bool ConfigurationManager::isAddressTaken(const RTNETADDRIPV4& addr, Lease& lease) +{ + MapLease2Ip4AddressIterator it; + + for (it = m->m_allocations.begin(); + it != m->m_allocations.end(); + ++it) + { + if (it->second.u == addr.u) + { + if (lease != Lease::NullLease) + lease = it->first; + + return true; + } + } + lease = Lease::NullLease; + return false; +} + + +bool ConfigurationManager::isAddressTaken(const RTNETADDRIPV4& addr) +{ + Lease ignore; + return isAddressTaken(addr, ignore); +} + + +NetworkConfigEntity *ConfigurationManager::addNetwork(NetworkConfigEntity *, + const RTNETADDRIPV4& networkId, + const RTNETADDRIPV4& netmask, + RTNETADDRIPV4& LowerAddress, + RTNETADDRIPV4& UpperAddress) +{ + static int id; + char name[64]; + + RTStrPrintf(name, RT_ELEMENTS(name), "network-%d", id); + std::string strname(name); + id++; + + + if (!LowerAddress.u) + LowerAddress = networkId; + + if (!UpperAddress.u) + UpperAddress.u = networkId.u | (~netmask.u); + + return new NetworkConfigEntity(strname, + g_RootConfig, + g_AnyClient, + 5, + networkId, + netmask, + LowerAddress, + UpperAddress); +} + +HostConfigEntity *ConfigurationManager::addHost(NetworkConfigEntity* pCfg, + const RTNETADDRIPV4& address, + ClientMatchCriteria *criteria) +{ + static int id; + char name[64]; + + RTStrPrintf(name, RT_ELEMENTS(name), "host-%d", id); + std::string strname(name); + id++; + + return new HostConfigEntity(address, strname, pCfg, criteria); +} + +int ConfigurationManager::addToAddressList(uint8_t u8OptId, RTNETADDRIPV4& address) +{ + switch(u8OptId) + { + case RTNET_DHCP_OPT_DNS: + m->m_nameservers.push_back(address); + break; + case RTNET_DHCP_OPT_ROUTERS: + m->m_routers.push_back(address); + break; + default: + Log(("dhcp-opt: list (%d) unsupported\n", u8OptId)); + } + return VINF_SUCCESS; +} + + +int ConfigurationManager::flushAddressList(uint8_t u8OptId) +{ + switch(u8OptId) + { + case RTNET_DHCP_OPT_DNS: + m->m_nameservers.clear(); + break; + case RTNET_DHCP_OPT_ROUTERS: + m->m_routers.clear(); + break; + default: + Log(("dhcp-opt: list (%d) unsupported\n", u8OptId)); + } + return VINF_SUCCESS; +} + + +const Ipv4AddressContainer& ConfigurationManager::getAddressList(uint8_t u8OptId) +{ + switch(u8OptId) + { + case RTNET_DHCP_OPT_DNS: + return m->m_nameservers; + + case RTNET_DHCP_OPT_ROUTERS: + return m->m_routers; + + } + /* XXX: Grrr !!! */ + return m_empty; +} + + +int ConfigurationManager::setString(uint8_t u8OptId, const std::string& str) +{ + switch (u8OptId) + { + case RTNET_DHCP_OPT_DOMAIN_NAME: + m->m_domainName = str; + break; + default: + break; + } + + return VINF_SUCCESS; +} + + +const std::string &ConfigurationManager::getString(uint8_t u8OptId) +{ + switch (u8OptId) + { + case RTNET_DHCP_OPT_DOMAIN_NAME: + if (m->m_domainName.length()) + return m->m_domainName; + return m_noString; + default: + break; + } + + return m_noString; +} + + +void ConfigurationManager::init() +{ + m = new ConfigurationManager::Data(); +} + + +ConfigurationManager::~ConfigurationManager() { if (m) delete m; } + +/** + * Network manager + */ +struct NetworkManager::Data +{ + Data() + { + RT_ZERO(BootPReplyMsg); + cbBooPReplyMsg = 0; + + m_OurAddress.u = 0; + m_OurNetmask.u = 0; + RT_ZERO(m_OurMac); + } + + union { + RTNETBOOTP BootPHeader; + uint8_t au8Storage[1024]; + } BootPReplyMsg; + int cbBooPReplyMsg; + + RTNETADDRIPV4 m_OurAddress; + RTNETADDRIPV4 m_OurNetmask; + RTMAC m_OurMac; + + ComPtr<IDHCPServer> m_DhcpServer; + const VBoxNetHlpUDPService *m_service; +}; + + +NetworkManager::NetworkManager():m(NULL) +{ + m = new NetworkManager::Data(); +} + + +NetworkManager::~NetworkManager() +{ + delete m; + m = NULL; +} + + +NetworkManager *NetworkManager::getNetworkManager(ComPtr<IDHCPServer> aDhcpServer) +{ + if (!g_NetworkManager) + { + g_NetworkManager = new NetworkManager(); + g_NetworkManager->m->m_DhcpServer = aDhcpServer; + } + + return g_NetworkManager; +} + + +const RTNETADDRIPV4& NetworkManager::getOurAddress() const +{ + return m->m_OurAddress; +} + + +const RTNETADDRIPV4& NetworkManager::getOurNetmask() const +{ + return m->m_OurNetmask; +} + + +const RTMAC& NetworkManager::getOurMac() const +{ + return m->m_OurMac; +} + + +void NetworkManager::setOurAddress(const RTNETADDRIPV4& aAddress) +{ + m->m_OurAddress = aAddress; +} + + +void NetworkManager::setOurNetmask(const RTNETADDRIPV4& aNetmask) +{ + m->m_OurNetmask = aNetmask; +} + + +void NetworkManager::setOurMac(const RTMAC& aMac) +{ + m->m_OurMac = aMac; +} + + +void NetworkManager::setService(const VBoxNetHlpUDPService *srv) +{ + m->m_service = srv; +} + +/** + * Network manager creates DHCPOFFER datagramm + */ +int NetworkManager::offer4Client(const Client& client, uint32_t u32Xid, + uint8_t *pu8ReqList, int cReqList) +{ + Lease l(client); /* XXX: oh, it looks badly, but now we have lease */ + prepareReplyPacket4Client(client, u32Xid); + + RTNETADDRIPV4 address = l.getAddress(); + m->BootPReplyMsg.BootPHeader.bp_yiaddr = address; + + /* Ubuntu ???*/ + m->BootPReplyMsg.BootPHeader.bp_ciaddr = address; + + /* options: + * - IP lease time + * - message type + * - server identifier + */ + RawOption opt; + RT_ZERO(opt); + + std::vector<RawOption> extra; + opt.u8OptId = RTNET_DHCP_OPT_MSG_TYPE; + opt.au8RawOpt[0] = RTNET_DHCP_MT_OFFER; + opt.cbRawOpt = 1; + extra.push_back(opt); + + opt.u8OptId = RTNET_DHCP_OPT_LEASE_TIME; + + const NetworkConfigEntity *pCfg = l.getConfig(); + AssertPtr(pCfg); + + *(uint32_t *)opt.au8RawOpt = RT_H2N_U32(pCfg->expirationPeriod()); + opt.cbRawOpt = sizeof(RTNETADDRIPV4); + + extra.push_back(opt); + + processParameterReqList(client, pu8ReqList, cReqList, extra); + + return doReply(client, extra); +} + +/** + * Network manager creates DHCPACK + */ +int NetworkManager::ack(const Client& client, uint32_t u32Xid, + uint8_t *pu8ReqList, int cReqList) +{ + RTNETADDRIPV4 address; + + prepareReplyPacket4Client(client, u32Xid); + + Lease l = client.lease(); + address = l.getAddress(); + m->BootPReplyMsg.BootPHeader.bp_ciaddr = address; + + + /* rfc2131 4.3.1 is about DHCPDISCOVER and this value is equal to ciaddr from + * DHCPREQUEST or 0 ... + * XXX: Using addressHint is not correct way to initialize [cy]iaddress... + */ + m->BootPReplyMsg.BootPHeader.bp_ciaddr = address; + m->BootPReplyMsg.BootPHeader.bp_yiaddr = address; + + Assert(m->BootPReplyMsg.BootPHeader.bp_yiaddr.u); + + /* options: + * - IP address lease time (if DHCPREQUEST) + * - message type + * - server identifier + */ + RawOption opt; + RT_ZERO(opt); + + std::vector<RawOption> extra; + opt.u8OptId = RTNET_DHCP_OPT_MSG_TYPE; + opt.au8RawOpt[0] = RTNET_DHCP_MT_ACK; + opt.cbRawOpt = 1; + extra.push_back(opt); + + /* + * XXX: lease time should be conditional. If on dhcprequest then tim should be provided, + * else on dhcpinform it mustn't. + */ + opt.u8OptId = RTNET_DHCP_OPT_LEASE_TIME; + *(uint32_t *)opt.au8RawOpt = RT_H2N_U32(l.getExpiration()); + opt.cbRawOpt = sizeof(RTNETADDRIPV4); + extra.push_back(opt); + + processParameterReqList(client, pu8ReqList, cReqList, extra); + + return doReply(client, extra); +} + +/** + * Network manager creates DHCPNAK + */ +int NetworkManager::nak(const Client& client, uint32_t u32Xid) +{ + + Lease l = client.lease(); + if (l == Lease::NullLease) + return VERR_INTERNAL_ERROR; + + prepareReplyPacket4Client(client, u32Xid); + + /* this field filed in prepareReplyPacket4Session, and + * RFC 2131 require to have it zero fo NAK. + */ + m->BootPReplyMsg.BootPHeader.bp_yiaddr.u = 0; + + /* options: + * - message type (if DHCPREQUEST) + * - server identifier + */ + RawOption opt; + std::vector<RawOption> extra; + + opt.u8OptId = RTNET_DHCP_OPT_MSG_TYPE; + opt.au8RawOpt[0] = RTNET_DHCP_MT_NAC; + opt.cbRawOpt = 1; + extra.push_back(opt); + + return doReply(client, extra); +} + +/** + * + */ +int NetworkManager::prepareReplyPacket4Client(const Client& client, uint32_t u32Xid) +{ + RT_ZERO(m->BootPReplyMsg); + + m->BootPReplyMsg.BootPHeader.bp_op = RTNETBOOTP_OP_REPLY; + m->BootPReplyMsg.BootPHeader.bp_htype = RTNET_ARP_ETHER; + m->BootPReplyMsg.BootPHeader.bp_hlen = sizeof(RTMAC); + m->BootPReplyMsg.BootPHeader.bp_hops = 0; + m->BootPReplyMsg.BootPHeader.bp_xid = u32Xid; + m->BootPReplyMsg.BootPHeader.bp_secs = 0; + /* XXX: bp_flags should be processed specially */ + m->BootPReplyMsg.BootPHeader.bp_flags = 0; + m->BootPReplyMsg.BootPHeader.bp_ciaddr.u = 0; + m->BootPReplyMsg.BootPHeader.bp_giaddr.u = 0; + + m->BootPReplyMsg.BootPHeader.bp_chaddr.Mac = client.getMacAddress(); + + const Lease l = client.lease(); + m->BootPReplyMsg.BootPHeader.bp_yiaddr = l.getAddress(); + m->BootPReplyMsg.BootPHeader.bp_siaddr.u = 0; + + + m->BootPReplyMsg.BootPHeader.bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32_C(RTNET_DHCP_COOKIE); + + memset(&m->BootPReplyMsg.BootPHeader.bp_vend.Dhcp.dhcp_opts[0], + '\0', + RTNET_DHCP_OPT_SIZE); + + return VINF_SUCCESS; +} + + +int NetworkManager::doReply(const Client& client, const std::vector<RawOption>& extra) +{ + int rc; + + /* + Options.... + */ + VBoxNetDhcpWriteCursor Cursor(&m->BootPReplyMsg.BootPHeader, RTNET_DHCP_NORMAL_SIZE); + + /* The basics */ + + Cursor.optIPv4Addr(RTNET_DHCP_OPT_SERVER_ID, m->m_OurAddress); + + const Lease l = client.lease(); + const std::map<uint8_t, RawOption>& options = l.options(); + + for(std::vector<RawOption>::const_iterator it = extra.begin(); + it != extra.end(); ++it) + { + if (!Cursor.begin(it->u8OptId, it->cbRawOpt)) + break; + Cursor.put(it->au8RawOpt, it->cbRawOpt); + + } + + for(std::map<uint8_t, RawOption>::const_iterator it = options.begin(); + it != options.end(); ++it) + { + if (!Cursor.begin(it->second.u8OptId, it->second.cbRawOpt)) + break; + Cursor.put(it->second.au8RawOpt, it->second.cbRawOpt); + + } + + Cursor.optEnd(); + + /* + */ +#if 0 + /** @todo need to see someone set this flag to check that it's correct. */ + if (!(pDhcpMsg->bp_flags & RTNET_DHCP_FLAGS_NO_BROADCAST)) + { + rc = VBoxNetUDPUnicast(m_pSession, + m_hIf, + m_pIfBuf, + m_OurAddress, + &m_OurMac, + RTNETIPV4_PORT_BOOTPS, /* sender */ + IPv4AddrBrdCast, + &BootPReplyMsg.BootPHeader->bp_chaddr.Mac, + RTNETIPV4_PORT_BOOTPC, /* receiver */ + &BootPReplyMsg, cbBooPReplyMsg); + } + else +#endif + rc = m->m_service->hlpUDPBroadcast(RTNETIPV4_PORT_BOOTPS, /* sender */ + RTNETIPV4_PORT_BOOTPC, + &m->BootPReplyMsg, + RTNET_DHCP_NORMAL_SIZE); + + AssertRCReturn(rc,rc); + + return VINF_SUCCESS; +} + + +/* + * XXX: TODO: Share decoding code with DHCPServer::addOption. + */ +static int parseDhcpOptionText(const char *pszText, + int *pOptCode, char **ppszOptText, int *pOptEncoding) +{ + uint8_t u8Code; + uint32_t u32Enc; + char *pszNext; + int rc; + + rc = RTStrToUInt8Ex(pszText, &pszNext, 10, &u8Code); + if (!RT_SUCCESS(rc)) + return VERR_PARSE_ERROR; + + switch (*pszNext) + { + case ':': /* support legacy format too */ + { + u32Enc = 0; + break; + } + + case '=': + { + u32Enc = 1; + break; + } + + case '@': + { + rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &u32Enc); + if (!RT_SUCCESS(rc)) + return VERR_PARSE_ERROR; + if (*pszNext != '=') + return VERR_PARSE_ERROR; + break; + } + + default: + return VERR_PARSE_ERROR; + } + + *pOptCode = u8Code; + *ppszOptText = pszNext + 1; + *pOptEncoding = (int)u32Enc; + + return VINF_SUCCESS; +} + + +static int fillDhcpOption(RawOption &opt, const std::string &OptText, int OptEncoding) +{ + int rc; + + if (OptEncoding == DhcpOptEncoding_Hex) + { + if (OptText.empty()) + return VERR_INVALID_PARAMETER; + + size_t cbRawOpt = 0; + char *pszNext = const_cast<char *>(OptText.c_str()); + while (*pszNext != '\0') + { + if (cbRawOpt >= RT_ELEMENTS(opt.au8RawOpt)) + return VERR_INVALID_PARAMETER; + + uint8_t u8Byte; + rc = RTStrToUInt8Ex(pszNext, &pszNext, 16, &u8Byte); + if (!RT_SUCCESS(rc)) + return rc; + + if (*pszNext == ':') + ++pszNext; + else if (*pszNext != '\0') + return VERR_PARSE_ERROR; + + opt.au8RawOpt[cbRawOpt] = u8Byte; + ++cbRawOpt; + } + opt.cbRawOpt = (uint8_t)cbRawOpt; + } + else if (OptEncoding == DhcpOptEncoding_Legacy) + { + /* + * XXX: TODO: encode "known" option opt.u8OptId + */ + return VERR_INVALID_PARAMETER; + } + + return VINF_SUCCESS; +} + + +int NetworkManager::processParameterReqList(const Client& client, const uint8_t *pu8ReqList, + int cReqList, std::vector<RawOption>& extra) +{ + int rc; + + const Lease l = client.lease(); + + const NetworkConfigEntity *pNetCfg = l.getConfig(); + + /* + * XXX: Brute-force. Unfortunately, there's no notification event + * for changes. Should at least cache the options for a short + * time, enough to last discover/offer/request/ack cycle. + */ + typedef std::map< int, std::pair<std::string, int> > DhcpOptionMap; + DhcpOptionMap OptMap; + + if (!m->m_DhcpServer.isNull()) + { + com::SafeArray<BSTR> strings; + com::Bstr str; + HRESULT hrc; + int OptCode, OptEncoding; + char *pszOptText; + + strings.setNull(); + hrc = m->m_DhcpServer->COMGETTER(GlobalOptions)(ComSafeArrayAsOutParam(strings)); + AssertComRC(hrc); + for (size_t i = 0; i < strings.size(); ++i) + { + com::Utf8Str encoded(strings[i]); + rc = parseDhcpOptionText(encoded.c_str(), + &OptCode, &pszOptText, &OptEncoding); + if (!RT_SUCCESS(rc)) + continue; + + OptMap[OptCode] = std::make_pair(pszOptText, OptEncoding); + } + + const RTMAC &mac = client.getMacAddress(); + char strMac[6*2+1] = ""; + RTStrPrintf(strMac, sizeof(strMac), "%02x%02x%02x%02x%02x%02x", + mac.au8[0], mac.au8[1], mac.au8[2], + mac.au8[3], mac.au8[4], mac.au8[5]); + + strings.setNull(); + hrc = m->m_DhcpServer->GetMacOptions(com::Bstr(strMac).raw(), + ComSafeArrayAsOutParam(strings)); + AssertComRC(hrc); + for (size_t i = 0; i < strings.size(); ++i) + { + com::Utf8Str text(strings[i]); + rc = parseDhcpOptionText(text.c_str(), + &OptCode, &pszOptText, &OptEncoding); + if (!RT_SUCCESS(rc)) + continue; + + OptMap[OptCode] = std::make_pair(pszOptText, OptEncoding); + } + } + + /* request parameter list */ + RawOption opt; + bool fIgnore; + uint8_t u8Req; + for (int idxParam = 0; idxParam < cReqList; ++idxParam) + { + fIgnore = false; + RT_ZERO(opt); + u8Req = opt.u8OptId = pu8ReqList[idxParam]; + + switch(u8Req) + { + case RTNET_DHCP_OPT_SUBNET_MASK: + ((PRTNETADDRIPV4)opt.au8RawOpt)->u = pNetCfg->netmask().u; + opt.cbRawOpt = sizeof(RTNETADDRIPV4); + + break; + + case RTNET_DHCP_OPT_ROUTERS: + case RTNET_DHCP_OPT_DNS: + { + const Ipv4AddressContainer lst = + g_ConfigurationManager->getAddressList(u8Req); + PRTNETADDRIPV4 pAddresses = (PRTNETADDRIPV4)&opt.au8RawOpt[0]; + + for (Ipv4AddressConstIterator it = lst.begin(); + it != lst.end(); + ++it) + { + *pAddresses = (*it); + pAddresses++; + opt.cbRawOpt += sizeof(RTNETADDRIPV4); + } + + if (lst.empty()) + fIgnore = true; + } + break; + case RTNET_DHCP_OPT_DOMAIN_NAME: + { + std::string domainName = g_ConfigurationManager->getString(u8Req); + if (domainName == g_ConfigurationManager->m_noString) + { + fIgnore = true; + break; + } + + size_t cchLength = domainName.length(); + if (cchLength >= sizeof(opt.au8RawOpt)) + cchLength = sizeof(opt.au8RawOpt) - 1; + memcpy(&opt.au8RawOpt[0], domainName.c_str(), cchLength); + opt.au8RawOpt[cchLength] = '\0'; + opt.cbRawOpt = (uint8_t)cchLength; + } + break; + default: + { + DhcpOptionMap::const_iterator it = OptMap.find((int)u8Req); + if (it == OptMap.end()) + { + Log(("opt: %d is ignored\n", u8Req)); + fIgnore = true; + } + else + { + std::string OptText((*it).second.first); + int OptEncoding((*it).second.second); + + rc = fillDhcpOption(opt, OptText, OptEncoding); + if (!RT_SUCCESS(rc)) + { + fIgnore = true; + break; + } + } + } + break; + } + + if (!fIgnore) + extra.push_back(opt); + + } + + return VINF_SUCCESS; +} + +/* Client */ +Client::Client() +{ + m = SharedPtr<ClientData>(); +} + + +void Client::initWithMac(const RTMAC& mac) +{ + m = SharedPtr<ClientData>(new ClientData()); + m->m_mac = mac; +} + + +bool Client::operator== (const RTMAC& mac) const +{ + return (m.get() && m->m_mac == mac); +} + + +const RTMAC& Client::getMacAddress() const +{ + return m->m_mac; +} + + +Lease Client::lease() +{ + if (!m.get()) return Lease::NullLease; + + if (m->fHasLease) + return Lease(*this); + else + return Lease::NullLease; +} + + +const Lease Client::lease() const +{ + return const_cast<Client *>(this)->lease(); +} + + +Client::Client(ClientData *data):m(SharedPtr<ClientData>(data)){} + +/* Lease */ +Lease::Lease() +{ + m = SharedPtr<ClientData>(); +} + + +Lease::Lease (const Client& c) +{ + m = SharedPtr<ClientData>(c.m); + if ( !m->fHasLease + || ( isExpired() + && !isInBindingPhase())) + { + m->fHasLease = true; + m->fBinding = true; + phaseStart(RTTimeMilliTS()); + } +} + + +bool Lease::isExpired() const +{ + AssertPtrReturn(m.get(), false); + + if (!m->fBinding) + return (ASMDivU64ByU32RetU32(RTTimeMilliTS() - m->u64TimestampLeasingStarted, 1000) + > m->u32LeaseExpirationPeriod); + else + return (ASMDivU64ByU32RetU32(RTTimeMilliTS() - m->u64TimestampBindingStarted, 1000) + > m->u32BindExpirationPeriod); +} + + +void Lease::expire() +{ + /* XXX: TODO */ +} + + +void Lease::phaseStart(uint64_t u64Start) +{ + if (m->fBinding) + m->u64TimestampBindingStarted = u64Start; + else + m->u64TimestampLeasingStarted = u64Start; +} + + +void Lease::bindingPhase(bool fOnOff) +{ + m->fBinding = fOnOff; +} + + +bool Lease::isInBindingPhase() const +{ + return m->fBinding; +} + + +uint64_t Lease::issued() const +{ + return m->u64TimestampLeasingStarted; +} + + +void Lease::setExpiration(uint32_t exp) +{ + if (m->fBinding) + m->u32BindExpirationPeriod = exp; + else + m->u32LeaseExpirationPeriod = exp; +} + + +uint32_t Lease::getExpiration() const +{ + if (m->fBinding) + return m->u32BindExpirationPeriod; + else + return m->u32LeaseExpirationPeriod; +} + + +RTNETADDRIPV4 Lease::getAddress() const +{ + return m->m_address; +} + + +void Lease::setAddress(RTNETADDRIPV4 address) +{ + m->m_address = address; +} + + +const NetworkConfigEntity *Lease::getConfig() const +{ + return m->pCfg; +} + + +void Lease::setConfig(NetworkConfigEntity *pCfg) +{ + m->pCfg = pCfg; +} + + +const MapOptionId2RawOption& Lease::options() const +{ + return m->options; +} + + +Lease::Lease(ClientData *pd):m(SharedPtr<ClientData>(pd)){} + + +bool Lease::toXML(xml::ElementNode *node) const +{ + xml::AttributeNode *pAttribNode = node->setAttribute(tagXMLLeaseAttributeMac.c_str(), + com::Utf8StrFmt("%RTmac", &m->m_mac)); + if (!pAttribNode) + return false; + + pAttribNode = node->setAttribute(tagXMLLeaseAttributeNetwork.c_str(), + com::Utf8StrFmt("%RTnaipv4", m->m_network)); + if (!pAttribNode) + return false; + + xml::ElementNode *pLeaseAddress = node->createChild(tagXMLLeaseAddress.c_str()); + if (!pLeaseAddress) + return false; + + pAttribNode = pLeaseAddress->setAttribute(tagXMLAddressAttributeValue.c_str(), + com::Utf8StrFmt("%RTnaipv4", m->m_address)); + if (!pAttribNode) + return false; + + xml::ElementNode *pLeaseTime = node->createChild(tagXMLLeaseTime.c_str()); + if (!pLeaseTime) + return false; + + pAttribNode = pLeaseTime->setAttribute(tagXMLTimeAttributeIssued.c_str(), + m->u64TimestampLeasingStarted); + if (!pAttribNode) + return false; + + pAttribNode = pLeaseTime->setAttribute(tagXMLTimeAttributeExpiration.c_str(), + m->u32LeaseExpirationPeriod); + if (!pAttribNode) + return false; + + return true; +} + + +bool Lease::fromXML(const xml::ElementNode *node) +{ + com::Utf8Str mac; + bool valueExists = node->getAttributeValue(tagXMLLeaseAttributeMac.c_str(), mac); + if (!valueExists) return false; + int rc = RTNetStrToMacAddr(mac.c_str(), &m->m_mac); + if (RT_FAILURE(rc)) return false; + + com::Utf8Str network; + valueExists = node->getAttributeValue(tagXMLLeaseAttributeNetwork.c_str(), network); + if (!valueExists) return false; + rc = RTNetStrToIPv4Addr(network.c_str(), &m->m_network); + if (RT_FAILURE(rc)) return false; + + /* Address */ + const xml::ElementNode *address = node->findChildElement(tagXMLLeaseAddress.c_str()); + if (!address) return false; + com::Utf8Str addressValue; + valueExists = address->getAttributeValue(tagXMLAddressAttributeValue.c_str(), addressValue); + if (!valueExists) return false; + rc = RTNetStrToIPv4Addr(addressValue.c_str(), &m->m_address); + + /* Time */ + const xml::ElementNode *time = node->findChildElement(tagXMLLeaseTime.c_str()); + if (!time) return false; + + valueExists = time->getAttributeValue(tagXMLTimeAttributeIssued.c_str(), + &m->u64TimestampLeasingStarted); + if (!valueExists) return false; + m->fBinding = false; + + valueExists = time->getAttributeValue(tagXMLTimeAttributeExpiration.c_str(), + &m->u32LeaseExpirationPeriod); + if (!valueExists) return false; + + m->fHasLease = true; + return true; +} + + +const Lease Lease::NullLease; + +const Client Client::NullClient; diff --git a/src/VBox/NetworkServices/DHCP/Config.h b/src/VBox/NetworkServices/DHCP/Config.h new file mode 100644 index 00000000..a8431686 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/Config.h @@ -0,0 +1,845 @@ +/* $Id: Config.h $ */ +/** @file + * Config.h + */ + +/* + * 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_DHCP_Config_h +#define VBOX_INCLUDED_SRC_DHCP_Config_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/asm-math.h> +#include <iprt/cpp/utils.h> + +#include <VBox/com/ptr.h> +#include <VBox/com/string.h> +#include <VBox/com/VirtualBox.h> + +#include "../NetLib/cpp/utils.h" + + +class RawOption +{ +public: + RawOption() + { + /** @todo r=bird: this is crazy. */ + RT_ZERO(*this); + } + uint8_t u8OptId; + uint8_t cbRawOpt; + uint8_t au8RawOpt[255]; +}; + +class ClientData; +class Client; +class Lease; +class BaseConfigEntity; + +class NetworkConfigEntity; +class HostConfigEntity; +class ClientMatchCriteria; +class ConfigurationManager; + +/* + * it's a basic representation of + * of out undestanding what client is + * XXX: Client might sends Option 61 (RFC2132 9.14 "Client-identifier") signalling + * that we may identify it in special way + * + * XXX: Client might send Option 60 (RFC2132 9.13 "Vendor class undentifier") + * in response it's expected server sends Option 43 (RFC2132 8.4. "Vendor Specific Information") + */ +class Client +{ + friend class Lease; + friend class ConfigurationManager; + + public: + Client(); + void initWithMac(const RTMAC& mac); + bool operator== (const RTMAC& mac) const; + const RTMAC& getMacAddress() const; + + /** Dumps client query */ + void dump(); + + Lease lease(); + const Lease lease() const; + + public: + static const Client NullClient; + + private: + Client(ClientData *); + SharedPtr<ClientData> m; +}; + + +bool operator== (const Lease&, const Lease&); +bool operator!= (const Lease&, const Lease&); +bool operator< (const Lease&, const Lease&); + + +typedef std::map<uint8_t, RawOption> MapOptionId2RawOption; +typedef MapOptionId2RawOption::iterator MapOptionId2RawOptionIterator; +typedef MapOptionId2RawOption::const_iterator MapOptionId2RawOptionConstIterator; +typedef MapOptionId2RawOption::value_type MapOptionId2RawOptionValue; + +namespace xml { + class ElementNode; +} + +class Lease +{ + friend class Client; + friend bool operator== (const Lease&, const Lease&); + //friend int ConfigurationManager::loadFromFile(const std::string&); + friend class ConfigurationManager; + + public: + Lease(); + Lease(const Client&); + + bool isExpired() const; + void expire(); + + /* Depending on phase *Expiration and phaseStart initialize different values. */ + void bindingPhase(bool); + void phaseStart(uint64_t u64Start); + bool isInBindingPhase() const; + /* returns 0 if in binding state */ + uint64_t issued() const; + + void setExpiration(uint32_t); + uint32_t getExpiration() const; + + RTNETADDRIPV4 getAddress() const; + void setAddress(RTNETADDRIPV4); + + const NetworkConfigEntity *getConfig() const; + void setConfig(NetworkConfigEntity *); + + const MapOptionId2RawOption& options() const; + + bool toXML(xml::ElementNode *) const; + bool fromXML(const xml::ElementNode *); + + public: + static const Lease NullLease; + + private: + Lease(ClientData *); + SharedPtr<ClientData> m; +}; + + +typedef std::vector<Client> VecClient; +typedef VecClient::iterator VecClientIterator; +typedef VecClient::const_iterator VecClientConstIterator; + +typedef std::vector<RTMAC> MacAddressContainer; +typedef MacAddressContainer::iterator MacAddressIterator; + +typedef std::vector<RTNETADDRIPV4> Ipv4AddressContainer; +typedef Ipv4AddressContainer::iterator Ipv4AddressIterator; +typedef Ipv4AddressContainer::const_iterator Ipv4AddressConstIterator; + +typedef std::map<Lease, RTNETADDRIPV4> MapLease2Ip4Address; +typedef MapLease2Ip4Address::iterator MapLease2Ip4AddressIterator; +typedef MapLease2Ip4Address::const_iterator MapLease2Ip4AddressConstIterator; +typedef MapLease2Ip4Address::value_type MapLease2Ip4AddressPair; + +/** + * + */ +class ClientMatchCriteria +{ + public: + virtual bool check(const Client&) const {return false;}; +}; + + +class ORClientMatchCriteria: ClientMatchCriteria +{ + ClientMatchCriteria* m_left; + ClientMatchCriteria* m_right; + ORClientMatchCriteria(ClientMatchCriteria *left, ClientMatchCriteria *right) + { + m_left = left; + m_right = right; + } + + virtual bool check(const Client& client) const + { + return (m_left->check(client) || m_right->check(client)); + } +}; + + +class ANDClientMatchCriteria: ClientMatchCriteria +{ +public: + ANDClientMatchCriteria(ClientMatchCriteria *left, ClientMatchCriteria *right) + { + m_left = left; + m_right = right; + } + + virtual bool check(const Client& client) const + { + return (m_left->check(client) && m_right->check(client)); + } + +private: + ClientMatchCriteria* m_left; + ClientMatchCriteria* m_right; + +}; + + +class AnyClientMatchCriteria: public ClientMatchCriteria +{ +public: + virtual bool check(const Client&) const + { + return true; + } +}; + + +class MACClientMatchCriteria: public ClientMatchCriteria +{ +public: + MACClientMatchCriteria(const RTMAC& mac):m_mac(mac){} + + virtual bool check(const Client& client) const; + +private: + RTMAC m_mac; +}; + + +#if 0 +/* XXX: Later */ +class VmSlotClientMatchCriteria: public ClientMatchCriteria +{ + str::string VmName; + uint8_t u8Slot; + virtual bool check(const Client& client) + { + return ( client.VmName == VmName + && ( u8Slot == (uint8_t)~0 /* any */ + || client.u8Slot == u8Slot)); + } +}; +#endif + + +/* Option 60 */ +class ClassClientMatchCriteria: ClientMatchCriteria{}; +/* Option 61 */ +class ClientIdentifierMatchCriteria: ClientMatchCriteria{}; + + +class BaseConfigEntity +{ + public: + BaseConfigEntity(const ClientMatchCriteria *criteria = NULL, + int matchingLevel = 0) + : m_criteria(criteria), + m_MatchLevel(matchingLevel){}; + virtual ~BaseConfigEntity(){}; + /* XXX */ + int add(BaseConfigEntity *cfg) + { + m_children.push_back(cfg); + return 0; + } + + /* Should return how strong matching */ + virtual int match(Client& client, BaseConfigEntity **cfg); + virtual uint32_t expirationPeriod() const = 0; + + protected: + const ClientMatchCriteria *m_criteria; + int m_MatchLevel; + std::vector<BaseConfigEntity *> m_children; +}; + + +class NullConfigEntity: public BaseConfigEntity +{ + public: + NullConfigEntity(){} + virtual ~NullConfigEntity(){} + int add(BaseConfigEntity *) const { return 0;} + virtual uint32_t expirationPeriod() const {return 0;} +}; + + +class ConfigEntity: public BaseConfigEntity +{ + public: + /* range */ + /* match conditions */ + ConfigEntity(std::string& name, + const BaseConfigEntity *cfg, + const ClientMatchCriteria *criteria, + int matchingLevel = 0): + BaseConfigEntity(criteria, matchingLevel), + m_name(name), + m_parentCfg(cfg), + m_u32ExpirationPeriod(0) + { + unconst(m_parentCfg)->add(this); + } + + virtual uint32_t expirationPeriod() const + { + if (!m_u32ExpirationPeriod) + return m_parentCfg->expirationPeriod(); + else + return m_u32ExpirationPeriod; + } + + /* XXX: private:*/ + std::string m_name; + const BaseConfigEntity *m_parentCfg; + uint32_t m_u32ExpirationPeriod; +}; + + +/** + * Network specific entries + */ +class NetworkConfigEntity:public ConfigEntity +{ +public: + /* Address Pool matching with network declaration */ + NetworkConfigEntity(std::string name, + const BaseConfigEntity *cfg, + const ClientMatchCriteria *criteria, + int matchlvl, + const RTNETADDRIPV4& networkID, + const RTNETADDRIPV4& networkMask, + const RTNETADDRIPV4& lowerIP, + const RTNETADDRIPV4& upperIP): + ConfigEntity(name, cfg, criteria, matchlvl), + m_NetworkID(networkID), + m_NetworkMask(networkMask), + m_UpperIP(upperIP), + m_LowerIP(lowerIP) + { + }; + + NetworkConfigEntity(std::string name, + const BaseConfigEntity *cfg, + const ClientMatchCriteria *criteria, + const RTNETADDRIPV4& networkID, + const RTNETADDRIPV4& networkMask): + ConfigEntity(name, cfg, criteria, 5), + m_NetworkID(networkID), + m_NetworkMask(networkMask) + { + m_UpperIP.u = m_NetworkID.u | (~m_NetworkMask.u); + m_LowerIP.u = m_NetworkID.u; + }; + + const RTNETADDRIPV4& upperIp() const {return m_UpperIP;} + const RTNETADDRIPV4& lowerIp() const {return m_LowerIP;} + const RTNETADDRIPV4& networkId() const {return m_NetworkID;} + const RTNETADDRIPV4& netmask() const {return m_NetworkMask;} + + private: + RTNETADDRIPV4 m_NetworkID; + RTNETADDRIPV4 m_NetworkMask; + RTNETADDRIPV4 m_UpperIP; + RTNETADDRIPV4 m_LowerIP; +}; + + +/** + * Host specific entry + * Address pool is contains one element + */ +class HostConfigEntity: public NetworkConfigEntity +{ +public: + HostConfigEntity(const RTNETADDRIPV4& addr, + std::string name, + const NetworkConfigEntity *cfg, + const ClientMatchCriteria *criteria): + NetworkConfigEntity(name, + static_cast<const ConfigEntity*>(cfg), criteria, 10, + cfg->networkId(), cfg->netmask(), addr, addr) + { + /* upper addr == lower addr */ + } +}; + +class RootConfigEntity: public NetworkConfigEntity +{ +public: + RootConfigEntity(std::string name, uint32_t expirationPeriod); + virtual ~RootConfigEntity(){}; +}; + + +#if 0 +/** + * Shared regions e.g. some of configured networks declarations + * are cover each other. + * XXX: Shared Network is join on Network config entities with possible + * overlaps in address pools. for a moment we won't configure and use them them + */ +class SharedNetworkConfigEntity: public NetworkEntity +{ +public: + SharedNetworkConfigEntity(){} + int match(const Client& client) const { return m_criteria.match(client)? 3 : 0;} + + SharedNetworkConfigEntity(NetworkEntity& network) + { + Networks.push_back(network); + } + virtual ~SharedNetworkConfigEntity(){} + + std::vector<NetworkConfigEntity> Networks; +}; +#endif + +class ConfigurationManager +{ +public: + static ConfigurationManager* getConfigurationManager(); + static int extractRequestList(PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg, RawOption& rawOpt); + + int loadFromFile(const com::Utf8Str&); + int saveToFile(); + /** + * + */ + Client getClientByDhcpPacket(const RTNETBOOTP *pDhcpMsg, size_t cbDhcpMsg); + + /** + * XXX: it's could be done on DHCPOFFER or on DHCPACK (rfc2131 gives freedom here + * 3.1.2, what is strict that allocation should do address check before real + * allocation)... + */ + Lease allocateLease4Client(const Client& client, PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg); + + /** + * We call this before DHCPACK sent and after DHCPREQUEST received ... + * when requested configuration is acceptable. + */ + int commitLease4Client(Client& client); + + /** + * Expires client lease. + */ + int expireLease4Client(Client& client); + + static int findOption(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg, RawOption& opt); + + NetworkConfigEntity *addNetwork(NetworkConfigEntity *pCfg, + const RTNETADDRIPV4& networkId, + const RTNETADDRIPV4& netmask, + RTNETADDRIPV4& UpperAddress, + RTNETADDRIPV4& LowerAddress); + + HostConfigEntity *addHost(NetworkConfigEntity*, const RTNETADDRIPV4&, ClientMatchCriteria*); + int addToAddressList(uint8_t u8OptId, RTNETADDRIPV4& address); + int flushAddressList(uint8_t u8OptId); + int setString(uint8_t u8OptId, const std::string& str); + const std::string& getString(uint8_t u8OptId); + const Ipv4AddressContainer& getAddressList(uint8_t u8OptId); + +private: + ConfigurationManager():m(NULL){} + void init(); + + ~ConfigurationManager(); + bool isAddressTaken(const RTNETADDRIPV4& addr, Lease& lease); + bool isAddressTaken(const RTNETADDRIPV4& addr); + +public: + /* nulls */ + const Ipv4AddressContainer m_empty; + const std::string m_noString; + +private: + struct Data; + Data *m; +}; + + +class NetworkManager +{ +public: + static NetworkManager *getNetworkManager(ComPtr<IDHCPServer> aDhcpServer = ComPtr<IDHCPServer>()); + + const RTNETADDRIPV4& getOurAddress() const; + const RTNETADDRIPV4& getOurNetmask() const; + const RTMAC& getOurMac() const; + + void setOurAddress(const RTNETADDRIPV4& aAddress); + void setOurNetmask(const RTNETADDRIPV4& aNetmask); + void setOurMac(const RTMAC& aMac); + + bool handleDhcpReqDiscover(PCRTNETBOOTP pDhcpMsg, size_t cb); + bool handleDhcpReqRequest(PCRTNETBOOTP pDhcpMsg, size_t cb); + bool handleDhcpReqDecline(PCRTNETBOOTP pDhcpMsg, size_t cb); + bool handleDhcpReqRelease(PCRTNETBOOTP pDhcpMsg, size_t cb); + + void setService(const VBoxNetHlpUDPService *); +private: + NetworkManager(); + ~NetworkManager(); + + int offer4Client(const Client& lease, uint32_t u32Xid, uint8_t *pu8ReqList, int cReqList); + int ack(const Client& lease, uint32_t u32Xid, uint8_t *pu8ReqList, int cReqList); + int nak(const Client& lease, uint32_t u32Xid); + + int prepareReplyPacket4Client(const Client& client, uint32_t u32Xid); + int doReply(const Client& client, const std::vector<RawOption>& extra); + int processParameterReqList(const Client& client, const uint8_t *pu8ReqList, int cReqList, std::vector<RawOption>& extra); + +private: + static NetworkManager *g_NetworkManager; + +private: + struct Data; + Data *m; + +}; + + +extern const ClientMatchCriteria *g_AnyClient; +extern RootConfigEntity *g_RootConfig; +extern const NullConfigEntity *g_NullConfig; + +/** + * Helper class for stuffing DHCP options into a reply packet. + */ +class VBoxNetDhcpWriteCursor +{ +private: + uint8_t *m_pbCur; /**< The current cursor position. */ + uint8_t *m_pbEnd; /**< The end the current option space. */ + uint8_t *m_pfOverload; /**< Pointer to the flags of the overload option. */ + uint8_t m_fUsed; /**< Overload fields that have been used. */ + PRTNETDHCPOPT m_pOpt; /**< The current option. */ + PRTNETBOOTP m_pDhcp; /**< The DHCP packet. */ + bool m_fOverflowed; /**< Set if we've overflowed, otherwise false. */ + +public: + /** Instantiate an option cursor for the specified DHCP message. */ + VBoxNetDhcpWriteCursor(PRTNETBOOTP pDhcp, size_t cbDhcp) : + m_pbCur(&pDhcp->bp_vend.Dhcp.dhcp_opts[0]), + m_pbEnd((uint8_t *)pDhcp + cbDhcp), + m_pfOverload(NULL), + m_fUsed(0), + m_pOpt(NULL), + m_pDhcp(pDhcp), + m_fOverflowed(false) + { + AssertPtr(pDhcp); + Assert(cbDhcp > RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts[10])); + } + + /** Destructor. */ + ~VBoxNetDhcpWriteCursor() + { + m_pbCur = m_pbEnd = m_pfOverload = NULL; + m_pOpt = NULL; + m_pDhcp = NULL; + } + + /** + * Try use the bp_file field. + * @returns true if not overloaded, false otherwise. + */ + bool useBpFile(void) + { + if ( m_pfOverload + && (*m_pfOverload & 1)) + return false; + m_fUsed |= 1 /* bp_file flag*/; + return true; + } + + + /** + * Try overload more BOOTP fields + */ + bool overloadMore(void) + { + /* switch option area. */ + uint8_t *pbNew; + uint8_t *pbNewEnd; + uint8_t fField; + if (!(m_fUsed & 1)) + { + fField = 1; + pbNew = &m_pDhcp->bp_file[0]; + pbNewEnd = &m_pDhcp->bp_file[sizeof(m_pDhcp->bp_file)]; + } + else if (!(m_fUsed & 2)) + { + fField = 2; + pbNew = &m_pDhcp->bp_sname[0]; + pbNewEnd = &m_pDhcp->bp_sname[sizeof(m_pDhcp->bp_sname)]; + } + else + return false; + + if (!m_pfOverload) + { + /* Add an overload option. */ + *m_pbCur++ = RTNET_DHCP_OPT_OPTION_OVERLOAD; + *m_pbCur++ = fField; + m_pfOverload = m_pbCur; + *m_pbCur++ = 1; /* bp_file flag */ + } + else + *m_pfOverload |= fField; + + /* pad current option field */ + while (m_pbCur != m_pbEnd) + *m_pbCur++ = RTNET_DHCP_OPT_PAD; /** @todo not sure if this stuff is at all correct... */ + + /* switch */ + m_pbCur = pbNew; + m_pbEnd = pbNewEnd; + return true; + } + + /** + * Begin an option. + * + * @returns true on success, false if we're out of space. + * + * @param uOption The option number. + * @param cb The amount of data. + */ + bool begin(uint8_t uOption, size_t cb) + { + /* Check that the data of the previous option has all been written. */ + Assert( !m_pOpt + || (m_pbCur - m_pOpt->dhcp_len == (uint8_t *)(m_pOpt + 1))); + AssertMsg(cb <= 255, ("%#x\n", cb)); + + /* Check if we need to overload more stuff. */ + if ((uintptr_t)(m_pbEnd - m_pbCur) < cb + 2 + (m_pfOverload ? 1 : 3)) + { + m_pOpt = NULL; + if (!overloadMore()) + { + m_fOverflowed = true; + AssertMsgFailedReturn(("%u %#x\n", uOption, cb), false); + } + if ((uintptr_t)(m_pbEnd - m_pbCur) < cb + 2 + 1) + { + m_fOverflowed = true; + AssertMsgFailedReturn(("%u %#x\n", uOption, cb), false); + } + } + + /* Emit the option header. */ + m_pOpt = (PRTNETDHCPOPT)m_pbCur; + m_pOpt->dhcp_opt = uOption; + m_pOpt->dhcp_len = (uint8_t)cb; + m_pbCur += 2; + return true; + } + + /** + * Puts option data. + * + * @param pvData The data. + * @param cb The amount to put. + */ + void put(void const *pvData, size_t cb) + { + Assert(m_pOpt || m_fOverflowed); + if (RT_LIKELY(m_pOpt)) + { + Assert((uintptr_t)m_pbCur - (uintptr_t)(m_pOpt + 1) + cb <= (size_t)m_pOpt->dhcp_len); + memcpy(m_pbCur, pvData, cb); + m_pbCur += cb; + } + } + + /** + * Puts an IPv4 Address. + * + * @param IPv4Addr The address. + */ + void putIPv4Addr(RTNETADDRIPV4 IPv4Addr) + { + put(&IPv4Addr, 4); + } + + /** + * Adds an IPv4 address option. + * + * @returns true/false just like begin(). + * + * @param uOption The option number. + * @param IPv4Addr The address. + */ + bool optIPv4Addr(uint8_t uOption, RTNETADDRIPV4 IPv4Addr) + { + if (!begin(uOption, 4)) + return false; + putIPv4Addr(IPv4Addr); + return true; + } + + /** + * Adds an option taking 1 or more IPv4 address. + * + * If the vector contains no addresses, the option will not be added. + * + * @returns true/false just like begin(). + * + * @param uOption The option number. + * @param rIPv4Addrs Reference to the address vector. + */ + bool optIPv4Addrs(uint8_t uOption, std::vector<RTNETADDRIPV4> const &rIPv4Addrs) + { + size_t const c = rIPv4Addrs.size(); + if (!c) + return true; + + if (!begin(uOption, 4*c)) + return false; + for (size_t i = 0; i < c; i++) + putIPv4Addr(rIPv4Addrs[i]); + return true; + } + + /** + * Puts an 8-bit integer. + * + * @param u8 The integer. + */ + void putU8(uint8_t u8) + { + put(&u8, 1); + } + + /** + * Adds an 8-bit integer option. + * + * @returns true/false just like begin(). + * + * @param uOption The option number. + * @param u8 The integer + */ + bool optU8(uint8_t uOption, uint8_t u8) + { + if (!begin(uOption, 1)) + return false; + putU8(u8); + return true; + } + + /** + * Puts an 32-bit integer (network endian). + * + * @param u32 The integer. + */ + void putU32(uint32_t u32) + { + put(&u32, 4); + } + + /** + * Adds an 32-bit integer (network endian) option. + * + * @returns true/false just like begin(). + * + * @param uOption The option number. + * @param u32 The integer. + */ + bool optU32(uint8_t uOption, uint32_t u32) + { + if (!begin(uOption, 4)) + return false; + putU32(u32); + return true; + } + + /** + * Puts a std::string. + * + * @param rStr Reference to the string. + */ + void putStr(std::string const &rStr) + { + put(rStr.c_str(), rStr.size()); + } + + /** + * Adds an std::string option if the string isn't empty. + * + * @returns true/false just like begin(). + * + * @param uOption The option number. + * @param rStr Reference to the string. + */ + bool optStr(uint8_t uOption, std::string const &rStr) + { + const size_t cch = rStr.size(); + if (!cch) + return true; + + if (!begin(uOption, cch)) + return false; + put(rStr.c_str(), cch); + return true; + } + + /** + * Whether we've overflowed. + * + * @returns true on overflow, false otherwise. + */ + bool hasOverflowed(void) const + { + return m_fOverflowed; + } + + /** + * Adds the terminating END option. + * + * The END will always be added as we're reserving room for it, however, we + * might have dropped previous options due to overflows and that is what the + * return status indicates. + * + * @returns true on success, false on a (previous) overflow. + */ + bool optEnd(void) + { + Assert((uintptr_t)(m_pbEnd - m_pbCur) < 4096); + *m_pbCur++ = RTNET_DHCP_OPT_END; + return !hasOverflowed(); + } +}; + +#endif /* !VBOX_INCLUDED_SRC_DHCP_Config_h */ diff --git a/src/VBox/NetworkServices/DHCP/Makefile.kmk b/src/VBox/NetworkServices/DHCP/Makefile.kmk new file mode 100644 index 00000000..41e9bb8e --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/Makefile.kmk @@ -0,0 +1,72 @@ + # $Id: Makefile.kmk $ +## @file +# Sub-Makefile for VBoxNetDHCP. +# + +# +# 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. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +VBOX_PATH_NET_DHCP_SRC := $(PATH_SUB_CURRENT) + +# +# Targets. +# +ifdef VBOX_WITH_HARDENING + PROGRAMS += VBoxNetDHCPHardened + DLLS += VBoxNetDHCP +else + PROGRAMS += VBoxNetDHCP +endif + + +# +# Hardened VBoxNetDHCP. +# +VBoxNetDHCPHardened_TEMPLATE = VBOXR3HARDENEDEXE +VBoxNetDHCPHardened_SOURCES = VBoxNetDHCPHardened.cpp +VBoxNetDHCPHardened_SOURCES.win = $(VBoxNetDHCP_0_OUTDIR)/VBoxNetDHCP-icon.rc +VBoxNetDHCPHardened_NAME = VBoxNetDHCP +VBoxNetDHCPHardened_LDFLAGS.win = /SUBSYSTEM:windows + + +# +# VBoxNetDHCP +# +VBoxNetDHCP_TEMPLATE := VBOX$(if-expr defined(VBOX_WITH_HARDENING),MAINDLL,MAINCLIENTEXE) +VBoxNetDHCP_SOURCES = \ + VBoxNetDHCP.cpp \ + Config.cpp \ + NetworkManagerDhcp.cpp \ + $(VBOX_PATH_NET_DHCP_SRC)/../NetLib/VBoxNetIntIf.cpp \ + $(VBOX_PATH_NET_DHCP_SRC)/../NetLib/VBoxNetUDP.cpp \ + $(VBOX_PATH_NET_DHCP_SRC)/../NetLib/VBoxNetARP.cpp \ + $(VBOX_PATH_NET_DHCP_SRC)/../NetLib/VBoxNetBaseService.cpp \ + $(VBOX_PATH_NET_DHCP_SRC)/../NetLib/ComHostUtils.cpp +VBoxNetDHCP_LIBS = \ + $(LIB_RUNTIME) +VBoxNetDHCP_LDFLAGS.win = /SUBSYSTEM:windows + +ifeq ($(KBUILD_TARGET),win) +# Icon include file. +VBoxNetDHCP_SOURCES += VBoxNetDHCP.rc +VBoxNetDHCP.rc_INCS = $(VBoxNetDHCP_0_OUTDIR) +VBoxNetDHCP.rc_DEPS = $(VBoxNetDHCP_0_OUTDIR)/VBoxNetDHCP-icon.rc +VBoxNetDHCP.rc_CLEAN = $(VBoxNetDHCP_0_OUTDIR)/VBoxNetDHCP-icon.rc +$$(VBoxNetDHCP_0_OUTDIR)/VBoxNetDHCP-icon.rc: $(VBOX_WINDOWS_ICON_FILE) $$(VBoxNetDHCP_DEFPATH)/Makefile.kmk | $$(dir $$@) + $(RM) -f $@ + $(APPEND) $@ 'IDI_VIRTUALBOX ICON DISCARDABLE "$(subst /,\\,$(VBOX_WINDOWS_ICON_FILE))"' +endif # win + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/NetworkServices/DHCP/NetworkManagerDhcp.cpp b/src/VBox/NetworkServices/DHCP/NetworkManagerDhcp.cpp new file mode 100644 index 00000000..591da5b8 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/NetworkManagerDhcp.cpp @@ -0,0 +1,189 @@ +/* $Id: NetworkManagerDhcp.cpp $ */ +/** @file + * NetworkManagerDhcp - Network Manager part handling Dhcp. + */ + +/* + * 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/cdefs.h> +#include <iprt/getopt.h> +#include <iprt/net.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/time.h> +#include <iprt/string.h> + +#include "../NetLib/shared_ptr.h" + +#include <vector> +#include <list> +#include <string> +#include <map> + +#include <VBox/sup.h> +#include <VBox/intnet.h> + +#define BASE_SERVICES_ONLY +#include "../NetLib/VBoxNetBaseService.h" +#include "Config.h" +#include "ClientDataInt.h" + +/** + * The client is requesting an offer. + * + * @returns true. + * + * @param pDhcpMsg The message. + * @param cb The message size. + */ +bool NetworkManager::handleDhcpReqDiscover(PCRTNETBOOTP pDhcpMsg, size_t cb) +{ + RawOption opt; + RT_ZERO(opt); + + /* 1. Find client */ + ConfigurationManager *confManager = ConfigurationManager::getConfigurationManager(); + Client client = confManager->getClientByDhcpPacket(pDhcpMsg, cb); + + /* 2. Find/Bind lease for client */ + Lease lease = confManager->allocateLease4Client(client, pDhcpMsg, cb); + AssertReturn(lease != Lease::NullLease, VINF_SUCCESS); + + int rc = ConfigurationManager::extractRequestList(pDhcpMsg, cb, opt); + NOREF(rc); /** @todo check */ + + /* 3. Send of offer */ + + lease.bindingPhase(true); + lease.phaseStart(RTTimeMilliTS()); + lease.setExpiration(300); /* 3 min. */ + offer4Client(client, pDhcpMsg->bp_xid, opt.au8RawOpt, opt.cbRawOpt); + + return true; +} + + +/** + * The client is requesting an offer. + * + * @returns true. + * + * @param pDhcpMsg The message. + * @param cb The message size. + */ +bool NetworkManager::handleDhcpReqRequest(PCRTNETBOOTP pDhcpMsg, size_t cb) +{ + ConfigurationManager *confManager = ConfigurationManager::getConfigurationManager(); + + /* 1. find client */ + Client client = confManager->getClientByDhcpPacket(pDhcpMsg, cb); + + /* 2. find bound lease */ + Lease l = client.lease(); + if (l != Lease::NullLease) + { + + if (l.isExpired()) + { + /* send client to INIT state */ + Client c(client); + nak(client, pDhcpMsg->bp_xid); + confManager->expireLease4Client(c); + return true; + } + /* XXX: Validate request */ + RawOption opt; + RT_ZERO(opt); + + Client c(client); + int rc = confManager->commitLease4Client(c); + AssertRCReturn(rc, false); + + rc = ConfigurationManager::extractRequestList(pDhcpMsg, cb, opt); + AssertRCReturn(rc, false); + + ack(client, pDhcpMsg->bp_xid, opt.au8RawOpt, opt.cbRawOpt); + } + else + { + nak(client, pDhcpMsg->bp_xid); + } + return true; +} + + +/** + * The client is declining an offer we've made. + * + * @returns true. + * + * @param pDhcpMsg The message. + * @param cb The message size. + */ +bool NetworkManager::handleDhcpReqDecline(PCRTNETBOOTP, size_t) +{ + /** @todo Probably need to match the server IP here to work correctly with + * other servers. */ + + /* + * The client is supposed to pass us option 50, requested address, + * from the offer. We also match the lease state. Apparently the + * MAC address is not supposed to be checked here. + */ + + /** @todo this is not required in the initial implementation, do it later. */ + return true; +} + + +/** + * The client is releasing its lease - good boy. + * + * @returns true. + * + * @param pDhcpMsg The message. + * @param cb The message size. + */ +bool NetworkManager::handleDhcpReqRelease(PCRTNETBOOTP, size_t) +{ + /** @todo Probably need to match the server IP here to work correctly with + * other servers. */ + + /* + * The client may pass us option 61, client identifier, which we should + * use to find the lease by. + * + * We're matching MAC address and lease state as well. + */ + + /* + * If no client identifier or if we couldn't find a lease by using it, + * we will try look it up by the client IP address. + */ + + + /* + * If found, release it. + */ + + + /** @todo this is not required in the initial implementation, do it later. */ + return true; +} + diff --git a/src/VBox/NetworkServices/DHCP/README.customoptions b/src/VBox/NetworkServices/DHCP/README.customoptions new file mode 100644 index 00000000..c121d318 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/README.customoptions @@ -0,0 +1,23 @@ +To configure custom DHCP options for a VM use the following command adapted +to your needs: + +$ VBoxManage dhcpserver modify \ + --netname test-0 --options --vm "Test Client" --slot 0 \ + --id 0 --value "224=c0:a8:02:01:c0:a8:02:02" \ + --id 0 --value "225=0:0" + +Note that custom DHCP options must be specified with ID 0 and the actual +number in the value. This has technical reasons which may change in future +VirtualBox releases. + + +It corresponds to the following bit of ISC 'dhcpd.conf': + +option sample1 code 224 = array of ip-address; +option sample2 code 225 = array of integer 8; + +... + option sample1 192.168.2.1,192.168.2.2; + option sample2 0,0; +... + diff --git a/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.cpp b/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.cpp new file mode 100644 index 00000000..8c543272 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.cpp @@ -0,0 +1,885 @@ +/* $Id: VBoxNetDHCP.cpp $ */ +/** @file + * VBoxNetDHCP - DHCP Service for connecting to IntNet. + */ + +/* + * 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. + */ + +/** @page pg_net_dhcp VBoxNetDHCP + * + * Write a few words... + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/listeners.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/EventQueue.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/alloca.h> +#include <iprt/buildconfig.h> +#include <iprt/err.h> +#include <iprt/net.h> /* must come before getopt */ +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/time.h> +#include <iprt/string.h> +#ifdef RT_OS_WINDOWS +# include <iprt/thread.h> +#endif + +#include <VBox/sup.h> +#include <VBox/intnet.h> +#include <VBox/intnetinline.h> +#include <VBox/vmm/vmm.h> +#include <VBox/version.h> + +#include "../NetLib/VBoxNetLib.h" +#include "../NetLib/shared_ptr.h" + +#include <vector> +#include <list> +#include <string> +#include <map> + +#include "../NetLib/VBoxNetBaseService.h" +#include "../NetLib/utils.h" + +#ifdef RT_OS_WINDOWS /* WinMain */ +# include <iprt/win/windows.h> +# include <stdlib.h> +# ifdef INET_ADDRSTRLEN +/* On Windows INET_ADDRSTRLEN defined as 22 Ws2ipdef.h, because it include port number */ +# undef INET_ADDRSTRLEN +# endif +# define INET_ADDRSTRLEN 16 +#else +# include <netinet/in.h> +#endif + + +#include "Config.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * DHCP server instance. + */ +class VBoxNetDhcp : public VBoxNetBaseService, public NATNetworkEventAdapter +{ +public: + VBoxNetDhcp(); + virtual ~VBoxNetDhcp(); + + int init(); + void done(); + void usage(void) { /* XXX: document options */ }; + int parseOpt(int rc, const RTGETOPTUNION& getOptVal); + int processFrame(void *, size_t) {return VERR_IGNORED; }; + int processGSO(PCPDMNETWORKGSO, size_t) {return VERR_IGNORED; }; + int processUDP(void *, size_t); + +protected: + bool handleDhcpMsg(uint8_t uMsgType, PCRTNETBOOTP pDhcpMsg, size_t cb); + + void debugPrintV(int32_t iMinLevel, bool fMsg, const char *pszFmt, va_list va) const; + static const char *debugDhcpName(uint8_t uMsgType); + +private: + int initNoMain(); + int initWithMain(); + HRESULT HandleEvent(VBoxEventType_T aEventType, IEvent *pEvent); + + static int hostDnsServers(const ComHostPtr& host, + const RTNETADDRIPV4& networkid, + const AddressToOffsetMapping& mapping, + AddressList& servers); + int fetchAndUpdateDnsInfo(); + +protected: + /** @name The DHCP server specific configuration data members. + * @{ */ + /* + * XXX: what was the plan? SQL3 or plain text file? + * How it will coexists with managment from VBoxManagement, who should manage db + * in that case (VBoxManage, VBoxSVC ???) + */ + std::string m_LeaseDBName; + + /** @} */ + + /* corresponding dhcp server description in Main */ + ComPtr<IDHCPServer> m_DhcpServer; + + ComPtr<INATNetwork> m_NATNetwork; + + /** Listener for Host DNS changes */ + ComNatListenerPtr m_VBoxListener; + ComNatListenerPtr m_VBoxClientListener; + + NetworkManager *m_NetworkManager; + + /* + * We will ignore cmd line parameters IFF there will be some DHCP specific arguments + * otherwise all paramters will come from Main. + */ + bool m_fIgnoreCmdLineParameters; + + /* + * -b -n 10.0.1.2 -m 255.255.255.0 -> to the list processing in + */ + typedef struct + { + char Key; + std::string strValue; + } CMDLNPRM; + std::list<CMDLNPRM> CmdParameterll; + typedef std::list<CMDLNPRM>::iterator CmdParameterIterator; + + /** @name Debug stuff + * @{ */ + int32_t m_cVerbosity; + uint8_t m_uCurMsgType; + size_t m_cbCurMsg; + PCRTNETBOOTP m_pCurMsg; + VBOXNETUDPHDRS m_CurHdrs; + /** @} */ +}; + + +static inline int configGetBoundryAddress(const ComDhcpServerPtr& dhcp, bool fUpperBoundry, RTNETADDRIPV4& boundryAddress) +{ + boundryAddress.u = INADDR_ANY; + + HRESULT hrc; + com::Bstr strAddress; + if (fUpperBoundry) + hrc = dhcp->COMGETTER(UpperIP)(strAddress.asOutParam()); + else + hrc = dhcp->COMGETTER(LowerIP)(strAddress.asOutParam()); + AssertComRCReturn(hrc, VERR_INTERNAL_ERROR); + + return RTNetStrToIPv4Addr(com::Utf8Str(strAddress).c_str(), &boundryAddress); +} + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Pointer to the DHCP server. */ +static VBoxNetDhcp *g_pDhcp; + +/* DHCP server specific options */ +static RTGETOPTDEF g_aOptionDefs[] = +{ + { "--lease-db", 'D', RTGETOPT_REQ_STRING }, + { "--begin-config", 'b', RTGETOPT_REQ_NOTHING }, + { "--gateway", 'g', RTGETOPT_REQ_IPV4ADDR }, + { "--lower-ip", 'l', RTGETOPT_REQ_IPV4ADDR }, + { "--upper-ip", 'u', RTGETOPT_REQ_IPV4ADDR }, +}; + +/** + * Construct a DHCP server with a default configuration. + */ +VBoxNetDhcp::VBoxNetDhcp() + : VBoxNetBaseService("VBoxNetDhcp", "VBoxNetDhcp"), + m_NetworkManager(NULL) +{ + /* m_enmTrunkType = kIntNetTrunkType_WhateverNone; */ + RTMAC mac; + mac.au8[0] = 0x08; + mac.au8[1] = 0x00; + mac.au8[2] = 0x27; + mac.au8[3] = 0x40; + mac.au8[4] = 0x41; + mac.au8[5] = 0x42; + setMacAddress(mac); + + RTNETADDRIPV4 address; + address.u = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2, 5))); + setIpv4Address(address); + + setSendBufSize(8 * _1K); + setRecvBufSize(50 * _1K); + + m_uCurMsgType = UINT8_MAX; + m_cbCurMsg = 0; + m_pCurMsg = NULL; + RT_ZERO(m_CurHdrs); + + m_fIgnoreCmdLineParameters = true; + + for(unsigned int i = 0; i < RT_ELEMENTS(g_aOptionDefs); ++i) + addCommandLineOption(&g_aOptionDefs[i]); +} + + +/** + * Destruct a DHCP server. + */ +VBoxNetDhcp::~VBoxNetDhcp() +{ +} + + + + +/** + * Parse the DHCP specific arguments. + * + * This callback caled for each paramenter so + * .... + * we nee post analisys of the parameters, at least + * for -b, -g, -l, -u, -m + */ +int VBoxNetDhcp::parseOpt(int rc, const RTGETOPTUNION& Val) +{ + CMDLNPRM prm; + + /* Ok, we've entered here, thus we can't ignore cmd line parameters anymore */ + m_fIgnoreCmdLineParameters = false; + + prm.Key = rc; + + switch (rc) + { + case 'l': + case 'u': + case 'g': + { + char buf[17]; + RTStrPrintf(buf, 17, "%RTnaipv4", Val.IPv4Addr.u); + prm.strValue = buf; + CmdParameterll.push_back(prm); + } + break; + + case 'b': // ignore + case 'D': // ignore + break; + + default: + rc = RTGetOptPrintError(rc, &Val); + RTPrintf("Use --help for more information.\n"); + return rc; + } + + return VINF_SUCCESS; +} + +int VBoxNetDhcp::init() +{ + int rc = this->VBoxNetBaseService::init(); + AssertRCReturn(rc, rc); + + if (isMainNeeded()) + rc = initWithMain(); + else + rc = initNoMain(); + AssertRCReturn(rc, rc); + + m_NetworkManager = NetworkManager::getNetworkManager(m_DhcpServer); + AssertPtrReturn(m_NetworkManager, VERR_INTERNAL_ERROR); + + m_NetworkManager->setOurAddress(getIpv4Address()); + m_NetworkManager->setOurNetmask(getIpv4Netmask()); + m_NetworkManager->setOurMac(getMacAddress()); + m_NetworkManager->setService(this); + + return VINF_SUCCESS; +} + +void VBoxNetDhcp::done() +{ + destroyNatListener(m_VBoxListener, virtualbox); + destroyClientListener(m_VBoxClientListener, virtualboxClient); +} + +int VBoxNetDhcp::processUDP(void *pv, size_t cbPv) +{ + PCRTNETBOOTP pDhcpMsg = (PCRTNETBOOTP)pv; + m_pCurMsg = pDhcpMsg; + m_cbCurMsg = cbPv; + + uint8_t uMsgType; + if (RTNetIPv4IsDHCPValid(NULL /* why is this here? */, pDhcpMsg, cbPv, &uMsgType)) + { + m_uCurMsgType = uMsgType; + { + /* To avoid fight with event processing thread */ + VBoxNetALock(this); + handleDhcpMsg(uMsgType, pDhcpMsg, cbPv); + } + m_uCurMsgType = UINT8_MAX; + } + else + debugPrint(1, true, "VBoxNetDHCP: Skipping invalid DHCP packet.\n"); /** @todo handle pure bootp clients too? */ + + m_pCurMsg = NULL; + m_cbCurMsg = 0; + + return VINF_SUCCESS; +} + + +/** + * Handles a DHCP message. + * + * @returns true if handled, false if not. (IGNORED BY CALLER) + * @param uMsgType The message type. + * @param pDhcpMsg The DHCP message. + * @param cb The size of the DHCP message. + */ +bool VBoxNetDhcp::handleDhcpMsg(uint8_t uMsgType, PCRTNETBOOTP pDhcpMsg, size_t cb) +{ + if (pDhcpMsg->bp_op == RTNETBOOTP_OP_REQUEST) + { + AssertPtrReturn(m_NetworkManager, false); + + switch (uMsgType) + { + case RTNET_DHCP_MT_DISCOVER: + return m_NetworkManager->handleDhcpReqDiscover(pDhcpMsg, cb); + + case RTNET_DHCP_MT_REQUEST: + return m_NetworkManager->handleDhcpReqRequest(pDhcpMsg, cb); + + case RTNET_DHCP_MT_DECLINE: + return m_NetworkManager->handleDhcpReqDecline(pDhcpMsg, cb); + + case RTNET_DHCP_MT_RELEASE: + return m_NetworkManager->handleDhcpReqRelease(pDhcpMsg, cb); + + case RTNET_DHCP_MT_INFORM: + debugPrint(0, true, "Should we handle this?"); + break; + + default: + debugPrint(0, true, "Unexpected."); + break; + } + } + return false; +} + +/** + * Print debug message depending on the m_cVerbosity level. + * + * @param iMinLevel The minimum m_cVerbosity level for this message. + * @param fMsg Whether to dump parts for the current DHCP message. + * @param pszFmt The message format string. + * @param va Optional arguments. + */ +void VBoxNetDhcp::debugPrintV(int iMinLevel, bool fMsg, const char *pszFmt, va_list va) const +{ + if (iMinLevel <= m_cVerbosity) + { + va_list vaCopy; /* This dude is *very* special, thus the copy. */ + va_copy(vaCopy, va); + RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: %s: %N\n", iMinLevel >= 2 ? "debug" : "info", pszFmt, &vaCopy); + va_end(vaCopy); + + if ( fMsg + && m_cVerbosity >= 2 + && m_pCurMsg) + { + /* XXX: export this to debugPrinfDhcpMsg or variant and other method export + * to base class + */ + const char *pszMsg = m_uCurMsgType != UINT8_MAX ? debugDhcpName(m_uCurMsgType) : ""; + RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: debug: %8s chaddr=%.6Rhxs ciaddr=%d.%d.%d.%d yiaddr=%d.%d.%d.%d siaddr=%d.%d.%d.%d xid=%#x\n", + pszMsg, + &m_pCurMsg->bp_chaddr, + m_pCurMsg->bp_ciaddr.au8[0], m_pCurMsg->bp_ciaddr.au8[1], m_pCurMsg->bp_ciaddr.au8[2], m_pCurMsg->bp_ciaddr.au8[3], + m_pCurMsg->bp_yiaddr.au8[0], m_pCurMsg->bp_yiaddr.au8[1], m_pCurMsg->bp_yiaddr.au8[2], m_pCurMsg->bp_yiaddr.au8[3], + m_pCurMsg->bp_siaddr.au8[0], m_pCurMsg->bp_siaddr.au8[1], m_pCurMsg->bp_siaddr.au8[2], m_pCurMsg->bp_siaddr.au8[3], + m_pCurMsg->bp_xid); + } + } +} + + +/** + * Gets the name of given DHCP message type. + * + * @returns Readonly name. + * @param uMsgType The message number. + */ +/* static */ const char *VBoxNetDhcp::debugDhcpName(uint8_t uMsgType) +{ + switch (uMsgType) + { + case 0: return "MT_00"; + case RTNET_DHCP_MT_DISCOVER: return "DISCOVER"; + case RTNET_DHCP_MT_OFFER: return "OFFER"; + case RTNET_DHCP_MT_REQUEST: return "REQUEST"; + case RTNET_DHCP_MT_DECLINE: return "DECLINE"; + case RTNET_DHCP_MT_ACK: return "ACK"; + case RTNET_DHCP_MT_NAC: return "NAC"; + case RTNET_DHCP_MT_RELEASE: return "RELEASE"; + case RTNET_DHCP_MT_INFORM: return "INFORM"; + case 9: return "MT_09"; + case 10: return "MT_0a"; + case 11: return "MT_0b"; + case 12: return "MT_0c"; + case 13: return "MT_0d"; + case 14: return "MT_0e"; + case 15: return "MT_0f"; + case 16: return "MT_10"; + case 17: return "MT_11"; + case 18: return "MT_12"; + case 19: return "MT_13"; + case UINT8_MAX: return "MT_ff"; + default: return "UNKNOWN"; + } +} + + +int VBoxNetDhcp::initNoMain() +{ + CmdParameterIterator it; + + RTNETADDRIPV4 address = getIpv4Address(); + RTNETADDRIPV4 netmask = getIpv4Netmask(); + RTNETADDRIPV4 networkId; + networkId.u = address.u & netmask.u; + + RTNETADDRIPV4 UpperAddress; + RTNETADDRIPV4 LowerAddress = networkId; + UpperAddress.u = RT_H2N_U32(RT_N2H_U32(LowerAddress.u) | RT_N2H_U32(netmask.u)); + + for (it = CmdParameterll.begin(); it != CmdParameterll.end(); ++it) + { + switch(it->Key) + { + case 'l': + RTNetStrToIPv4Addr(it->strValue.c_str(), &LowerAddress); + break; + + case 'u': + RTNetStrToIPv4Addr(it->strValue.c_str(), &UpperAddress); + break; + case 'b': + break; + + } + } + + ConfigurationManager *confManager = ConfigurationManager::getConfigurationManager(); + AssertPtrReturn(confManager, VERR_INTERNAL_ERROR); + confManager->addNetwork(unconst(g_RootConfig), + networkId, + netmask, + LowerAddress, + UpperAddress); + + return VINF_SUCCESS; +} + + +int VBoxNetDhcp::initWithMain() +{ + /* ok, here we should initiate instance of dhcp server + * and listener for Dhcp configuration events + */ + AssertRCReturn(virtualbox.isNull(), VERR_INTERNAL_ERROR); + std::string networkName = getNetworkName(); + + int rc = findDhcpServer(virtualbox, networkName, m_DhcpServer); + AssertRCReturn(rc, rc); + + rc = findNatNetwork(virtualbox, networkName, m_NATNetwork); + AssertRCReturn(rc, rc); + + BOOL fNeedDhcpServer = isDhcpRequired(m_NATNetwork); + if (!fNeedDhcpServer) + return VERR_CANCELLED; + + RTNETADDRIPV4 gateway; + com::Bstr strGateway; + HRESULT hrc = m_NATNetwork->COMGETTER(Gateway)(strGateway.asOutParam()); + AssertComRCReturn(hrc, VERR_INTERNAL_ERROR); + RTNetStrToIPv4Addr(com::Utf8Str(strGateway).c_str(), &gateway); + + ConfigurationManager *confManager = ConfigurationManager::getConfigurationManager(); + AssertPtrReturn(confManager, VERR_INTERNAL_ERROR); + confManager->addToAddressList(RTNET_DHCP_OPT_ROUTERS, gateway); + + rc = fetchAndUpdateDnsInfo(); + AssertMsgRCReturn(rc, ("Wasn't able to fetch Dns info"), rc); + + { + ComEventTypeArray eventTypes; + eventTypes.push_back(VBoxEventType_OnHostNameResolutionConfigurationChange); + eventTypes.push_back(VBoxEventType_OnNATNetworkStartStop); + rc = createNatListener(m_VBoxListener, virtualbox, this, eventTypes); + AssertRCReturn(rc, rc); + } + + { + ComEventTypeArray eventTypes; + eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged); + rc = createClientListener(m_VBoxClientListener, virtualboxClient, this, eventTypes); + AssertRCReturn(rc, rc); + } + + RTNETADDRIPV4 LowerAddress; + rc = configGetBoundryAddress(m_DhcpServer, false, LowerAddress); + AssertMsgRCReturn(rc, ("can't get lower boundrary adderss'"),rc); + + RTNETADDRIPV4 UpperAddress; + rc = configGetBoundryAddress(m_DhcpServer, true, UpperAddress); + AssertMsgRCReturn(rc, ("can't get upper boundrary adderss'"),rc); + + RTNETADDRIPV4 address = getIpv4Address(); + RTNETADDRIPV4 netmask = getIpv4Netmask(); + RTNETADDRIPV4 networkId = networkid(address, netmask); + std::string name = std::string("default"); + + confManager->addNetwork(unconst(g_RootConfig), + networkId, + netmask, + LowerAddress, + UpperAddress); + + com::Bstr bstr; + hrc = virtualbox->COMGETTER(HomeFolder)(bstr.asOutParam()); + com::Utf8StrFmt strXmlLeaseFile("%ls%c%s.leases", + bstr.raw(), RTPATH_DELIMITER, networkName.c_str()); + confManager->loadFromFile(strXmlLeaseFile); + + return VINF_SUCCESS; +} + + +int VBoxNetDhcp::fetchAndUpdateDnsInfo() +{ + ComHostPtr host; + if (SUCCEEDED(virtualbox->COMGETTER(Host)(host.asOutParam()))) + { + AddressToOffsetMapping mapIp4Addr2Off; + int rc = localMappings(m_NATNetwork, mapIp4Addr2Off); + /* XXX: here could be several cases: 1. COM error, 2. not found (empty) 3. ? */ + AssertMsgRCReturn(rc, ("Can't fetch local mappings"), rc); + + RTNETADDRIPV4 address = getIpv4Address(); + RTNETADDRIPV4 netmask = getIpv4Netmask(); + + AddressList nameservers; + rc = hostDnsServers(host, networkid(address, netmask), mapIp4Addr2Off, nameservers); + AssertMsgRCReturn(rc, ("Debug me!!!"), rc); + /* XXX: Search strings */ + + std::string domain; + rc = hostDnsDomain(host, domain); + AssertMsgRCReturn(rc, ("Debug me!!"), rc); + + { + VBoxNetALock(this); + ConfigurationManager *confManager = ConfigurationManager::getConfigurationManager(); + confManager->flushAddressList(RTNET_DHCP_OPT_DNS); + + for (AddressList::iterator it = nameservers.begin(); it != nameservers.end(); ++it) + confManager->addToAddressList(RTNET_DHCP_OPT_DNS, *it); + + confManager->setString(RTNET_DHCP_OPT_DOMAIN_NAME, domain); + } + } + + return VINF_SUCCESS; +} + + +int VBoxNetDhcp::hostDnsServers(const ComHostPtr& host, + const RTNETADDRIPV4& networkid, + const AddressToOffsetMapping& mapping, + AddressList& servers) +{ + ComBstrArray strs; + + HRESULT hrc = host->COMGETTER(NameServers)(ComSafeArrayAsOutParam(strs)); + if (FAILED(hrc)) + return VERR_NOT_FOUND; + + /* + * Recent fashion is to run dnsmasq on 127.0.1.1 which we + * currently can't map. If that's the only nameserver we've got, + * we need to use DNS proxy for VMs to reach it. + */ + bool fUnmappedLoopback = false; + + for (size_t i = 0; i < strs.size(); ++i) + { + RTNETADDRIPV4 addr; + int rc; + + rc = RTNetStrToIPv4Addr(com::Utf8Str(strs[i]).c_str(), &addr); + if (RT_FAILURE(rc)) + continue; + + if (addr.u == INADDR_ANY) + { + /* + * This doesn't seem to be very well documented except for + * RTFS of res_init.c, but INADDR_ANY is a valid value for + * for "nameserver". + */ + addr.u = RT_H2N_U32_C(INADDR_LOOPBACK); + } + + if (addr.au8[0] == 127) + { + AddressToOffsetMapping::const_iterator remap(mapping.find(addr)); + + if (remap != mapping.end()) + { + int offset = remap->second; + addr.u = RT_H2N_U32(RT_N2H_U32(networkid.u) + offset); + } + else + { + fUnmappedLoopback = true; + continue; + } + } + + servers.push_back(addr); + } + + if (servers.empty() && fUnmappedLoopback) + { + RTNETADDRIPV4 proxy; + + proxy.u = networkid.u | RT_H2N_U32_C(1U); + servers.push_back(proxy); + } + + return VINF_SUCCESS; +} + + +HRESULT VBoxNetDhcp::HandleEvent(VBoxEventType_T aEventType, IEvent *pEvent) +{ + switch (aEventType) + { + case VBoxEventType_OnHostNameResolutionConfigurationChange: + fetchAndUpdateDnsInfo(); + break; + + case VBoxEventType_OnNATNetworkStartStop: + { + ComPtr <INATNetworkStartStopEvent> pStartStopEvent = pEvent; + + com::Bstr networkName; + HRESULT hrc = pStartStopEvent->COMGETTER(NetworkName)(networkName.asOutParam()); + AssertComRCReturn(hrc, hrc); + if (networkName.compare(getNetworkName().c_str())) + break; /* change not for our network */ + + BOOL fStart = TRUE; + hrc = pStartStopEvent->COMGETTER(StartEvent)(&fStart); + AssertComRCReturn(hrc, hrc); + if (!fStart) + shutdown(); + break; + } + + case VBoxEventType_OnVBoxSVCAvailabilityChanged: + { + shutdown(); + break; + } + + default: break; /* Shut up MSC. */ + } + + return S_OK; +} + +#ifdef RT_OS_WINDOWS + +/** The class name for the DIFx-killable window. */ +static WCHAR g_wszWndClassName[] = L"VBoxNetDHCPClass"; +/** Whether to exit the process on quit. */ +static bool g_fExitProcessOnQuit = true; + +/** + * Window procedure for making us DIFx-killable. + */ +static LRESULT CALLBACK DIFxKillableWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg == WM_DESTROY) + { + PostQuitMessage(0); + return 0; + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +/** @callback_method_impl{FNRTTHREAD, + * Thread that creates service a window the DIFx can destroy, thereby + * triggering process termination. } + */ +static DECLCALLBACK(int) DIFxKillableProcessThreadProc(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf, pvUser); + HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL); + + /* Register the Window Class. */ + WNDCLASSW WndCls; + WndCls.style = 0; + WndCls.lpfnWndProc = DIFxKillableWindowProc; + WndCls.cbClsExtra = 0; + WndCls.cbWndExtra = sizeof(void *); + WndCls.hInstance = hInstance; + WndCls.hIcon = NULL; + WndCls.hCursor = NULL; + WndCls.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + WndCls.lpszMenuName = NULL; + WndCls.lpszClassName = g_wszWndClassName; + + ATOM atomWindowClass = RegisterClassW(&WndCls); + if (atomWindowClass != 0) + { + /* Create the window. */ + HWND hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST, + g_wszWndClassName, g_wszWndClassName, + WS_POPUPWINDOW, + -200, -200, 100, 100, NULL, NULL, hInstance, NULL); + if (hwnd) + { + SetWindowPos(hwnd, HWND_TOPMOST, -200, -200, 0, 0, + SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + DestroyWindow(hwnd); + } + + UnregisterClassW(g_wszWndClassName, hInstance); + + if (hwnd && g_fExitProcessOnQuit) + exit(0); + } + return 0; +} + +#endif /* RT_OS_WINDOWS */ + +/** + * Entry point. + */ +extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv) +{ + /* + * Instantiate the DHCP server and hand it the options. + */ + VBoxNetDhcp *pDhcp = new VBoxNetDhcp(); + if (!pDhcp) + { + RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: new VBoxNetDhcp failed!\n"); + return 1; + } + + RTEXITCODE rcExit = (RTEXITCODE)pDhcp->parseArgs(argc - 1, argv + 1); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + +#ifdef RT_OS_WINDOWS + /* DIFx hack. */ + RTTHREAD hMakeUseKillableThread = NIL_RTTHREAD; + int rc2 = RTThreadCreate(&hMakeUseKillableThread, DIFxKillableProcessThreadProc, NULL, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "DIFxKill"); + if (RT_FAILURE(rc2)) + hMakeUseKillableThread = NIL_RTTHREAD; +#endif + + pDhcp->init(); + + /* + * Try connect the server to the network. + */ + int rc = pDhcp->tryGoOnline(); + if (RT_SUCCESS(rc)) + { + /* + * Process requests. + */ + g_pDhcp = pDhcp; + rc = pDhcp->run(); + pDhcp->done(); + + g_pDhcp = NULL; + } + delete pDhcp; + +#ifdef RT_OS_WINDOWS + /* Kill DIFx hack. */ + if (hMakeUseKillableThread != NIL_RTTHREAD) + { + g_fExitProcessOnQuit = false; + PostThreadMessage((DWORD)RTThreadGetNative(hMakeUseKillableThread), WM_QUIT, 0, 0); + RTThreadWait(hMakeUseKillableThread, RT_MS_1SEC * 5U, NULL); + } +#endif + + 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) +{ + NOREF(hInstance); NOREF(hPrevInstance); NOREF(lpCmdLine); NOREF(nCmdShow); + return main(__argc, __argv); +} +# endif /* RT_OS_WINDOWS */ + +#endif /* !VBOX_WITH_HARDENING */ + diff --git a/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.rc b/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.rc new file mode 100644 index 00000000..5a8b64aa --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.rc @@ -0,0 +1,55 @@ +/* $Id: VBoxNetDHCP.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", "VBoxNetDHCP\0" + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "OriginalFilename", "VBoxNetDHCP.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 "VBoxNetDHCP-icon.rc" + diff --git a/src/VBox/NetworkServices/DHCP/VBoxNetDHCPHardened.cpp b/src/VBox/NetworkServices/DHCP/VBoxNetDHCPHardened.cpp new file mode 100644 index 00000000..8ed60a98 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/VBoxNetDHCPHardened.cpp @@ -0,0 +1,25 @@ +/* $Id: VBoxNetDHCPHardened.cpp $ */ +/** @file + * VBoxNetDHCP - 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); +} + |