/* $Id: DHCPServerImpl.cpp 79733 2019-07-12 12:51:57Z vboxsync $ */ /** @file * VirtualBox COM class implementation */ /* * 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. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_MAIN_DHCPSERVER #include "DHCPServerImpl.h" #include "LoggingNew.h" #include #include #include #include #include #include #include #include #include #include #include "AutoCaller.h" #include "DHCPConfigImpl.h" #include "MachineImpl.h" #include "NetworkServiceRunner.h" #include "VirtualBoxImpl.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) # define DHCP_EXECUTABLE_NAME "VBoxNetDHCP.exe" #else # define DHCP_EXECUTABLE_NAME "VBoxNetDHCP" #endif /** * DHCP server specialization of NetworkServiceRunner. * * Just defines the executable name and adds option constants. */ class DHCPServerRunner : public NetworkServiceRunner { public: DHCPServerRunner() : NetworkServiceRunner(DHCP_EXECUTABLE_NAME) {} virtual ~DHCPServerRunner() {} static const char * const kDsrKeyGateway; static const char * const kDsrKeyLowerIp; static const char * const kDsrKeyUpperIp; static const char * const kDsrKeyConfig; static const char * const kDsrKeyComment; }; /*static*/ const char * const DHCPServerRunner::kDsrKeyGateway = "--gateway"; /*static*/ const char * const DHCPServerRunner::kDsrKeyLowerIp = "--lower-ip"; /*static*/ const char * const DHCPServerRunner::kDsrKeyUpperIp = "--upper-ip"; /*static*/ const char * const DHCPServerRunner::kDsrKeyConfig = "--config"; /*static*/ const char * const DHCPServerRunner::kDsrKeyComment = "--comment"; /** * Hidden private data of the DHCPServer class. */ struct DHCPServer::Data { Data() : pVirtualBox(NULL) , strName() , enabled(FALSE) // , router(false) , uIndividualMACAddressVersion(1) { szTempConfigFileName[0] = '\0'; } /** weak VirtualBox parent */ VirtualBox * const pVirtualBox; /** The DHCP server name (network). * @todo Kind of duplicated by networkName, if I understand it correctly. */ Utf8Str const strName; Utf8Str IPAddress; Utf8Str lowerIP; Utf8Str upperIP; BOOL enabled; #if 0 /** Don't quit get WTF this is about, but the old addOption method contained the * following hint: "Indirect way to understand that we're on NAT network." * * Apparently this is a busted with the new dhcpd implementation, so we don't * maintain it with the API overhaul in 6.0.12. */ bool router; #endif DHCPServerRunner dhcp; char szTempConfigFileName[RTPATH_MAX]; com::Utf8Str strLeasesFilename; com::Utf8Str networkName; com::Utf8Str trunkName; com::Utf8Str trunkType; /** Global configuration. */ ComObjPtr globalConfig; // /** Group configuration indexed by name. */ // std::map> groupConfigs; // /** Iterator for groupConfigs. */ // typedef std::map>::iterator GroupConfigIterator; /** Individual (host) configuration indexed by MAC address or VM UUID. */ std::map> individualConfigs; /** Iterator for individualConfigs. */ typedef std::map>::iterator IndividualConfigIterator; /** Part of a lock-avoidance hack to resolve the VM ID + slot into MAC * addresses before writing out the Dhcpd configuration file. */ uint32_t uIndividualMACAddressVersion; }; // constructor / destructor ///////////////////////////////////////////////////////////////////////////// DHCPServer::DHCPServer() : m(NULL) { m = new DHCPServer::Data(); } DHCPServer::~DHCPServer() { if (m) { delete m; m = NULL; } } HRESULT DHCPServer::FinalConstruct() { return BaseFinalConstruct(); } void DHCPServer::FinalRelease() { uninit(); BaseFinalRelease(); } void DHCPServer::uninit() { /* Enclose the state transition Ready->InUninit->NotReady */ AutoUninitSpan autoUninitSpan(this); if (autoUninitSpan.uninitDone()) return; if (m->dhcp.isRunning()) stop(); unconst(m->pVirtualBox) = NULL; } HRESULT DHCPServer::init(VirtualBox *aVirtualBox, const Utf8Str &aName) { AssertReturn(!aName.isEmpty(), E_INVALIDARG); AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); /* share VirtualBox weakly (parent remains NULL so far) */ unconst(m->pVirtualBox) = aVirtualBox; unconst(m->strName) = aName; m->IPAddress = "0.0.0.0"; m->lowerIP = "0.0.0.0"; m->upperIP = "0.0.0.0"; m->enabled = FALSE; /* Global configuration: */ HRESULT hrc = m->globalConfig.createObject(); if (SUCCEEDED(hrc)) hrc = m->globalConfig->initWithDefaults(aVirtualBox, this); /* Confirm a successful initialization or not: */ if (SUCCEEDED(hrc)) autoInitSpan.setSucceeded(); else autoInitSpan.setFailed(hrc); return hrc; } HRESULT DHCPServer::init(VirtualBox *aVirtualBox, const settings::DHCPServer &rData) { /* Enclose the state transition NotReady->InInit->Ready */ AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); /* share VirtualBox weakly (parent remains NULL so far) */ unconst(m->pVirtualBox) = aVirtualBox; unconst(m->strName) = rData.strNetworkName; m->IPAddress = rData.strIPAddress; m->enabled = rData.fEnabled; m->lowerIP = rData.strIPLower; m->upperIP = rData.strIPUpper; /* * Global configuration: */ HRESULT hrc = m->globalConfig.createObject(); if (SUCCEEDED(hrc)) hrc = m->globalConfig->initWithSettings(aVirtualBox, this, rData.GlobalConfig); /* * Group configurations: */ /* * Individual configuration: */ Assert(m->individualConfigs.size() == 0); if (SUCCEEDED(hrc)) { for (settings::DHCPIndividualConfigMap::const_iterator it = rData.IndividualConfigs.begin(); it != rData.IndividualConfigs.end() && SUCCEEDED(hrc); ++it) { ComObjPtr ptrIndiCfg; com::Utf8Str strKey; if (!it->second.strVMName.isNotEmpty()) { RTMAC MACAddress; int vrc = RTNetStrToMacAddr(it->second.strMACAddress.c_str(), &MACAddress); if (RT_FAILURE(vrc)) { LogRel(("Ignoring invalid MAC address for individual DHCP config: '%s' - %Rrc\n", it->second.strMACAddress.c_str(), vrc)); continue; } vrc = strKey.printfNoThrow("%RTmac", &MACAddress); AssertRCReturn(vrc, E_OUTOFMEMORY); hrc = ptrIndiCfg.createObject(); if (SUCCEEDED(hrc)) hrc = ptrIndiCfg->initWithSettingsAndMACAddress(aVirtualBox, this, it->second, &MACAddress); } else { /* This ASSUMES that we're being called after the machines have been loaded so we can resolve VM names into UUID for old settings. */ com::Guid idMachine; hrc = i_vmNameToIdAndValidateSlot(it->second.strVMName, it->second.uSlot, idMachine); if (SUCCEEDED(hrc)) { hrc = ptrIndiCfg.createObject(); if (SUCCEEDED(hrc)) hrc = ptrIndiCfg->initWithSettingsAndMachineIdAndSlot(aVirtualBox, this, it->second, idMachine, it->second.uSlot, m->uIndividualMACAddressVersion - UINT32_MAX / 4); } } if (SUCCEEDED(hrc)) { try { m->individualConfigs[strKey] = ptrIndiCfg; } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } } } } /* Confirm a successful initialization or not: */ if (SUCCEEDED(hrc)) autoInitSpan.setSucceeded(); else autoInitSpan.setFailed(hrc); return hrc; } HRESULT DHCPServer::i_saveSettings(settings::DHCPServer &rData) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); rData.strNetworkName = m->strName; rData.strIPAddress = m->IPAddress; rData.fEnabled = m->enabled != FALSE; rData.strIPLower = m->lowerIP; rData.strIPUpper = m->upperIP; /* Global configuration: */ HRESULT hrc = m->globalConfig->i_saveSettings(rData.GlobalConfig); /* Group configuration: */ /* Individual configuration: */ for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end() && SUCCEEDED(hrc); ++it) { try { rData.IndividualConfigs[it->first] = settings::DHCPIndividualConfig(); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } hrc = it->second->i_saveSettings(rData.IndividualConfigs[it->first]); } return hrc; } /** * Internal worker that saves the settings after a modification was made. * * @returns COM status code. * * @note Caller must not hold any locks! */ HRESULT DHCPServer::i_doSaveSettings() { // save the global settings; for that we should hold only the VirtualBox lock AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS); return m->pVirtualBox->i_saveSettings(); } HRESULT DHCPServer::getNetworkName(com::Utf8Str &aName) { /* The name is const, so no need to for locking. */ return aName.assignEx(m->strName); } HRESULT DHCPServer::getEnabled(BOOL *aEnabled) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); *aEnabled = m->enabled; return S_OK; } HRESULT DHCPServer::setEnabled(BOOL aEnabled) { { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); m->enabled = aEnabled; } return i_doSaveSettings(); } HRESULT DHCPServer::getIPAddress(com::Utf8Str &aIPAddress) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); return aIPAddress.assignEx(m->IPAddress); } HRESULT DHCPServer::getNetworkMask(com::Utf8Str &aNetworkMask) { return m->globalConfig->i_getNetworkMask(aNetworkMask); } HRESULT DHCPServer::getLowerIP(com::Utf8Str &aIPAddress) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); return aIPAddress.assignEx(m->lowerIP); } HRESULT DHCPServer::getUpperIP(com::Utf8Str &aIPAddress) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); return aIPAddress.assignEx(m->upperIP); } HRESULT DHCPServer::setConfiguration(const com::Utf8Str &aIPAddress, const com::Utf8Str &aNetworkMask, const com::Utf8Str &aLowerIP, const com::Utf8Str &aUpperIP) { RTNETADDRIPV4 IPAddress, NetworkMask, LowerIP, UpperIP; int vrc = RTNetStrToIPv4Addr(aIPAddress.c_str(), &IPAddress); if (RT_FAILURE(vrc)) return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid server address: %s"), aIPAddress.c_str()); vrc = RTNetStrToIPv4Addr(aNetworkMask.c_str(), &NetworkMask); if (RT_FAILURE(vrc)) return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid netmask: %s"), aNetworkMask.c_str()); vrc = RTNetStrToIPv4Addr(aLowerIP.c_str(), &LowerIP); if (RT_FAILURE(vrc)) return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid range lower address: %s"), aLowerIP.c_str()); vrc = RTNetStrToIPv4Addr(aUpperIP.c_str(), &UpperIP); if (RT_FAILURE(vrc)) return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid range upper address: %s"), aUpperIP.c_str()); /* * Insist on continuous mask. May be also accept prefix length * here or address/prefix for aIPAddress? */ vrc = RTNetMaskToPrefixIPv4(&NetworkMask, NULL); if (RT_FAILURE(vrc)) return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid netmask: %s"), aNetworkMask.c_str()); /* It's more convenient to convert to host order once: */ IPAddress.u = RT_N2H_U32(IPAddress.u); NetworkMask.u = RT_N2H_U32(NetworkMask.u); LowerIP.u = RT_N2H_U32(LowerIP.u); UpperIP.u = RT_N2H_U32(UpperIP.u); /* * Addresses must be unicast and from the same network */ if ( (IPAddress.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000) || (IPAddress.u & ~NetworkMask.u) == 0 || ((IPAddress.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff)) return setError(E_INVALIDARG, tr("Invalid server address: %s (mask %s)"), aIPAddress.c_str(), aNetworkMask.c_str()); if ( (LowerIP.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000) || (LowerIP.u & NetworkMask.u) != (IPAddress.u &NetworkMask.u) || (LowerIP.u & ~NetworkMask.u) == 0 || ((LowerIP.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff)) return setError(E_INVALIDARG, tr("Invalid range lower address: %s (mask %s)"), aLowerIP.c_str(), aNetworkMask.c_str()); if ( (UpperIP.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000) || (UpperIP.u & NetworkMask.u) != (IPAddress.u &NetworkMask.u) || (UpperIP.u & ~NetworkMask.u) == 0 || ((UpperIP.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff)) return setError(E_INVALIDARG, tr("Invalid range upper address"), aUpperIP.c_str(), aNetworkMask.c_str()); /* The range should be valid ... */ if (LowerIP.u > UpperIP.u) return setError(E_INVALIDARG, tr("Lower bound must be less or eqaul than the upper: %s vs %s"), aLowerIP.c_str(), aUpperIP.c_str()); /* ... and shouldn't contain the server's address */ if (LowerIP.u <= IPAddress.u && IPAddress.u <= UpperIP.u) return setError(E_INVALIDARG, tr("Server address within range bounds: %s in %s - %s"), aIPAddress.c_str(), aLowerIP.c_str(), aUpperIP.c_str()); /* * Input is valid, effect the changes. */ HRESULT hrc; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); m->IPAddress = aIPAddress; m->lowerIP = aLowerIP; m->upperIP = aUpperIP; hrc = m->globalConfig->i_setNetworkMask(aNetworkMask); } if (SUCCEEDED(hrc)) hrc = i_doSaveSettings(); return hrc; } /** * Used by the legacy 6.0 IDHCPServer::GetVmSlotOptions() and * IDHCPServer::GetGlobalOptions() implementations. * * New interfaces have the option number and option encoding separate from the * value. */ HRESULT DHCPServer::i_encode60Option(com::Utf8Str &strEncoded, DhcpOpt_T enmOption, DHCPOptionEncoding_T enmEncoding, const com::Utf8Str &strValue) { int vrc; switch (enmEncoding) { case DHCPOptionEncoding_Legacy: { /* * This is original encoding which assumed that for each * option we know its format and so we know how option * "value" text is to be interpreted. * * "2:10800" # integer 32 * "6:1.2.3.4 8.8.8.8" # array of ip-address */ vrc = strEncoded.printfNoThrow("%d:%s", (int)enmOption, strValue.c_str()); break; } case DHCPOptionEncoding_Hex: { /* * This is a bypass for any option - preformatted value as * hex string with no semantic involved in formatting the * value for the DHCP reply. * * 234=68:65:6c:6c:6f:2c:20:77:6f:72:6c:64 */ vrc = strEncoded.printfNoThrow("%d=%s", (int)enmOption, strValue.c_str()); break; } default: { /* * Try to be forward compatible. * * "254@42=i hope you know what this means" */ vrc = strEncoded.printfNoThrow("%d@%d=%s", (int)enmOption, (int)enmEncoding, strValue.c_str()); break; } } return RT_SUCCESS(vrc) ? S_OK : E_OUTOFMEMORY; } /** * worker for IDHCPServer::GetGlobalOptions. */ HRESULT DHCPServer::i_getAllOptions60(DHCPConfig &aSourceConfig, std::vector &aValues) { /* Get the values using the new getter: */ std::vector Options; std::vector Encodings; std::vector Values; HRESULT hrc = aSourceConfig.i_getAllOptions(Options, Encodings, Values); if (SUCCEEDED(hrc)) { /* Encoding them using in the 6.0 style: */ size_t const cValues = Values.size(); aValues.resize(cValues); for (size_t i = 0; i < cValues && SUCCEEDED(hrc); i++) hrc = i_encode60Option(aValues[i], Options[i], Encodings[i], Values[i]); } return hrc; } /** * Worker for legacy <=6.0 interfaces for adding options. * * @throws std::bad_alloc */ HRESULT DHCPServer::i_add60Option(DHCPConfig &aTargetConfig, DhcpOpt_T aOption, const com::Utf8Str &aValue) { if (aOption != 0) return aTargetConfig.i_setOption(aOption, DHCPOptionEncoding_Legacy, aValue); /* * This is a kludge to sneak in option encoding information * through existing API. We use option 0 and supply the real * option/value in the same format that i_encode60Option() above * produces for getter methods. */ uint8_t u8Code; char *pszNext; int vrc = RTStrToUInt8Ex(aValue.c_str(), &pszNext, 10, &u8Code); if (RT_FAILURE(vrc)) return setErrorBoth(E_INVALIDARG, VERR_PARSE_ERROR); DHCPOptionEncoding_T enmEncoding; switch (*pszNext) { case ':': /* support legacy format too */ { enmEncoding = DHCPOptionEncoding_Legacy; break; } case '=': { enmEncoding = DHCPOptionEncoding_Hex; break; } case '@': { uint32_t u32Enc = 0; vrc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &u32Enc); if (RT_FAILURE(vrc)) return setErrorBoth(E_INVALIDARG, VERR_PARSE_ERROR); if (*pszNext != '=') return setErrorBoth(E_INVALIDARG, VERR_PARSE_ERROR); enmEncoding = (DHCPOptionEncoding_T)u32Enc; break; } default: return VERR_PARSE_ERROR; } return aTargetConfig.i_setOption(aOption, enmEncoding, com::Utf8Str(pszNext + 1)); } HRESULT DHCPServer::addGlobalOption(DhcpOpt_T aOption, const com::Utf8Str &aValue) { return i_add60Option(*m->globalConfig, aOption, aValue); } HRESULT DHCPServer::removeGlobalOption(DhcpOpt_T aOption) { return m->globalConfig->i_removeOption(aOption); } HRESULT DHCPServer::removeGlobalOptions() { return m->globalConfig->i_removeAllOptions(); } HRESULT DHCPServer::getGlobalOptions(std::vector &aValues) { return i_getAllOptions60(*m->globalConfig, aValues); } HRESULT DHCPServer::getVmConfigs(std::vector &aValues) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); try { aValues.resize(m->individualConfigs.size()); size_t i = 0; for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it, i++) if (it->second->i_getScope() != DHCPConfigScope_MAC) aValues[i].printf("[%RTuuid]:%d", it->second->i_getMachineId().raw(), it->second->i_getSlot()); else aValues[i].printf("[%RTmac]", it->second->i_getMACAddress()); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } return S_OK; } HRESULT DHCPServer::i_vmNameToIdAndValidateSlot(const com::Utf8Str &aVmName, LONG aSlot, com::Guid &idMachine) { if ((ULONG)aSlot <= 32) { /* Is it a UUID? */ idMachine = aVmName; if (idMachine.isValid() && !idMachine.isZero()) return S_OK; /* No, find the VM and get it's UUID. */ ComObjPtr ptrMachine; HRESULT hrc = m->pVirtualBox->i_findMachine(aVmName, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine); if (SUCCEEDED(hrc)) idMachine = ptrMachine->i_getId(); return hrc; } return setError(E_INVALIDARG, tr("NIC slot number (%d) is out of range (0..32)"), aSlot); } /** * Translates a VM name/id and slot to an individual configuration object. * * @returns COM status code. * @param a_strVmName The VM name or ID. * @param a_uSlot The NIC slot. * @param a_fCreateIfNeeded Whether to create a new entry if not found. * @param a_rPtrConfig Where to return the config object. It's * implicitly referenced, so we don't be returning * with any locks held. * * @note Caller must not be holding any locks! */ HRESULT DHCPServer::i_vmNameAndSlotToConfig(const com::Utf8Str &a_strVmName, LONG a_uSlot, bool a_fCreateIfNeeded, ComObjPtr &a_rPtrConfig) { /* * Validate the slot and normalize the name into a UUID. */ com::Guid idMachine; HRESULT hrc = i_vmNameToIdAndValidateSlot(a_strVmName, a_uSlot, idMachine); if (SUCCEEDED(hrc)) { Utf8Str strKey; int vrc = strKey.printfNoThrow("%RTuuid/%u", idMachine.raw(), a_uSlot); if (RT_SUCCESS(vrc)) { /* * Look it up. */ { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Data::IndividualConfigIterator it = m->individualConfigs.find(strKey); if (it != m->individualConfigs.end()) { a_rPtrConfig = it->second; return S_OK; } } if (a_fCreateIfNeeded) { /* * Create a new slot. */ /* Instantiate the object: */ hrc = a_rPtrConfig.createObject(); if (SUCCEEDED(hrc)) hrc = a_rPtrConfig->initWithMachineIdAndSlot(m->pVirtualBox, this, idMachine, a_uSlot, m->uIndividualMACAddressVersion - UINT32_MAX / 4); if (SUCCEEDED(hrc)) { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Check for creation race: */ Data::IndividualConfigIterator it = m->individualConfigs.find(strKey); if (it != m->individualConfigs.end()) { a_rPtrConfig.setNull(); a_rPtrConfig = it->second; return S_OK; } /* Add it. */ try { m->individualConfigs[strKey] = a_rPtrConfig; return S_OK; } catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; } a_rPtrConfig.setNull(); } } else hrc = VBOX_E_OBJECT_NOT_FOUND; } else hrc = E_OUTOFMEMORY; } return hrc; } HRESULT DHCPServer::addVmSlotOption(const com::Utf8Str &aVmName, LONG aSlot, DhcpOpt_T aOption, const com::Utf8Str &aValue) { ComObjPtr ptrConfig; HRESULT hrc = i_vmNameAndSlotToConfig(aVmName, aSlot, true, ptrConfig); if (SUCCEEDED(hrc)) hrc = i_add60Option(*ptrConfig, aOption, aValue); return hrc; } HRESULT DHCPServer::removeVmSlotOption(const com::Utf8Str &aVmName, LONG aSlot, DhcpOpt_T aOption) { ComObjPtr ptrConfig; HRESULT hrc = i_vmNameAndSlotToConfig(aVmName, aSlot, false, ptrConfig); if (SUCCEEDED(hrc)) hrc = ptrConfig->i_removeOption(aOption); return hrc; } HRESULT DHCPServer::removeVmSlotOptions(const com::Utf8Str &aVmName, LONG aSlot) { ComObjPtr ptrConfig; HRESULT hrc = i_vmNameAndSlotToConfig(aVmName, aSlot, false, ptrConfig); if (SUCCEEDED(hrc)) hrc = ptrConfig->i_removeAllOptions(); return hrc; } HRESULT DHCPServer::getVmSlotOptions(const com::Utf8Str &aVmName, LONG aSlot, std::vector &aValues) { ComObjPtr ptrConfig; HRESULT hrc = i_vmNameAndSlotToConfig(aVmName, aSlot, false, ptrConfig); if (SUCCEEDED(hrc)) hrc = i_getAllOptions60(*ptrConfig, aValues); else if (hrc == VBOX_E_OBJECT_NOT_FOUND) { aValues.resize(0); hrc = S_OK; } return hrc; } HRESULT DHCPServer::getMacOptions(const com::Utf8Str &aMAC, std::vector &aOption) { RT_NOREF(aMAC, aOption); AssertFailed(); return setError(E_NOTIMPL, tr("The GetMacOptions method has been discontinued as it was only supposed to be used by the DHCP server and it does not need it any more. sorry")); } HRESULT DHCPServer::getEventSource(ComPtr &aEventSource) { NOREF(aEventSource); ReturnComNotImplemented(); } HRESULT DHCPServer::getGlobalConfig(ComPtr &aGlobalConfig) { /* The global configuration is immutable, so no need to lock anything here. */ return m->globalConfig.queryInterfaceTo(aGlobalConfig.asOutParam()); } HRESULT DHCPServer::getGroupConfigs(std::vector > &aGroupConfigs) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); #if 0 /** @todo implement group configs */ try { aGroupConfigs.resize(m->groupConfigs.size()); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } size_t i = 0; for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end(); ++it) { HRESULT hrc = it->second.queryInterfaceTo(aGroupConfigs[i].asOutParam()); if (FAILED(hrc)) return hrc; } #else aGroupConfigs.resize(0); #endif return S_OK; } HRESULT DHCPServer::getIndividualConfigs(std::vector > &aIndividualConfigs) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); try { aIndividualConfigs.resize(m->individualConfigs.size()); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } size_t i = 0; for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it) { HRESULT hrc = it->second.queryInterfaceTo(aIndividualConfigs[i].asOutParam()); if (FAILED(hrc)) return hrc; } return S_OK; } HRESULT DHCPServer::restart() { if (!m->dhcp.isRunning()) return setErrorBoth(E_FAIL, VERR_PROCESS_NOT_FOUND, tr("not running")); /* * Disabled servers will be brought down, but won't be restarted. * (see DHCPServer::start) */ HRESULT hrc = stop(); if (SUCCEEDED(hrc)) hrc = start(m->networkName, m->trunkName, m->trunkType); return hrc; } /** * @throws std::bad_alloc */ HRESULT DHCPServer::i_writeDhcpdConfig(const char *pszFilename, uint32_t uMACAddressVersion) { /* * Produce the DHCP server configuration. */ xml::Document doc; try { xml::ElementNode *pElmRoot = doc.createRootElement("DHCPServer"); pElmRoot->setAttribute("networkName", m->networkName); if (m->trunkName.isNotEmpty()) pElmRoot->setAttribute("trunkName", m->trunkName); pElmRoot->setAttribute("trunkType", m->trunkType); pElmRoot->setAttribute("IPAddress", m->IPAddress); pElmRoot->setAttribute("lowerIP", m->lowerIP); pElmRoot->setAttribute("upperIP", m->upperIP); pElmRoot->setAttribute("leasesFilename", m->strLeasesFilename); Utf8Str strNetworkMask; HRESULT hrc = m->globalConfig->i_getNetworkMask(strNetworkMask); if (FAILED(hrc)) return hrc; pElmRoot->setAttribute("networkMask", strNetworkMask); /* * Process global options */ m->globalConfig->i_writeDhcpdConfig(pElmRoot->createChild("Options")); /* * Groups. */ //for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end(); ++it) // it->second->i_writeDhcpdConfig(pElmRoot->createChild("Config")); /* * Individual NIC configurations. */ for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it) if (it->second->i_isMACAddressResolved(uMACAddressVersion)) it->second->i_writeDhcpdConfig(pElmRoot->createChild("Config")); else LogRelFunc(("Skipping %RTuuid/%u, no MAC address.\n", it->second->i_getMachineId().raw(), it->second->i_getSlot())); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } /* * Write out the document. */ try { xml::XmlFileWriter writer(doc); writer.write(pszFilename, false); } catch (...) { return E_FAIL; } return S_OK; } /** @todo r=bird: why do we get the network name passed in here? it's the same * as m->strName, isn't it? */ HRESULT DHCPServer::start(const com::Utf8Str &aNetworkName, const com::Utf8Str &aTrunkName, const com::Utf8Str &aTrunkType) { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Silently ignore attempts to run disabled servers. */ if (!m->enabled) return S_OK; /* * Resolve the MAC addresses. This requires us to leave the lock. */ uint32_t uMACAddressVersion = m->uIndividualMACAddressVersion; if (m->individualConfigs.size() > 0) { m->uIndividualMACAddressVersion = uMACAddressVersion + 1; /* Retain pointers to all the individual configuration objects so we can safely access these after releaseing the lock: */ std::vector< ComObjPtr > vecIndividualConfigs; try { vecIndividualConfigs.resize(m->individualConfigs.size()); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } size_t i = 0; for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it, i++) vecIndividualConfigs[i] = it->second; /* Drop the lock and resolve the MAC addresses: */ alock.release(); i = vecIndividualConfigs.size(); while (i-- > 0) vecIndividualConfigs[i]->i_resolveMACAddress(uMACAddressVersion); /* Reacquire the lock */ alock.acquire(); if (!m->enabled) return S_OK; } /* * Refuse to start a 2nd DHCP server instance for the same network. */ if (m->dhcp.isRunning()) return setErrorBoth(VBOX_E_OBJECT_IN_USE, VERR_PROCESS_RUNNING, tr("Cannot start DHCP server because it is already running")); /* * Copy the startup parameters. */ m->networkName = aNetworkName; m->trunkName = aTrunkName; m->trunkType = aTrunkType; HRESULT hrc = i_calcLeasesFilename(aNetworkName); if (SUCCEEDED(hrc)) { /* * Create configuration file path and write out the configuration. */ /** @todo put this next to the leases file. */ int vrc = RTPathTemp(m->szTempConfigFileName, sizeof(m->szTempConfigFileName)); if (RT_SUCCESS(vrc)) vrc = RTPathAppend(m->szTempConfigFileName, sizeof(m->szTempConfigFileName), "dhcp-config-XXXXX.xml"); if (RT_SUCCESS(vrc)) vrc = RTFileCreateTemp(m->szTempConfigFileName, 0600); if (RT_SUCCESS(vrc)) { hrc = i_writeDhcpdConfig(m->szTempConfigFileName, uMACAddressVersion); if (SUCCEEDED(hrc)) { /* * Setup the arguments and start the DHCP server. */ m->dhcp.resetArguments(); vrc = m->dhcp.addArgPair(DHCPServerRunner::kDsrKeyConfig, m->szTempConfigFileName); if (RT_SUCCESS(vrc)) vrc = m->dhcp.addArgPair(DHCPServerRunner::kDsrKeyComment, m->networkName.c_str()); if (RT_SUCCESS(vrc)) vrc = m->dhcp.start(true /*aKillProcessOnStop*/); if (RT_FAILURE(vrc)) hrc = setErrorVrc(vrc, tr("Failed to start DHCP server for '%s': %Rrc"), m->strName.c_str(), vrc); } if (FAILED(hrc)) { RTFileDelete(m->szTempConfigFileName); m->szTempConfigFileName[0] = '\0'; } } else { m->szTempConfigFileName[0] = '\0'; hrc = setErrorVrc(vrc); } } return hrc; } HRESULT DHCPServer::stop(void) { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); if (m->szTempConfigFileName[0]) { RTFileDelete(m->szTempConfigFileName); m->szTempConfigFileName[0] = '\0'; } int vrc = m->dhcp.stop(); if (RT_SUCCESS(vrc)) return S_OK; return setErrorVrc(vrc); } HRESULT DHCPServer::findLeaseByMAC(const com::Utf8Str &aMac, LONG aType, com::Utf8Str &aAddress, com::Utf8Str &aState, LONG64 *aIssued, LONG64 *aExpire) { /* Reset output before we start */ *aIssued = 0; *aExpire = 0; aAddress.setNull(); aState.setNull(); /* * Convert and check input. */ RTMAC MacAddress; int vrc = RTStrConvertHexBytes(aMac.c_str(), &MacAddress, sizeof(MacAddress), RTSTRCONVERTHEXBYTES_F_SEP_COLON); if (vrc != VINF_SUCCESS) return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid MAC address '%s': %Rrc"), aMac.c_str(), vrc); if (aType != 0) return setError(E_INVALIDARG, tr("flags must be zero (not %#x)"), aType); /* * Make sure we've got a lease filename to work with. */ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); if (m->strLeasesFilename.isEmpty()) { HRESULT hrc = i_calcLeasesFilename(m->networkName.isEmpty() ? m->strName : m->networkName); if (FAILED(hrc)) return hrc; } /* * Try at least twice to read the lease database, more if busy. */ uint64_t const nsStart = RTTimeNanoTS(); for (uint32_t uReadAttempt = 0; ; uReadAttempt++) { /* * Try read the file. */ xml::Document doc; try { xml::XmlFileParser parser; parser.read(m->strLeasesFilename.c_str(), doc); } catch (const xml::EIPRTFailure &e) { vrc = e.rc(); LogThisFunc(("caught xml::EIPRTFailure: rc=%Rrc (attempt %u, msg=%s)\n", vrc, uReadAttempt, e.what())); if ( ( vrc == VERR_FILE_NOT_FOUND || vrc == VERR_OPEN_FAILED || vrc == VERR_ACCESS_DENIED || vrc == VERR_SHARING_VIOLATION || vrc == VERR_READ_ERROR /*?*/) && ( uReadAttempt == 0 || ( uReadAttempt < 64 && RTTimeNanoTS() - nsStart < RT_NS_1SEC / 4)) ) { alock.release(); if (uReadAttempt > 0) RTThreadYield(); RTThreadSleep(8/*ms*/); alock.acquire(); LogThisFunc(("Retrying...\n")); continue; } return setErrorBoth(VBOX_E_FILE_ERROR, vrc, "Reading '%s' failed: %Rrc - %s", m->strLeasesFilename.c_str(), vrc, e.what()); } catch (const RTCError &e) { if (e.what()) return setError(VBOX_E_FILE_ERROR, "Reading '%s' failed: %s", m->strLeasesFilename.c_str(), e.what()); return setError(VBOX_E_FILE_ERROR, "Reading '%s' failed: RTCError", m->strLeasesFilename.c_str()); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } catch (...) { AssertFailed(); return setError(VBOX_E_FILE_ERROR, tr("Reading '%s' failed: Unexpected exception"), m->strLeasesFilename.c_str()); } /* * Look for that mac address. */ xml::ElementNode *pElmRoot = doc.getRootElement(); if (pElmRoot && pElmRoot->nameEquals("Leases")) { xml::NodesLoop it(*pElmRoot); const xml::ElementNode *pElmLease; while ((pElmLease = it.forAllNodes()) != NULL) if (pElmLease->nameEquals("Lease")) { const char *pszCurMacAddress = pElmLease->findAttributeValue("mac"); RTMAC CurMacAddress; if ( pszCurMacAddress && RT_SUCCESS(RTNetStrToMacAddr(pszCurMacAddress, &CurMacAddress)) && memcmp(&CurMacAddress, &MacAddress, sizeof(MacAddress)) == 0) { /* * Found it! */ xml::ElementNode const *pElmTime = pElmLease->findChildElement("Time"); int64_t secIssued = 0; uint32_t cSecsToLive = 0; if (pElmTime) { pElmTime->getAttributeValue("issued", &secIssued); pElmTime->getAttributeValue("expiration", &cSecsToLive); *aIssued = secIssued; *aExpire = secIssued + cSecsToLive; } try { aAddress = pElmLease->findChildElementAttributeValue("Address", "value"); aState = pElmLease->findAttributeValue("state"); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } /* Check if the lease has expired in the mean time. */ HRESULT hrc = S_OK; RTTIMESPEC Now; if ( (aState.equals("acked") || aState.equals("offered") || aState.isEmpty()) && secIssued + cSecsToLive < RTTimeSpecGetSeconds(RTTimeNow(&Now))) hrc = aState.assignNoThrow("expired"); return hrc; } } } break; } return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a lease for %RTmac"), &MacAddress); } HRESULT DHCPServer::getConfig(DHCPConfigScope_T aScope, const com::Utf8Str &aName, ULONG aSlot, BOOL aMayAdd, ComPtr &aConfig) { if (aSlot != 0 && aScope != DHCPConfigScope_MachineNIC) return setError(E_INVALIDARG, tr("The 'slot' argument must be zero for all but the MachineNIC scope!")); switch (aScope) { case DHCPConfigScope_Global: if (aName.isNotEmpty()) return setError(E_INVALIDARG, tr("The name must be empty or NULL for the Global scope!")); /* No locking required here. */ return m->globalConfig.queryInterfaceTo(aConfig.asOutParam()); case DHCPConfigScope_Group: return setError(E_NOTIMPL, tr("Groups are not yet implemented, sorry.")); case DHCPConfigScope_MachineNIC: { ComObjPtr ptrIndividualConfig; HRESULT hrc = i_vmNameAndSlotToConfig(aName, aSlot, aMayAdd != FALSE, ptrIndividualConfig); if (SUCCEEDED(hrc)) hrc = ptrIndividualConfig.queryInterfaceTo(aConfig.asOutParam()); return hrc; } case DHCPConfigScope_MAC: { /* Check and Normalize the MAC address into a key: */ RTMAC MACAddress; int vrc = RTNetStrToMacAddr(aName.c_str(), &MACAddress); if (RT_SUCCESS(vrc)) { Utf8Str strKey; vrc = strKey.printfNoThrow("%RTmac", &MACAddress); if (RT_SUCCESS(vrc)) { /* Look up the MAC address: */ { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Data::IndividualConfigIterator it = m->individualConfigs.find(strKey); if (it != m->individualConfigs.end()) return it->second.queryInterfaceTo(aConfig.asOutParam()); } if (aMayAdd) { /* Create a new individiual configuration: */ ComObjPtr ptrIndividualConfig; HRESULT hrc = ptrIndividualConfig.createObject(); if (SUCCEEDED(hrc)) hrc = ptrIndividualConfig->initWithMACAddress(m->pVirtualBox, this, &MACAddress); if (SUCCEEDED(hrc)) { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Check for insertion race: */ Data::IndividualConfigIterator it = m->individualConfigs.find(strKey); if (it != m->individualConfigs.end()) return it->second.queryInterfaceTo(aConfig.asOutParam()); /* creation race*/ /* Try insert it: */ try { m->individualConfigs[strKey] = ptrIndividualConfig; } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } return ptrIndividualConfig.queryInterfaceTo(aConfig.asOutParam()); } } else return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Found no configuration for MAC address %s"), strKey.c_str()); } return E_OUTOFMEMORY; } return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid MAC address: %s"), aName.c_str()); } default: return E_FAIL; } } /** * Calculates and updates the value of strLeasesFilename given @a aNetwork. */ HRESULT DHCPServer::i_calcLeasesFilename(const com::Utf8Str &aNetwork) RT_NOEXCEPT { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* The lease file must be the same as we used the last time, so careful when changing this code. */ int vrc = m->strLeasesFilename.assignNoThrow(m->pVirtualBox->i_homeDir()); if (RT_SUCCESS(vrc)) vrc = RTPathAppendCxx(m->strLeasesFilename, aNetwork); if (RT_SUCCESS(vrc)) vrc = m->strLeasesFilename.appendNoThrow("-Dhcpd.leases"); if (RT_SUCCESS(vrc)) { RTPathPurgeFilename(RTPathFilename(m->strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST); m->strLeasesFilename.jolt(); return S_OK; } return setErrorBoth(E_FAIL, vrc, tr("Failed to construct lease filename: %Rrc"), vrc); }