VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/DHCPServerImpl.cpp@ 90441

Last change on this file since 90441 was 85235, checked in by vboxsync, 4 years ago

Main/DHCPServerImpl: Fixed error code mixup in findLeaseByMAC() and corrected a signedness issue with the slot argument for i_vmNameToIdAndValidateSlot and i_vmNameAndSlotToConfig. bugref:9790

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.8 KB
Line 
1/* $Id: DHCPServerImpl.cpp 85235 2020-07-11 16:34:10Z vboxsync $ */
2/** @file
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_MAIN_DHCPSERVER
23#include "DHCPServerImpl.h"
24#include "LoggingNew.h"
25
26#include <iprt/asm.h>
27#include <iprt/err.h>
28#include <iprt/file.h>
29#include <iprt/net.h>
30#include <iprt/path.h>
31#include <iprt/cpp/path.h>
32#include <iprt/cpp/utils.h>
33#include <iprt/cpp/xml.h>
34
35#include <VBox/com/array.h>
36#include <VBox/settings.h>
37
38#include "AutoCaller.h"
39#include "DHCPConfigImpl.h"
40#include "MachineImpl.h"
41#include "NetworkServiceRunner.h"
42#include "VirtualBoxImpl.h"
43
44
45/*********************************************************************************************************************************
46* Defined Constants And Macros *
47*********************************************************************************************************************************/
48#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
49# define DHCP_EXECUTABLE_NAME "VBoxNetDHCP.exe"
50#else
51# define DHCP_EXECUTABLE_NAME "VBoxNetDHCP"
52#endif
53
54
55/**
56 * DHCP server specialization of NetworkServiceRunner.
57 *
58 * Just defines the executable name and adds option constants.
59 */
60class DHCPServerRunner : public NetworkServiceRunner
61{
62public:
63 DHCPServerRunner() : NetworkServiceRunner(DHCP_EXECUTABLE_NAME)
64 {}
65 virtual ~DHCPServerRunner()
66 {}
67};
68
69
70/**
71 * Hidden private data of the DHCPServer class.
72 */
73struct DHCPServer::Data
74{
75 Data()
76 : pVirtualBox(NULL)
77 , strName()
78 , enabled(FALSE)
79 , uIndividualMACAddressVersion(1)
80 {
81 }
82
83 /** weak VirtualBox parent */
84 VirtualBox * const pVirtualBox;
85 /** The DHCP server name (network). */
86 Utf8Str const strName;
87
88 Utf8Str IPAddress;
89 Utf8Str lowerIP;
90 Utf8Str upperIP;
91
92 BOOL enabled;
93 DHCPServerRunner dhcp;
94
95 com::Utf8Str strLeasesFilename;
96 com::Utf8Str strConfigFilename;
97 com::Utf8Str strLogFilename;
98
99 com::Utf8Str trunkName;
100 com::Utf8Str trunkType;
101
102 /** Global configuration. */
103 ComObjPtr<DHCPGlobalConfig> globalConfig;
104
105 /** Group configuration indexed by name. */
106 std::map<com::Utf8Str, ComObjPtr<DHCPGroupConfig> > groupConfigs;
107 /** Iterator for groupConfigs. */
108 typedef std::map<com::Utf8Str, ComObjPtr<DHCPGroupConfig> >::iterator GroupConfigIterator;
109
110 /** Individual (host) configuration indexed by MAC address or VM UUID. */
111 std::map<com::Utf8Str, ComObjPtr<DHCPIndividualConfig> > individualConfigs;
112 /** Iterator for individualConfigs. */
113 typedef std::map<com::Utf8Str, ComObjPtr<DHCPIndividualConfig> >::iterator IndividualConfigIterator;
114
115 /** Part of a lock-avoidance hack to resolve the VM ID + slot into MAC
116 * addresses before writing out the Dhcpd configuration file. */
117 uint32_t uIndividualMACAddressVersion;
118};
119
120
121// constructor / destructor
122/////////////////////////////////////////////////////////////////////////////
123
124
125DHCPServer::DHCPServer()
126 : m(NULL)
127{
128 m = new DHCPServer::Data();
129}
130
131
132DHCPServer::~DHCPServer()
133{
134 if (m)
135 {
136 delete m;
137 m = NULL;
138 }
139}
140
141
142HRESULT DHCPServer::FinalConstruct()
143{
144 return BaseFinalConstruct();
145}
146
147
148void DHCPServer::FinalRelease()
149{
150 uninit();
151 BaseFinalRelease();
152}
153
154
155void DHCPServer::uninit()
156{
157 /* Enclose the state transition Ready->InUninit->NotReady */
158 AutoUninitSpan autoUninitSpan(this);
159 if (autoUninitSpan.uninitDone())
160 return;
161
162 if (m->dhcp.isRunning())
163 stop();
164
165 unconst(m->pVirtualBox) = NULL;
166}
167
168
169HRESULT DHCPServer::init(VirtualBox *aVirtualBox, const Utf8Str &aName)
170{
171 AssertReturn(!aName.isEmpty(), E_INVALIDARG);
172
173 AutoInitSpan autoInitSpan(this);
174 AssertReturn(autoInitSpan.isOk(), E_FAIL);
175
176 /* share VirtualBox weakly (parent remains NULL so far) */
177 unconst(m->pVirtualBox) = aVirtualBox;
178
179 unconst(m->strName) = aName;
180 m->IPAddress = "0.0.0.0";
181 m->lowerIP = "0.0.0.0";
182 m->upperIP = "0.0.0.0";
183 m->enabled = FALSE;
184
185 /* Global configuration: */
186 HRESULT hrc = m->globalConfig.createObject();
187 if (SUCCEEDED(hrc))
188 hrc = m->globalConfig->initWithDefaults(aVirtualBox, this);
189
190 Assert(m->groupConfigs.size() == 0);
191 Assert(m->individualConfigs.size() == 0);
192
193 /* Confirm a successful initialization or not: */
194 if (SUCCEEDED(hrc))
195 autoInitSpan.setSucceeded();
196 else
197 autoInitSpan.setFailed(hrc);
198 return hrc;
199}
200
201
202HRESULT DHCPServer::init(VirtualBox *aVirtualBox, const settings::DHCPServer &rData)
203{
204 /* Enclose the state transition NotReady->InInit->Ready */
205 AutoInitSpan autoInitSpan(this);
206 AssertReturn(autoInitSpan.isOk(), E_FAIL);
207
208 /* share VirtualBox weakly (parent remains NULL so far) */
209 unconst(m->pVirtualBox) = aVirtualBox;
210
211 unconst(m->strName) = rData.strNetworkName;
212 m->IPAddress = rData.strIPAddress;
213 m->enabled = rData.fEnabled;
214 m->lowerIP = rData.strIPLower;
215 m->upperIP = rData.strIPUpper;
216
217 /*
218 * Global configuration:
219 */
220 HRESULT hrc = m->globalConfig.createObject();
221 if (SUCCEEDED(hrc))
222 hrc = m->globalConfig->initWithSettings(aVirtualBox, this, rData.globalConfig);
223
224 /*
225 * Group configurations:
226 */
227 Assert(m->groupConfigs.size() == 0);
228 for (settings::DHCPGroupConfigVec::const_iterator it = rData.vecGroupConfigs.begin();
229 it != rData.vecGroupConfigs.end() && SUCCEEDED(hrc); ++it)
230 {
231 ComObjPtr<DHCPGroupConfig> ptrGroupConfig;
232 hrc = ptrGroupConfig.createObject();
233 if (SUCCEEDED(hrc))
234 hrc = ptrGroupConfig->initWithSettings(aVirtualBox, this, *it);
235 if (SUCCEEDED(hrc))
236 {
237 try
238 {
239 m->groupConfigs[it->strName] = ptrGroupConfig;
240 }
241 catch (std::bad_alloc &)
242 {
243 return E_OUTOFMEMORY;
244 }
245 }
246 }
247
248 /*
249 * Individual configuration:
250 */
251 Assert(m->individualConfigs.size() == 0);
252 for (settings::DHCPIndividualConfigMap::const_iterator it = rData.mapIndividualConfigs.begin();
253 it != rData.mapIndividualConfigs.end() && SUCCEEDED(hrc); ++it)
254 {
255 ComObjPtr<DHCPIndividualConfig> ptrIndiCfg;
256 com::Utf8Str strKey;
257 if (it->second.strVMName.isEmpty())
258 {
259 RTMAC MACAddress;
260 int vrc = RTNetStrToMacAddr(it->second.strMACAddress.c_str(), &MACAddress);
261 if (RT_FAILURE(vrc))
262 {
263 LogRel(("Ignoring invalid MAC address for individual DHCP config: '%s' - %Rrc\n", it->second.strMACAddress.c_str(), vrc));
264 continue;
265 }
266
267 vrc = strKey.printfNoThrow("%RTmac", &MACAddress);
268 AssertRCReturn(vrc, E_OUTOFMEMORY);
269
270 hrc = ptrIndiCfg.createObject();
271 if (SUCCEEDED(hrc))
272 hrc = ptrIndiCfg->initWithSettingsAndMACAddress(aVirtualBox, this, it->second, &MACAddress);
273 }
274 else
275 {
276 /* This ASSUMES that we're being called after the machines have been
277 loaded so we can resolve VM names into UUID for old settings. */
278 com::Guid idMachine;
279 hrc = i_vmNameToIdAndValidateSlot(it->second.strVMName, it->second.uSlot, idMachine);
280 if (SUCCEEDED(hrc))
281 {
282 int vrc = strKey.printfNoThrow("%RTuuid/%u", idMachine.raw(), it->second.uSlot);
283 AssertRCReturn(vrc, E_OUTOFMEMORY);
284
285 hrc = ptrIndiCfg.createObject();
286 if (SUCCEEDED(hrc))
287 hrc = ptrIndiCfg->initWithSettingsAndMachineIdAndSlot(aVirtualBox, this, it->second,
288 idMachine, it->second.uSlot,
289 m->uIndividualMACAddressVersion - UINT32_MAX / 4);
290 }
291 }
292 if (SUCCEEDED(hrc))
293 {
294 try
295 {
296 m->individualConfigs[strKey] = ptrIndiCfg;
297 }
298 catch (std::bad_alloc &)
299 {
300 return E_OUTOFMEMORY;
301 }
302 }
303 }
304
305 /* Confirm a successful initialization or not: */
306 if (SUCCEEDED(hrc))
307 autoInitSpan.setSucceeded();
308 else
309 autoInitSpan.setFailed(hrc);
310 return hrc;
311}
312
313
314/**
315 * Called by VirtualBox to save our settings.
316 */
317HRESULT DHCPServer::i_saveSettings(settings::DHCPServer &rData)
318{
319 AutoCaller autoCaller(this);
320 if (FAILED(autoCaller.rc())) return autoCaller.rc();
321
322 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
323
324 rData.strNetworkName = m->strName;
325 rData.strIPAddress = m->IPAddress;
326 rData.fEnabled = m->enabled != FALSE;
327 rData.strIPLower = m->lowerIP;
328 rData.strIPUpper = m->upperIP;
329
330 /* Global configuration: */
331 HRESULT hrc = m->globalConfig->i_saveSettings(rData.globalConfig);
332
333 /* Group configuration: */
334 size_t const cGroupConfigs = m->groupConfigs.size();
335 try
336 {
337 rData.vecGroupConfigs.resize(cGroupConfigs);
338 }
339 catch (std::bad_alloc &)
340 {
341 return E_OUTOFMEMORY;
342 }
343 size_t i = 0;
344 for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end() && SUCCEEDED(hrc); ++it, i++)
345 {
346 try
347 {
348 rData.vecGroupConfigs[i] = settings::DHCPGroupConfig();
349 }
350 catch (std::bad_alloc &)
351 {
352 return E_OUTOFMEMORY;
353 }
354 hrc = it->second->i_saveSettings(rData.vecGroupConfigs[i]);
355 }
356
357 /* Individual configuration: */
358 for (Data::IndividualConfigIterator it = m->individualConfigs.begin();
359 it != m->individualConfigs.end() && SUCCEEDED(hrc); ++it)
360 {
361 try
362 {
363 rData.mapIndividualConfigs[it->first] = settings::DHCPIndividualConfig();
364 }
365 catch (std::bad_alloc &)
366 {
367 return E_OUTOFMEMORY;
368 }
369 hrc = it->second->i_saveSettings(rData.mapIndividualConfigs[it->first]);
370 }
371
372 return hrc;
373}
374
375
376HRESULT DHCPServer::i_removeConfig(DHCPConfig *pConfig, DHCPConfigScope_T enmScope)
377{
378 {
379 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
380
381 bool fFound = false;
382 switch (enmScope)
383 {
384 case DHCPConfigScope_Group:
385 {
386 for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end();)
387 {
388 DHCPConfig *pCurConfig = it->second;
389 if (pCurConfig == pConfig)
390 {
391 m->groupConfigs.erase(it++); /* Post increment returns copy of original that is then erased. */
392 fFound = true;
393 }
394 else
395 ++it;
396 }
397 break;
398 }
399
400 case DHCPConfigScope_MAC:
401 case DHCPConfigScope_MachineNIC:
402 {
403 for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end();)
404 {
405 DHCPConfig *pCurConfig = it->second;
406 if (pCurConfig == pConfig)
407 {
408 m->individualConfigs.erase(it++); /* Post increment returns copy of original that is then erased. */
409 fFound = true;
410 }
411 else
412 ++it;
413 }
414 break;
415 }
416
417 default:
418 AssertFailedReturn(E_FAIL);
419 }
420
421 /* Don't complain if already removed, right? */
422 if (!fFound)
423 return S_OK;
424 }
425
426 return i_doSaveSettings();
427}
428
429
430/**
431 * Internal worker that saves the settings after a modification was made.
432 *
433 * @returns COM status code.
434 *
435 * @note Caller must not hold any locks!
436 */
437HRESULT DHCPServer::i_doSaveSettings()
438{
439 // save the global settings; for that we should hold only the VirtualBox lock
440 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
441 return m->pVirtualBox->i_saveSettings();
442}
443
444
445HRESULT DHCPServer::getNetworkName(com::Utf8Str &aName)
446{
447 /* The name is const, so no need to for locking. */
448 return aName.assignEx(m->strName);
449}
450
451
452HRESULT DHCPServer::getEnabled(BOOL *aEnabled)
453{
454 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
455 *aEnabled = m->enabled;
456 return S_OK;
457}
458
459
460HRESULT DHCPServer::setEnabled(BOOL aEnabled)
461{
462 {
463 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
464 m->enabled = aEnabled;
465 }
466 return i_doSaveSettings();
467}
468
469
470HRESULT DHCPServer::getIPAddress(com::Utf8Str &aIPAddress)
471{
472 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
473 return aIPAddress.assignEx(m->IPAddress);
474}
475
476
477HRESULT DHCPServer::getNetworkMask(com::Utf8Str &aNetworkMask)
478{
479 return m->globalConfig->i_getNetworkMask(aNetworkMask);
480}
481
482
483HRESULT DHCPServer::getLowerIP(com::Utf8Str &aIPAddress)
484{
485 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
486 return aIPAddress.assignEx(m->lowerIP);
487}
488
489
490HRESULT DHCPServer::getUpperIP(com::Utf8Str &aIPAddress)
491{
492 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
493 return aIPAddress.assignEx(m->upperIP);
494}
495
496
497HRESULT DHCPServer::setConfiguration(const com::Utf8Str &aIPAddress,
498 const com::Utf8Str &aNetworkMask,
499 const com::Utf8Str &aLowerIP,
500 const com::Utf8Str &aUpperIP)
501{
502 RTNETADDRIPV4 IPAddress, NetworkMask, LowerIP, UpperIP;
503
504 int vrc = RTNetStrToIPv4Addr(aIPAddress.c_str(), &IPAddress);
505 if (RT_FAILURE(vrc))
506 return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid server address: %s"), aIPAddress.c_str());
507
508 vrc = RTNetStrToIPv4Addr(aNetworkMask.c_str(), &NetworkMask);
509 if (RT_FAILURE(vrc))
510 return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid netmask: %s"), aNetworkMask.c_str());
511
512 vrc = RTNetStrToIPv4Addr(aLowerIP.c_str(), &LowerIP);
513 if (RT_FAILURE(vrc))
514 return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid range lower address: %s"), aLowerIP.c_str());
515
516 vrc = RTNetStrToIPv4Addr(aUpperIP.c_str(), &UpperIP);
517 if (RT_FAILURE(vrc))
518 return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid range upper address: %s"), aUpperIP.c_str());
519
520 /*
521 * Insist on continuous mask. May be also accept prefix length
522 * here or address/prefix for aIPAddress?
523 */
524 vrc = RTNetMaskToPrefixIPv4(&NetworkMask, NULL);
525 if (RT_FAILURE(vrc))
526 return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid netmask: %s"), aNetworkMask.c_str());
527
528 /* It's more convenient to convert to host order once: */
529 IPAddress.u = RT_N2H_U32(IPAddress.u);
530 NetworkMask.u = RT_N2H_U32(NetworkMask.u);
531 LowerIP.u = RT_N2H_U32(LowerIP.u);
532 UpperIP.u = RT_N2H_U32(UpperIP.u);
533
534 /*
535 * Addresses must be unicast and from the same network
536 */
537 if ( (IPAddress.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
538 || (IPAddress.u & ~NetworkMask.u) == 0
539 || ((IPAddress.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
540 return setError(E_INVALIDARG, tr("Invalid server address: %s (mask %s)"), aIPAddress.c_str(), aNetworkMask.c_str());
541
542 if ( (LowerIP.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
543 || (LowerIP.u & NetworkMask.u) != (IPAddress.u &NetworkMask.u)
544 || (LowerIP.u & ~NetworkMask.u) == 0
545 || ((LowerIP.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
546 return setError(E_INVALIDARG, tr("Invalid range lower address: %s (mask %s)"), aLowerIP.c_str(), aNetworkMask.c_str());
547
548 if ( (UpperIP.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
549 || (UpperIP.u & NetworkMask.u) != (IPAddress.u &NetworkMask.u)
550 || (UpperIP.u & ~NetworkMask.u) == 0
551 || ((UpperIP.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
552 return setError(E_INVALIDARG, tr("Invalid range upper address"), aUpperIP.c_str(), aNetworkMask.c_str());
553
554 /* The range should be valid. (It's okay to overlap the server IP.) */
555 if (LowerIP.u > UpperIP.u)
556 return setError(E_INVALIDARG, tr("Lower bound must be less or eqaul than the upper: %s vs %s"),
557 aLowerIP.c_str(), aUpperIP.c_str());
558
559 /*
560 * Input is valid, effect the changes.
561 */
562 HRESULT hrc;
563 {
564 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
565 m->IPAddress = aIPAddress;
566 m->lowerIP = aLowerIP;
567 m->upperIP = aUpperIP;
568 hrc = m->globalConfig->i_setNetworkMask(aNetworkMask);
569 }
570 if (SUCCEEDED(hrc))
571 hrc = i_doSaveSettings();
572 return hrc;
573}
574
575
576/**
577 * Validates the VM name and slot, returning the machine ID.
578 *
579 * If a machine ID is given instead of a name, we won't check whether it
580 * actually exists...
581 *
582 * @returns COM status code.
583 * @param aVmName The VM name or UUID.
584 * @param a_uSlot The slot.
585 * @param idMachine Where to return the VM UUID.
586 */
587HRESULT DHCPServer::i_vmNameToIdAndValidateSlot(const com::Utf8Str &aVmName, ULONG a_uSlot, com::Guid &idMachine)
588{
589 if (a_uSlot <= 32)
590 {
591 /* Is it a UUID? */
592 idMachine = aVmName;
593 if (idMachine.isValid() && !idMachine.isZero())
594 return S_OK;
595
596 /* No, find the VM and get it's UUID. */
597 ComObjPtr<Machine> ptrMachine;
598 HRESULT hrc = m->pVirtualBox->i_findMachineByName(aVmName, true /*aSetError*/, &ptrMachine);
599 if (SUCCEEDED(hrc))
600 idMachine = ptrMachine->i_getId();
601 return hrc;
602 }
603 return setError(E_INVALIDARG, tr("NIC slot number (%d) is out of range (0..32)"), a_uSlot);
604}
605
606
607/**
608 * Translates a VM name/id and slot to an individual configuration object.
609 *
610 * @returns COM status code.
611 * @param a_strVmName The VM name or ID.
612 * @param a_uSlot The NIC slot.
613 * @param a_fCreateIfNeeded Whether to create a new entry if not found.
614 * @param a_rPtrConfig Where to return the config object. It's
615 * implicitly referenced, so we don't be returning
616 * with any locks held.
617 *
618 * @note Caller must not be holding any locks!
619 */
620HRESULT DHCPServer::i_vmNameAndSlotToConfig(const com::Utf8Str &a_strVmName, ULONG a_uSlot, bool a_fCreateIfNeeded,
621 ComObjPtr<DHCPIndividualConfig> &a_rPtrConfig)
622{
623 /*
624 * Validate the slot and normalize the name into a UUID.
625 */
626 com::Guid idMachine;
627 HRESULT hrc = i_vmNameToIdAndValidateSlot(a_strVmName, a_uSlot, idMachine);
628 if (SUCCEEDED(hrc))
629 {
630 Utf8Str strKey;
631 int vrc = strKey.printfNoThrow("%RTuuid/%u", idMachine.raw(), a_uSlot);
632 if (RT_SUCCESS(vrc))
633 {
634 /*
635 * Look it up.
636 */
637 {
638 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
639 Data::IndividualConfigIterator it = m->individualConfigs.find(strKey);
640 if (it != m->individualConfigs.end())
641 {
642 a_rPtrConfig = it->second;
643 return S_OK;
644 }
645 }
646 if (a_fCreateIfNeeded)
647 {
648 /*
649 * Create a new slot.
650 */
651 /* Instantiate the object: */
652 hrc = a_rPtrConfig.createObject();
653 if (SUCCEEDED(hrc))
654 hrc = a_rPtrConfig->initWithMachineIdAndSlot(m->pVirtualBox, this, idMachine, a_uSlot,
655 m->uIndividualMACAddressVersion - UINT32_MAX / 4);
656 if (SUCCEEDED(hrc))
657 {
658 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
659
660 /* Check for creation race: */
661 Data::IndividualConfigIterator it = m->individualConfigs.find(strKey);
662 if (it != m->individualConfigs.end())
663 {
664 a_rPtrConfig.setNull();
665 a_rPtrConfig = it->second;
666 return S_OK;
667 }
668
669 /* Add it. */
670 try
671 {
672 m->individualConfigs[strKey] = a_rPtrConfig;
673
674 /* Save settings. */
675 alock.release();
676 return i_doSaveSettings();
677 }
678 catch (std::bad_alloc &)
679 {
680 hrc = E_OUTOFMEMORY;
681 }
682 a_rPtrConfig.setNull();
683 }
684 }
685 else
686 hrc = VBOX_E_OBJECT_NOT_FOUND;
687 }
688 else
689 hrc = E_OUTOFMEMORY;
690 }
691 return hrc;
692}
693
694
695HRESULT DHCPServer::getEventSource(ComPtr<IEventSource> &aEventSource)
696{
697 NOREF(aEventSource);
698 ReturnComNotImplemented();
699}
700
701
702HRESULT DHCPServer::getGlobalConfig(ComPtr<IDHCPGlobalConfig> &aGlobalConfig)
703{
704 /* The global configuration is immutable, so no need to lock anything here. */
705 return m->globalConfig.queryInterfaceTo(aGlobalConfig.asOutParam());
706}
707
708
709HRESULT DHCPServer::getGroupConfigs(std::vector<ComPtr<IDHCPGroupConfig> > &aGroupConfigs)
710{
711 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
712
713 size_t const cGroupConfigs = m->groupConfigs.size();
714 try
715 {
716 aGroupConfigs.resize(cGroupConfigs);
717 }
718 catch (std::bad_alloc &)
719 {
720 return E_OUTOFMEMORY;
721 }
722
723 size_t i = 0;
724 for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end(); ++it, i++)
725 {
726 Assert(i < cGroupConfigs);
727 HRESULT hrc = it->second.queryInterfaceTo(aGroupConfigs[i].asOutParam());
728 if (FAILED(hrc))
729 return hrc;
730 }
731
732 return S_OK;
733}
734
735
736HRESULT DHCPServer::getIndividualConfigs(std::vector<ComPtr<IDHCPIndividualConfig> > &aIndividualConfigs)
737{
738 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
739
740 size_t const cIndividualConfigs = m->individualConfigs.size();
741 try
742 {
743 aIndividualConfigs.resize(cIndividualConfigs);
744 }
745 catch (std::bad_alloc &)
746 {
747 return E_OUTOFMEMORY;
748 }
749
750 size_t i = 0;
751 for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it, i++)
752 {
753 Assert(i < cIndividualConfigs);
754 HRESULT hrc = it->second.queryInterfaceTo(aIndividualConfigs[i].asOutParam());
755 if (FAILED(hrc))
756 return hrc;
757 }
758
759 return S_OK;
760}
761
762
763HRESULT DHCPServer::restart()
764{
765 if (!m->dhcp.isRunning())
766 return setErrorBoth(E_FAIL, VERR_PROCESS_NOT_FOUND, tr("not running"));
767
768 /*
769 * Disabled servers will be brought down, but won't be restarted.
770 * (see DHCPServer::start)
771 */
772 HRESULT hrc = stop();
773 if (SUCCEEDED(hrc))
774 hrc = start(m->trunkName, m->trunkType);
775 return hrc;
776}
777
778
779/**
780 * @throws std::bad_alloc
781 */
782HRESULT DHCPServer::i_writeDhcpdConfig(const char *pszFilename, uint32_t uMACAddressVersion) RT_NOEXCEPT
783{
784 /*
785 * Produce the DHCP server configuration.
786 */
787 xml::Document doc;
788 try
789 {
790 xml::ElementNode *pElmRoot = doc.createRootElement("DHCPServer");
791 pElmRoot->setAttribute("networkName", m->strName);
792 if (m->trunkName.isNotEmpty())
793 pElmRoot->setAttribute("trunkName", m->trunkName);
794 pElmRoot->setAttribute("trunkType", m->trunkType);
795 pElmRoot->setAttribute("IPAddress", m->IPAddress);
796 pElmRoot->setAttribute("lowerIP", m->lowerIP);
797 pElmRoot->setAttribute("upperIP", m->upperIP);
798 pElmRoot->setAttribute("leasesFilename", m->strLeasesFilename);
799 Utf8Str strNetworkMask;
800 HRESULT hrc = m->globalConfig->i_getNetworkMask(strNetworkMask);
801 if (FAILED(hrc))
802 return hrc;
803 pElmRoot->setAttribute("networkMask", strNetworkMask);
804
805 /*
806 * Process global options
807 */
808 m->globalConfig->i_writeDhcpdConfig(pElmRoot->createChild("Options"));
809
810 /*
811 * Groups.
812 */
813 for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end(); ++it)
814 it->second->i_writeDhcpdConfig(pElmRoot->createChild("Group"));
815
816 /*
817 * Individual NIC configurations.
818 */
819 for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it)
820 if (it->second->i_isMACAddressResolved(uMACAddressVersion))
821 it->second->i_writeDhcpdConfig(pElmRoot->createChild("Config"));
822 else
823 LogRelFunc(("Skipping %RTuuid/%u, no MAC address.\n", it->second->i_getMachineId().raw(), it->second->i_getSlot()));
824 }
825 catch (std::bad_alloc &)
826 {
827 return E_OUTOFMEMORY;
828 }
829
830 /*
831 * Write out the document.
832 */
833 try
834 {
835 xml::XmlFileWriter writer(doc);
836 writer.write(pszFilename, false);
837 }
838 catch (...)
839 {
840 return E_FAIL;
841 }
842
843 return S_OK;
844}
845
846
847HRESULT DHCPServer::start(const com::Utf8Str &aTrunkName, const com::Utf8Str &aTrunkType)
848{
849 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
850
851 /* Silently ignore attempts to run disabled servers. */
852 if (!m->enabled)
853 return S_OK;
854
855 /*
856 * Resolve the MAC addresses. This requires us to leave the lock.
857 */
858 uint32_t uMACAddressVersion = m->uIndividualMACAddressVersion;
859 if (m->individualConfigs.size() > 0)
860 {
861 m->uIndividualMACAddressVersion = uMACAddressVersion + 1;
862
863 /* Retain pointers to all the individual configuration objects so we
864 can safely access these after releaseing the lock: */
865 std::vector< ComObjPtr<DHCPIndividualConfig> > vecIndividualConfigs;
866 try
867 {
868 vecIndividualConfigs.resize(m->individualConfigs.size());
869 }
870 catch (std::bad_alloc &)
871 {
872 return E_OUTOFMEMORY;
873 }
874 size_t i = 0;
875 for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it, i++)
876 vecIndividualConfigs[i] = it->second;
877
878 /* Drop the lock and resolve the MAC addresses: */
879 alock.release();
880
881 i = vecIndividualConfigs.size();
882 while (i-- > 0)
883 vecIndividualConfigs[i]->i_resolveMACAddress(uMACAddressVersion);
884
885 /* Reacquire the lock */
886 alock.acquire();
887 if (!m->enabled)
888 return S_OK;
889 }
890
891 /*
892 * Refuse to start a 2nd DHCP server instance for the same network.
893 */
894 if (m->dhcp.isRunning())
895 return setErrorBoth(VBOX_E_OBJECT_IN_USE, VERR_PROCESS_RUNNING,
896 tr("Cannot start DHCP server because it is already running (pid %RTproc)"), m->dhcp.getPid());
897
898 /*
899 * Copy the startup parameters.
900 */
901 m->trunkName = aTrunkName;
902 m->trunkType = aTrunkType;
903 HRESULT hrc = i_calcLeasesConfigAndLogFilenames(m->strName);
904 if (SUCCEEDED(hrc))
905 {
906 /*
907 * Create configuration file path and write out the configuration.
908 */
909 hrc = i_writeDhcpdConfig(m->strConfigFilename.c_str(), uMACAddressVersion);
910 if (SUCCEEDED(hrc))
911 {
912 /*
913 * Setup the arguments and start the DHCP server.
914 */
915 m->dhcp.resetArguments();
916 int vrc = m->dhcp.addArgPair("--comment", m->strName.c_str());
917 if (RT_SUCCESS(vrc))
918 vrc = m->dhcp.addArgPair("--config", m->strConfigFilename.c_str());
919 if (RT_SUCCESS(vrc))
920 vrc = m->dhcp.addArgPair("--log", m->strLogFilename.c_str());
921 /** @todo Add --log-flags, --log-group-settings, and --log-destinations with
922 * associated IDHCPServer attributes. (Not doing it now because that'll
923 * exhaust all reserved attribute slot in 6.0.) */
924 if (RT_SUCCESS(vrc))
925 {
926 /* Start it: */
927 vrc = m->dhcp.start(true /*aKillProcessOnStop*/);
928 if (RT_FAILURE(vrc))
929 hrc = setErrorVrc(vrc, tr("Failed to start DHCP server for '%s': %Rrc"), m->strName.c_str(), vrc);
930 }
931 else
932 hrc = setErrorVrc(vrc, tr("Failed to assemble the command line for DHCP server '%s': %Rrc"),
933 m->strName.c_str(), vrc);
934 }
935 }
936 return hrc;
937}
938
939
940HRESULT DHCPServer::stop(void)
941{
942 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
943
944 int vrc = m->dhcp.stop();
945 if (RT_SUCCESS(vrc))
946 return S_OK;
947 return setErrorVrc(vrc);
948}
949
950
951HRESULT DHCPServer::findLeaseByMAC(const com::Utf8Str &aMac, LONG aType,
952 com::Utf8Str &aAddress, com::Utf8Str &aState, LONG64 *aIssued, LONG64 *aExpire)
953{
954 /* Reset output before we start */
955 *aIssued = 0;
956 *aExpire = 0;
957 aAddress.setNull();
958 aState.setNull();
959
960 /*
961 * Convert and check input.
962 */
963 RTMAC MacAddress;
964 int vrc = RTStrConvertHexBytes(aMac.c_str(), &MacAddress, sizeof(MacAddress), RTSTRCONVERTHEXBYTES_F_SEP_COLON);
965 if (vrc != VINF_SUCCESS)
966 return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid MAC address '%s': %Rrc"), aMac.c_str(), vrc);
967 if (aType != 0)
968 return setError(E_INVALIDARG, tr("flags must be zero (not %#x)"), aType);
969
970 /*
971 * Make sure we've got a lease filename to work with.
972 */
973 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
974 if (m->strLeasesFilename.isEmpty())
975 {
976 HRESULT hrc = i_calcLeasesConfigAndLogFilenames(m->strName);
977 if (FAILED(hrc))
978 return hrc;
979 }
980
981 /*
982 * Try at least twice to read the lease database, more if busy.
983 */
984 uint64_t const nsStart = RTTimeNanoTS();
985 for (uint32_t uReadAttempt = 0; ; uReadAttempt++)
986 {
987 /*
988 * Try read the file.
989 */
990 xml::Document doc;
991 try
992 {
993 xml::XmlFileParser parser;
994 parser.read(m->strLeasesFilename.c_str(), doc);
995 }
996 catch (const xml::EIPRTFailure &e)
997 {
998 vrc = e.rc();
999 LogThisFunc(("caught xml::EIPRTFailure: rc=%Rrc (attempt %u, msg=%s)\n", vrc, uReadAttempt, e.what()));
1000 if ( ( vrc == VERR_FILE_NOT_FOUND
1001 || vrc == VERR_OPEN_FAILED
1002 || vrc == VERR_ACCESS_DENIED
1003 || vrc == VERR_SHARING_VIOLATION
1004 || vrc == VERR_READ_ERROR /*?*/)
1005 && ( uReadAttempt == 0
1006 || ( uReadAttempt < 64
1007 && RTTimeNanoTS() - nsStart < RT_NS_1SEC / 4)) )
1008 {
1009 alock.release();
1010
1011 if (uReadAttempt > 0)
1012 RTThreadYield();
1013 RTThreadSleep(8/*ms*/);
1014
1015 alock.acquire();
1016 LogThisFunc(("Retrying...\n"));
1017 continue;
1018 }
1019 return setErrorBoth(VBOX_E_FILE_ERROR, vrc, "Reading '%s' failed: %Rrc - %s",
1020 m->strLeasesFilename.c_str(), vrc, e.what());
1021 }
1022 catch (const RTCError &e)
1023 {
1024 if (e.what())
1025 return setError(VBOX_E_FILE_ERROR, "Reading '%s' failed: %s", m->strLeasesFilename.c_str(), e.what());
1026 return setError(VBOX_E_FILE_ERROR, "Reading '%s' failed: RTCError", m->strLeasesFilename.c_str());
1027 }
1028 catch (std::bad_alloc &)
1029 {
1030 return E_OUTOFMEMORY;
1031 }
1032 catch (...)
1033 {
1034 AssertFailed();
1035 return setError(VBOX_E_FILE_ERROR, tr("Reading '%s' failed: Unexpected exception"), m->strLeasesFilename.c_str());
1036 }
1037
1038 /*
1039 * Look for that mac address.
1040 */
1041 xml::ElementNode *pElmRoot = doc.getRootElement();
1042 if (pElmRoot && pElmRoot->nameEquals("Leases"))
1043 {
1044 xml::NodesLoop it(*pElmRoot);
1045 const xml::ElementNode *pElmLease;
1046 while ((pElmLease = it.forAllNodes()) != NULL)
1047 if (pElmLease->nameEquals("Lease"))
1048 {
1049 const char *pszCurMacAddress = pElmLease->findAttributeValue("mac");
1050 RTMAC CurMacAddress;
1051 if ( pszCurMacAddress
1052 && RT_SUCCESS(RTNetStrToMacAddr(pszCurMacAddress, &CurMacAddress))
1053 && memcmp(&CurMacAddress, &MacAddress, sizeof(MacAddress)) == 0)
1054 {
1055 /*
1056 * Found it!
1057 */
1058 xml::ElementNode const *pElmTime = pElmLease->findChildElement("Time");
1059 int64_t secIssued = 0;
1060 uint32_t cSecsToLive = 0;
1061 if (pElmTime)
1062 {
1063 pElmTime->getAttributeValue("issued", &secIssued);
1064 pElmTime->getAttributeValue("expiration", &cSecsToLive);
1065 *aIssued = secIssued;
1066 *aExpire = secIssued + cSecsToLive;
1067 }
1068 try
1069 {
1070 aAddress = pElmLease->findChildElementAttributeValue("Address", "value");
1071 aState = pElmLease->findAttributeValue("state");
1072 }
1073 catch (std::bad_alloc &)
1074 {
1075 return E_OUTOFMEMORY;
1076 }
1077
1078 /* Check if the lease has expired in the mean time. */
1079 HRESULT hrc = S_OK;
1080 RTTIMESPEC Now;
1081 if ( (aState.equals("acked") || aState.equals("offered") || aState.isEmpty())
1082 && secIssued + cSecsToLive < RTTimeSpecGetSeconds(RTTimeNow(&Now)))
1083 hrc = RT_SUCCESS(aState.assignNoThrow("expired")) ? S_OK : E_OUTOFMEMORY;
1084 return hrc;
1085 }
1086 }
1087 }
1088 break;
1089 }
1090
1091 return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a lease for %RTmac"), &MacAddress);
1092}
1093
1094
1095HRESULT DHCPServer::getConfig(DHCPConfigScope_T aScope, const com::Utf8Str &aName, ULONG aSlot, BOOL aMayAdd,
1096 ComPtr<IDHCPConfig> &aConfig)
1097{
1098 if (aSlot != 0 && aScope != DHCPConfigScope_MachineNIC)
1099 return setError(E_INVALIDARG, tr("The 'slot' argument must be zero for all but the MachineNIC scope!"));
1100
1101 switch (aScope)
1102 {
1103 case DHCPConfigScope_Global:
1104 if (aName.isNotEmpty())
1105 return setError(E_INVALIDARG, tr("The name must be empty or NULL for the Global scope!"));
1106
1107 /* No locking required here. */
1108 return m->globalConfig.queryInterfaceTo(aConfig.asOutParam());
1109
1110 case DHCPConfigScope_Group:
1111 {
1112 if (aName.isEmpty())
1113 return setError(E_INVALIDARG, tr("A group must have a name!"));
1114 if (aName.length() > _1K)
1115 return setError(E_INVALIDARG, tr("Name too long! %zu bytes"), aName.length());
1116
1117 /* Look up the group: */
1118 {
1119 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1120 Data::GroupConfigIterator it = m->groupConfigs.find(aName);
1121 if (it != m->groupConfigs.end())
1122 return it->second.queryInterfaceTo(aConfig.asOutParam());
1123 }
1124 /* Create a new group if we can. */
1125 if (!aMayAdd)
1126 return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Found no configuration for group %s"), aName.c_str());
1127 ComObjPtr<DHCPGroupConfig> ptrGroupConfig;
1128 HRESULT hrc = ptrGroupConfig.createObject();
1129 if (SUCCEEDED(hrc))
1130 hrc = ptrGroupConfig->initWithDefaults(m->pVirtualBox, this, aName);
1131 if (SUCCEEDED(hrc))
1132 {
1133 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1134
1135 /* Check for insertion race: */
1136 Data::GroupConfigIterator it = m->groupConfigs.find(aName);
1137 if (it != m->groupConfigs.end())
1138 return it->second.queryInterfaceTo(aConfig.asOutParam()); /* creation race*/
1139
1140 /* Try insert it: */
1141 try
1142 {
1143 m->groupConfigs[aName] = ptrGroupConfig;
1144 }
1145 catch (std::bad_alloc &)
1146 {
1147 return E_OUTOFMEMORY;
1148 }
1149 return ptrGroupConfig.queryInterfaceTo(aConfig.asOutParam());
1150 }
1151 return hrc;
1152 }
1153
1154 case DHCPConfigScope_MachineNIC:
1155 {
1156 ComObjPtr<DHCPIndividualConfig> ptrIndividualConfig;
1157 HRESULT hrc = i_vmNameAndSlotToConfig(aName, aSlot, aMayAdd != FALSE, ptrIndividualConfig);
1158 if (SUCCEEDED(hrc))
1159 hrc = ptrIndividualConfig.queryInterfaceTo(aConfig.asOutParam());
1160 return hrc;
1161 }
1162
1163 case DHCPConfigScope_MAC:
1164 {
1165 /* Check and Normalize the MAC address into a key: */
1166 RTMAC MACAddress;
1167 int vrc = RTNetStrToMacAddr(aName.c_str(), &MACAddress);
1168 if (RT_SUCCESS(vrc))
1169 {
1170 Utf8Str strKey;
1171 vrc = strKey.printfNoThrow("%RTmac", &MACAddress);
1172 if (RT_SUCCESS(vrc))
1173 {
1174 /* Look up the MAC address: */
1175 {
1176 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1177 Data::IndividualConfigIterator it = m->individualConfigs.find(strKey);
1178 if (it != m->individualConfigs.end())
1179 return it->second.queryInterfaceTo(aConfig.asOutParam());
1180 }
1181 if (aMayAdd)
1182 {
1183 /* Create a new individiual configuration: */
1184 ComObjPtr<DHCPIndividualConfig> ptrIndividualConfig;
1185 HRESULT hrc = ptrIndividualConfig.createObject();
1186 if (SUCCEEDED(hrc))
1187 hrc = ptrIndividualConfig->initWithMACAddress(m->pVirtualBox, this, &MACAddress);
1188 if (SUCCEEDED(hrc))
1189 {
1190 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1191
1192 /* Check for insertion race: */
1193 Data::IndividualConfigIterator it = m->individualConfigs.find(strKey);
1194 if (it != m->individualConfigs.end())
1195 return it->second.queryInterfaceTo(aConfig.asOutParam()); /* creation race*/
1196
1197 /* Try insert it: */
1198 try
1199 {
1200 m->individualConfigs[strKey] = ptrIndividualConfig;
1201 }
1202 catch (std::bad_alloc &)
1203 {
1204 return E_OUTOFMEMORY;
1205 }
1206 return ptrIndividualConfig.queryInterfaceTo(aConfig.asOutParam());
1207 }
1208 }
1209 else
1210 return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Found no configuration for MAC address %s"), strKey.c_str());
1211 }
1212 return E_OUTOFMEMORY;
1213 }
1214 return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid MAC address: %s"), aName.c_str());
1215 }
1216
1217 default:
1218 return E_FAIL;
1219 }
1220}
1221
1222
1223/**
1224 * Calculates and updates the value of strLeasesFilename given @a aNetwork.
1225 */
1226HRESULT DHCPServer::i_calcLeasesConfigAndLogFilenames(const com::Utf8Str &aNetwork) RT_NOEXCEPT
1227{
1228 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1229
1230 /* The lease file must be the same as we used the last time, so careful when changing this code. */
1231 int vrc = m->strLeasesFilename.assignNoThrow(m->pVirtualBox->i_homeDir());
1232 if (RT_SUCCESS(vrc))
1233 vrc = RTPathAppendCxx(m->strLeasesFilename, aNetwork);
1234 if (RT_SUCCESS(vrc))
1235 {
1236 RTPathPurgeFilename(RTPathFilename(m->strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST);
1237
1238 /* The configuration file: */
1239 vrc = m->strConfigFilename.assignNoThrow(m->strLeasesFilename);
1240 if (RT_SUCCESS(vrc))
1241 vrc = m->strConfigFilename.appendNoThrow("-Dhcpd.config");
1242
1243
1244 /* The log file: */
1245 if (RT_SUCCESS(vrc))
1246 {
1247 vrc = m->strLogFilename.assignNoThrow(m->strLeasesFilename);
1248 if (RT_SUCCESS(vrc))
1249 vrc = m->strLogFilename.appendNoThrow("-Dhcpd.log");
1250
1251 /* Finally, complete the leases file: */
1252 if (RT_SUCCESS(vrc))
1253 {
1254 vrc = m->strLeasesFilename.appendNoThrow("-Dhcpd.leases");
1255 if (RT_SUCCESS(vrc))
1256 {
1257 RTPathPurgeFilename(RTPathFilename(m->strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST);
1258 m->strLeasesFilename.jolt();
1259 return S_OK;
1260 }
1261 }
1262 }
1263 }
1264 return setErrorBoth(E_FAIL, vrc, tr("Failed to construct leases, config and log filenames: %Rrc"), vrc);
1265}
1266
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette