VirtualBox

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

Last change on this file since 98110 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

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