VirtualBox

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

Last change on this file since 79644 was 79644, checked in by vboxsync, 6 years ago

Main/NetworkServiceRunner: eliminated std::string and replaced the std::map option/value map with a C array that can be passed directly to RTProcCreate. Changed the term 'options' to 'arguments' to avoid DHCP option confusion. bugref:9288

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.8 KB
Line 
1/* $Id: DHCPServerImpl.cpp 79644 2019-07-09 14:01:06Z vboxsync $ */
2/** @file
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. 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 "NetworkServiceRunner.h"
24#include "DHCPServerImpl.h"
25#include "AutoCaller.h"
26#include "LoggingNew.h"
27
28#include <iprt/asm.h>
29#include <iprt/err.h>
30#include <iprt/file.h>
31#include <iprt/net.h>
32#include <iprt/path.h>
33#include <iprt/cpp/path.h>
34#include <iprt/cpp/utils.h>
35#include <iprt/cpp/xml.h>
36
37#include <VBox/com/array.h>
38#include <VBox/settings.h>
39
40#include "VirtualBoxImpl.h"
41
42
43/*********************************************************************************************************************************
44* Defined Constants And Macros *
45*********************************************************************************************************************************/
46#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
47# define DHCP_EXECUTABLE_NAME "VBoxNetDHCP.exe"
48#else
49# define DHCP_EXECUTABLE_NAME "VBoxNetDHCP"
50#endif
51
52
53/**
54 * DHCP server specialization of NetworkServiceRunner.
55 *
56 * Just defines the executable name and adds option constants.
57 */
58class DHCPServerRunner : public NetworkServiceRunner
59{
60public:
61 DHCPServerRunner() : NetworkServiceRunner(DHCP_EXECUTABLE_NAME)
62 {}
63 virtual ~DHCPServerRunner()
64 {}
65
66 static const char * const kDsrKeyGateway;
67 static const char * const kDsrKeyLowerIp;
68 static const char * const kDsrKeyUpperIp;
69 static const char * const kDsrKeyConfig;
70 static const char * const kDsrKeyComment;
71};
72
73/*static*/ const char * const DHCPServerRunner::kDsrKeyGateway = "--gateway";
74/*static*/ const char * const DHCPServerRunner::kDsrKeyLowerIp = "--lower-ip";
75/*static*/ const char * const DHCPServerRunner::kDsrKeyUpperIp = "--upper-ip";
76/*static*/ const char * const DHCPServerRunner::kDsrKeyConfig = "--config";
77/*static*/ const char * const DHCPServerRunner::kDsrKeyComment = "--comment";
78
79
80/**
81 * Hidden private data of the DHCPServer class.
82 */
83struct DHCPServer::Data
84{
85 Data()
86 : enabled(FALSE)
87 , router(false)
88 {
89 szTempConfigFileName[0] = '\0';
90 }
91
92 Utf8Str IPAddress;
93 Utf8Str lowerIP;
94 Utf8Str upperIP;
95
96 BOOL enabled;
97 bool router;
98 DHCPServerRunner dhcp;
99
100 settings::DhcpOptionMap GlobalDhcpOptions;
101 settings::VmSlot2OptionsMap VmSlot2Options;
102
103 char szTempConfigFileName[RTPATH_MAX];
104 com::Utf8Str strLeasesFilename;
105 com::Utf8Str networkName;
106 com::Utf8Str trunkName;
107 com::Utf8Str trunkType;
108};
109
110
111// constructor / destructor
112/////////////////////////////////////////////////////////////////////////////
113
114
115DHCPServer::DHCPServer()
116 : m(NULL)
117 , mVirtualBox(NULL)
118{
119 m = new DHCPServer::Data();
120}
121
122
123DHCPServer::~DHCPServer()
124{
125 if (m)
126 {
127 delete m;
128 m = NULL;
129 }
130}
131
132
133HRESULT DHCPServer::FinalConstruct()
134{
135 return BaseFinalConstruct();
136}
137
138
139void DHCPServer::FinalRelease()
140{
141 uninit ();
142
143 BaseFinalRelease();
144}
145
146
147void DHCPServer::uninit()
148{
149 /* Enclose the state transition Ready->InUninit->NotReady */
150 AutoUninitSpan autoUninitSpan(this);
151 if (autoUninitSpan.uninitDone())
152 return;
153
154 if (m->dhcp.isRunning())
155 stop();
156
157 unconst(mVirtualBox) = NULL;
158}
159
160
161HRESULT DHCPServer::init(VirtualBox *aVirtualBox, const Utf8Str &aName)
162{
163 AssertReturn(!aName.isEmpty(), E_INVALIDARG);
164
165 AutoInitSpan autoInitSpan(this);
166 AssertReturn(autoInitSpan.isOk(), E_FAIL);
167
168 /* share VirtualBox weakly (parent remains NULL so far) */
169 unconst(mVirtualBox) = aVirtualBox;
170
171 unconst(mName) = aName;
172 m->IPAddress = "0.0.0.0";
173 m->GlobalDhcpOptions[DhcpOpt_SubnetMask] = settings::DhcpOptValue("0.0.0.0");
174 m->enabled = FALSE;
175
176 m->lowerIP = "0.0.0.0";
177 m->upperIP = "0.0.0.0";
178
179 /* Confirm a successful initialization */
180 autoInitSpan.setSucceeded();
181
182 return S_OK;
183}
184
185
186HRESULT DHCPServer::init(VirtualBox *aVirtualBox, const settings::DHCPServer &data)
187{
188 /* Enclose the state transition NotReady->InInit->Ready */
189 AutoInitSpan autoInitSpan(this);
190 AssertReturn(autoInitSpan.isOk(), E_FAIL);
191
192 /* share VirtualBox weakly (parent remains NULL so far) */
193 unconst(mVirtualBox) = aVirtualBox;
194
195 unconst(mName) = data.strNetworkName;
196 m->IPAddress = data.strIPAddress;
197 m->enabled = data.fEnabled;
198 m->lowerIP = data.strIPLower;
199 m->upperIP = data.strIPUpper;
200
201 m->GlobalDhcpOptions.clear();
202 m->GlobalDhcpOptions.insert(data.GlobalDhcpOptions.begin(), data.GlobalDhcpOptions.end());
203
204 m->VmSlot2Options.clear();
205 m->VmSlot2Options.insert(data.VmSlot2OptionsM.begin(), data.VmSlot2OptionsM.end());
206
207 autoInitSpan.setSucceeded();
208
209 return S_OK;
210}
211
212
213HRESULT DHCPServer::i_saveSettings(settings::DHCPServer &data)
214{
215 AutoCaller autoCaller(this);
216 if (FAILED(autoCaller.rc())) return autoCaller.rc();
217
218 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
219
220 data.strNetworkName = mName;
221 data.strIPAddress = m->IPAddress;
222
223 data.fEnabled = !!m->enabled;
224 data.strIPLower = m->lowerIP;
225 data.strIPUpper = m->upperIP;
226
227 data.GlobalDhcpOptions.clear();
228 data.GlobalDhcpOptions.insert(m->GlobalDhcpOptions.begin(),
229 m->GlobalDhcpOptions.end());
230
231 data.VmSlot2OptionsM.clear();
232 data.VmSlot2OptionsM.insert(m->VmSlot2Options.begin(),
233 m->VmSlot2Options.end());
234
235 return S_OK;
236}
237
238
239HRESULT DHCPServer::getNetworkName(com::Utf8Str &aName)
240{
241 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
242
243 aName = mName;
244 return S_OK;
245}
246
247
248HRESULT DHCPServer::getEnabled(BOOL *aEnabled)
249{
250 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
251
252 *aEnabled = m->enabled;
253 return S_OK;
254}
255
256
257HRESULT DHCPServer::setEnabled(BOOL aEnabled)
258{
259 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
260 m->enabled = aEnabled;
261
262 // save the global settings; for that we should hold only the VirtualBox lock
263 alock.release();
264 AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
265 HRESULT rc = mVirtualBox->i_saveSettings();
266
267 return rc;
268}
269
270
271HRESULT DHCPServer::getIPAddress(com::Utf8Str &aIPAddress)
272{
273 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
274
275 aIPAddress = Utf8Str(m->IPAddress);
276 return S_OK;
277}
278
279
280HRESULT DHCPServer::getNetworkMask(com::Utf8Str &aNetworkMask)
281{
282 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
283
284 aNetworkMask = m->GlobalDhcpOptions[DhcpOpt_SubnetMask].text;
285 return S_OK;
286}
287
288
289HRESULT DHCPServer::getLowerIP(com::Utf8Str &aIPAddress)
290{
291 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
292
293 aIPAddress = Utf8Str(m->lowerIP);
294 return S_OK;
295}
296
297
298HRESULT DHCPServer::getUpperIP(com::Utf8Str &aIPAddress)
299{
300 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
301
302 aIPAddress = Utf8Str(m->upperIP);
303 return S_OK;
304}
305
306
307HRESULT DHCPServer::setConfiguration(const com::Utf8Str &aIPAddress,
308 const com::Utf8Str &aNetworkMask,
309 const com::Utf8Str &aLowerIP,
310 const com::Utf8Str &aUpperIP)
311{
312 RTNETADDRIPV4 IPAddress, NetworkMask, LowerIP, UpperIP;
313
314 int vrc = RTNetStrToIPv4Addr(aIPAddress.c_str(), &IPAddress);
315 if (RT_FAILURE(vrc))
316 return mVirtualBox->setErrorBoth(E_INVALIDARG, vrc, "Invalid server address");
317
318 vrc = RTNetStrToIPv4Addr(aNetworkMask.c_str(), &NetworkMask);
319 if (RT_FAILURE(vrc))
320 return mVirtualBox->setErrorBoth(E_INVALIDARG, vrc, "Invalid netmask");
321
322 vrc = RTNetStrToIPv4Addr(aLowerIP.c_str(), &LowerIP);
323 if (RT_FAILURE(vrc))
324 return mVirtualBox->setErrorBoth(E_INVALIDARG, vrc, "Invalid range lower address");
325
326 vrc = RTNetStrToIPv4Addr(aUpperIP.c_str(), &UpperIP);
327 if (RT_FAILURE(vrc))
328 return mVirtualBox->setErrorBoth(E_INVALIDARG, vrc, "Invalid range upper address");
329
330 /*
331 * Insist on continuous mask. May be also accept prefix length
332 * here or address/prefix for aIPAddress?
333 */
334 vrc = RTNetMaskToPrefixIPv4(&NetworkMask, NULL);
335 if (RT_FAILURE(vrc))
336 return mVirtualBox->setErrorBoth(E_INVALIDARG, vrc, "Invalid netmask");
337
338 /* It's more convenient to convert to host order once */
339 IPAddress.u = RT_N2H_U32(IPAddress.u);
340 NetworkMask.u = RT_N2H_U32(NetworkMask.u);
341 LowerIP.u = RT_N2H_U32(LowerIP.u);
342 UpperIP.u = RT_N2H_U32(UpperIP.u);
343
344 /*
345 * Addresses must be unicast and from the same network
346 */
347 if ( (IPAddress.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
348 || (IPAddress.u & ~NetworkMask.u) == 0
349 || ((IPAddress.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
350 return mVirtualBox->setError(E_INVALIDARG, "Invalid server address");
351
352 if ( (LowerIP.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
353 || (LowerIP.u & NetworkMask.u) != (IPAddress.u &NetworkMask.u)
354 || (LowerIP.u & ~NetworkMask.u) == 0
355 || ((LowerIP.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
356 return mVirtualBox->setError(E_INVALIDARG, "Invalid range lower address");
357
358 if ( (UpperIP.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
359 || (UpperIP.u & NetworkMask.u) != (IPAddress.u &NetworkMask.u)
360 || (UpperIP.u & ~NetworkMask.u) == 0
361 || ((UpperIP.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
362 return mVirtualBox->setError(E_INVALIDARG, "Invalid range upper address");
363
364 /* The range should be valid ... */
365 if (LowerIP.u > UpperIP.u)
366 return mVirtualBox->setError(E_INVALIDARG, "Invalid range bounds");
367
368 /* ... and shouldn't contain the server's address */
369 if (LowerIP.u <= IPAddress.u && IPAddress.u <= UpperIP.u)
370 return mVirtualBox->setError(E_INVALIDARG, "Server address within range bounds");
371
372 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
373 m->IPAddress = aIPAddress;
374 m->GlobalDhcpOptions[DhcpOpt_SubnetMask] = aNetworkMask;
375
376 m->lowerIP = aLowerIP;
377 m->upperIP = aUpperIP;
378
379 // save the global settings; for that we should hold only the VirtualBox lock
380 alock.release();
381 AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
382 return mVirtualBox->i_saveSettings();
383}
384
385
386HRESULT DHCPServer::encodeOption(com::Utf8Str &aEncoded,
387 uint32_t aOptCode,
388 const settings::DhcpOptValue &aOptValue)
389{
390 switch (aOptValue.encoding)
391 {
392 case DhcpOptEncoding_Legacy:
393 {
394 /*
395 * This is original encoding which assumed that for each
396 * option we know its format and so we know how option
397 * "value" text is to be interpreted.
398 *
399 * "2:10800" # integer 32
400 * "6:1.2.3.4 8.8.8.8" # array of ip-address
401 */
402 aEncoded = Utf8StrFmt("%d:%s", aOptCode, aOptValue.text.c_str());
403 break;
404 }
405
406 case DhcpOptEncoding_Hex:
407 {
408 /*
409 * This is a bypass for any option - preformatted value as
410 * hex string with no semantic involved in formatting the
411 * value for the DHCP reply.
412 *
413 * 234=68:65:6c:6c:6f:2c:20:77:6f:72:6c:64
414 */
415 aEncoded = Utf8StrFmt("%d=%s", aOptCode, aOptValue.text.c_str());
416 break;
417 }
418
419 default:
420 {
421 /*
422 * Try to be forward compatible.
423 *
424 * "254@42=i hope you know what this means"
425 */
426 aEncoded = Utf8StrFmt("%d@%d=%s", aOptCode, (int)aOptValue.encoding,
427 aOptValue.text.c_str());
428 break;
429 }
430 }
431
432 return S_OK;
433}
434
435
436int DHCPServer::addOption(settings::DhcpOptionMap &aMap,
437 DhcpOpt_T aOption, const com::Utf8Str &aValue)
438{
439 settings::DhcpOptValue OptValue;
440
441 if (aOption != 0)
442 {
443 OptValue = settings::DhcpOptValue(aValue, DhcpOptEncoding_Legacy);
444 }
445 /*
446 * This is a kludge to sneak in option encoding information
447 * through existing API. We use option 0 and supply the real
448 * option/value in the same format that encodeOption() above
449 * produces for getter methods.
450 */
451 else
452 {
453 uint8_t u8Code;
454 char *pszNext;
455 int vrc = RTStrToUInt8Ex(aValue.c_str(), &pszNext, 10, &u8Code);
456 if (!RT_SUCCESS(vrc))
457 return VERR_PARSE_ERROR;
458
459 uint32_t u32Enc;
460 switch (*pszNext)
461 {
462 case ':': /* support legacy format too */
463 {
464 u32Enc = DhcpOptEncoding_Legacy;
465 break;
466 }
467
468 case '=':
469 {
470 u32Enc = DhcpOptEncoding_Hex;
471 break;
472 }
473
474 case '@':
475 {
476 vrc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &u32Enc);
477 if (!RT_SUCCESS(vrc))
478 return VERR_PARSE_ERROR;
479 if (*pszNext != '=')
480 return VERR_PARSE_ERROR;
481 break;
482 }
483
484 default:
485 return VERR_PARSE_ERROR;
486 }
487
488 aOption = (DhcpOpt_T)u8Code;
489 OptValue = settings::DhcpOptValue(pszNext + 1, (DhcpOptEncoding_T)u32Enc);
490 }
491
492 aMap[aOption] = OptValue;
493 return VINF_SUCCESS;
494}
495
496
497HRESULT DHCPServer::addGlobalOption(DhcpOpt_T aOption, const com::Utf8Str &aValue)
498{
499 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
500
501 int rc = addOption(m->GlobalDhcpOptions, aOption, aValue);
502 if (!RT_SUCCESS(rc))
503 return E_INVALIDARG;
504
505 /* Indirect way to understand that we're on NAT network */
506 if (aOption == DhcpOpt_Router)
507 {
508 m->router = true;
509 }
510
511 alock.release();
512
513 AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
514 return mVirtualBox->i_saveSettings();
515}
516
517
518HRESULT DHCPServer::removeGlobalOption(DhcpOpt_T aOption)
519{
520 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
521
522 settings::DhcpOptionMap::size_type cErased = m->GlobalDhcpOptions.erase(aOption);
523 if (!cErased)
524 return E_INVALIDARG;
525
526 alock.release();
527
528 AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
529 return mVirtualBox->i_saveSettings();
530}
531
532
533HRESULT DHCPServer::removeGlobalOptions()
534{
535 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
536 m->GlobalDhcpOptions.clear();
537
538 alock.release();
539
540 AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
541 return mVirtualBox->i_saveSettings();
542}
543
544
545HRESULT DHCPServer::getGlobalOptions(std::vector<com::Utf8Str> &aValues)
546{
547 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
548 aValues.resize(m->GlobalDhcpOptions.size());
549 settings::DhcpOptionMap::const_iterator it;
550 size_t i = 0;
551 for (it = m->GlobalDhcpOptions.begin(); it != m->GlobalDhcpOptions.end(); ++it, ++i)
552 {
553 uint32_t OptCode = (*it).first;
554 const settings::DhcpOptValue &OptValue = (*it).second;
555
556 encodeOption(aValues[i], OptCode, OptValue);
557 }
558
559 return S_OK;
560}
561
562HRESULT DHCPServer::getVmConfigs(std::vector<com::Utf8Str> &aValues)
563{
564 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
565 aValues.resize(m->VmSlot2Options.size());
566 settings::VmSlot2OptionsMap::const_iterator it;
567 size_t i = 0;
568 for (it = m->VmSlot2Options.begin(); it != m->VmSlot2Options.end(); ++it, ++i)
569 {
570 aValues[i] = Utf8StrFmt("[%s]:%d", it->first.VmName.c_str(), it->first.Slot);
571 }
572
573 return S_OK;
574}
575
576
577HRESULT DHCPServer::addVmSlotOption(const com::Utf8Str &aVmName,
578 LONG aSlot,
579 DhcpOpt_T aOption,
580 const com::Utf8Str &aValue)
581{
582 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
583
584 settings::DhcpOptionMap &map = m->VmSlot2Options[settings::VmNameSlotKey(aVmName, aSlot)];
585 int rc = addOption(map, aOption, aValue);
586 if (!RT_SUCCESS(rc))
587 return E_INVALIDARG;
588
589 alock.release();
590
591 AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
592 return mVirtualBox->i_saveSettings();
593}
594
595
596HRESULT DHCPServer::removeVmSlotOption(const com::Utf8Str &aVmName, LONG aSlot, DhcpOpt_T aOption)
597{
598 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
599 settings::DhcpOptionMap &map = i_findOptMapByVmNameSlot(aVmName, aSlot);
600 settings::DhcpOptionMap::size_type cErased = map.erase(aOption);
601 if (!cErased)
602 return E_INVALIDARG;
603
604 alock.release();
605
606 AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
607 return mVirtualBox->i_saveSettings();
608}
609
610
611HRESULT DHCPServer::removeVmSlotOptions(const com::Utf8Str &aVmName, LONG aSlot)
612{
613 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
614 settings::DhcpOptionMap &map = i_findOptMapByVmNameSlot(aVmName, aSlot);
615 map.clear();
616
617 alock.release();
618
619 AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
620 return mVirtualBox->i_saveSettings();
621}
622
623/**
624 * this is mapping (vm, slot)
625 */
626HRESULT DHCPServer::getVmSlotOptions(const com::Utf8Str &aVmName,
627 LONG aSlot,
628 std::vector<com::Utf8Str> &aValues)
629{
630
631 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
632 settings::DhcpOptionMap &map = i_findOptMapByVmNameSlot(aVmName, aSlot);
633 aValues.resize(map.size());
634 size_t i = 0;
635 settings::DhcpOptionMap::const_iterator it;
636 for (it = map.begin(); it != map.end(); ++it, ++i)
637 {
638 uint32_t OptCode = (*it).first;
639 const settings::DhcpOptValue &OptValue = (*it).second;
640
641 encodeOption(aValues[i], OptCode, OptValue);
642 }
643
644 return S_OK;
645}
646
647
648HRESULT DHCPServer::getMacOptions(const com::Utf8Str &aMAC, std::vector<com::Utf8Str> &aOption)
649{
650 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
651 HRESULT hrc = S_OK;
652 ComPtr<IMachine> machine;
653 ComPtr<INetworkAdapter> nic;
654 settings::VmSlot2OptionsIterator it;
655 for(it = m->VmSlot2Options.begin(); it != m->VmSlot2Options.end(); ++it)
656 {
657 alock.release();
658 hrc = mVirtualBox->FindMachine(Bstr(it->first.VmName).raw(), machine.asOutParam());
659 alock.acquire();
660
661 if (FAILED(hrc))
662 continue;
663
664 alock.release();
665 hrc = machine->GetNetworkAdapter(it->first.Slot, nic.asOutParam());
666 alock.acquire();
667
668 if (FAILED(hrc))
669 continue;
670
671 com::Bstr mac;
672
673 alock.release();
674 hrc = nic->COMGETTER(MACAddress)(mac.asOutParam());
675 alock.acquire();
676
677 if (FAILED(hrc)) /* no MAC address ??? */
678 break;
679 if (!RTStrICmp(com::Utf8Str(mac).c_str(), aMAC.c_str()))
680 return getVmSlotOptions(it->first.VmName,
681 it->first.Slot,
682 aOption);
683 } /* end of for */
684
685 return hrc;
686}
687
688HRESULT DHCPServer::getEventSource(ComPtr<IEventSource> &aEventSource)
689{
690 NOREF(aEventSource);
691 ReturnComNotImplemented();
692}
693
694
695DECLINLINE(void) addOptionChild(xml::ElementNode *pParent, uint32_t OptCode, const settings::DhcpOptValue &OptValue)
696{
697 xml::ElementNode *pOption = pParent->createChild("Option");
698 pOption->setAttribute("name", OptCode);
699 pOption->setAttribute("encoding", OptValue.encoding);
700 pOption->setAttribute("value", OptValue.text.c_str());
701}
702
703
704HRESULT DHCPServer::restart()
705{
706 if (!m->dhcp.isRunning())
707 return setErrorBoth(E_FAIL, VERR_PROCESS_NOT_FOUND, tr("not running"));
708
709 /*
710 * Disabled servers will be brought down, but won't be restarted.
711 * (see DHCPServer::start)
712 */
713 HRESULT hrc = stop();
714 if (SUCCEEDED(hrc))
715 hrc = start(m->networkName, m->trunkName, m->trunkType);
716 return hrc;
717}
718
719
720HRESULT DHCPServer::start(const com::Utf8Str &aNetworkName,
721 const com::Utf8Str &aTrunkName,
722 const com::Utf8Str &aTrunkType)
723{
724 /* Silently ignore attempts to run disabled servers. */
725 if (!m->enabled)
726 return S_OK;
727
728 /**
729 * @todo: the existing code cannot handle concurrent attempts to start DHCP server.
730 * Note that technically it may receive different parameters from different callers.
731 */
732 m->networkName = aNetworkName;
733 m->trunkName = aTrunkName;
734 m->trunkType = aTrunkType;
735 HRESULT hrc = i_calcLeasesFilename(aNetworkName);
736 if (FAILED(hrc))
737 return hrc;
738
739 m->dhcp.resetArguments();
740
741#ifdef VBOX_WITH_DHCPD
742
743 /*
744 * Create configuration file path.
745 */
746 /** @todo put this next to the leases file. */
747 int rc = RTPathTemp(m->szTempConfigFileName, sizeof(m->szTempConfigFileName));
748 if (RT_SUCCESS(rc))
749 rc = RTPathAppend(m->szTempConfigFileName, sizeof(m->szTempConfigFileName), "dhcp-config-XXXXX.xml");
750 if (RT_SUCCESS(rc))
751 rc = RTFileCreateTemp(m->szTempConfigFileName, 0600);
752 if (RT_FAILURE(rc))
753 {
754 m->szTempConfigFileName[0] = '\0';
755 return E_FAIL;
756 }
757
758 /*
759 * Produce the DHCP server configuration.
760 */
761 xml::Document doc;
762 xml::ElementNode *pElmRoot = doc.createRootElement("DHCPServer");
763 pElmRoot->setAttribute("networkName", m->networkName);
764 if (m->trunkName.isNotEmpty())
765 pElmRoot->setAttribute("trunkName", m->trunkName);
766 pElmRoot->setAttribute("trunkType", m->trunkType);
767 pElmRoot->setAttribute("IPAddress", m->IPAddress);
768 pElmRoot->setAttribute("networkMask", m->GlobalDhcpOptions[DhcpOpt_SubnetMask].text);
769 pElmRoot->setAttribute("lowerIP", m->lowerIP);
770 pElmRoot->setAttribute("upperIP", m->upperIP);
771 pElmRoot->setAttribute("leasesFilename", m->strLeasesFilename);
772
773 /* Process global options */
774 xml::ElementNode *pOptions = pElmRoot->createChild("Options");
775 for (settings::DhcpOptionMap::const_iterator it = m->GlobalDhcpOptions.begin(); it != m->GlobalDhcpOptions.end(); ++it)
776 addOptionChild(pOptions, (*it).first, (*it).second);
777
778 /* Process network-adapter-specific options */
779 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
780 hrc = S_OK;
781 ComPtr<IMachine> machine;
782 ComPtr<INetworkAdapter> nic;
783 settings::VmSlot2OptionsIterator it;
784 for (it = m->VmSlot2Options.begin(); it != m->VmSlot2Options.end(); ++it)
785 {
786 alock.release();
787 hrc = mVirtualBox->FindMachine(Bstr(it->first.VmName).raw(), machine.asOutParam());
788 alock.acquire();
789
790 if (FAILED(hrc))
791 continue;
792
793 alock.release();
794 hrc = machine->GetNetworkAdapter(it->first.Slot, nic.asOutParam());
795 alock.acquire();
796
797 if (FAILED(hrc))
798 continue;
799
800 com::Bstr mac;
801
802 alock.release();
803 hrc = nic->COMGETTER(MACAddress)(mac.asOutParam());
804 alock.acquire();
805
806 if (FAILED(hrc)) /* no MAC address ??? */
807 continue;
808
809 /* Convert MAC address from XXXXXXXXXXXX to XX:XX:XX:XX:XX:XX */
810 Utf8Str strMacWithoutColons(mac);
811 const char *pszSrc = strMacWithoutColons.c_str();
812 RTMAC binaryMac;
813 if (RTStrConvertHexBytes(pszSrc, &binaryMac, sizeof(binaryMac), 0) != VINF_SUCCESS)
814 continue;
815 char szMac[18]; /* "XX:XX:XX:XX:XX:XX" */
816 if (RTStrPrintHexBytes(szMac, sizeof(szMac), &binaryMac, sizeof(binaryMac), RTSTRPRINTHEXBYTES_F_SEP_COLON) != VINF_SUCCESS)
817 continue;
818
819 xml::ElementNode *pMacConfig = pElmRoot->createChild("Config");
820 pMacConfig->setAttribute("MACAddress", szMac);
821
822 com::Utf8Str encodedOption;
823 settings::DhcpOptionMap &map = i_findOptMapByVmNameSlot(it->first.VmName, it->first.Slot);
824 settings::DhcpOptionMap::const_iterator itAdapterOption;
825 for (itAdapterOption = map.begin(); itAdapterOption != map.end(); ++itAdapterOption)
826 addOptionChild(pMacConfig, (*itAdapterOption).first, (*itAdapterOption).second);
827 }
828
829 xml::XmlFileWriter writer(doc);
830 writer.write(m->szTempConfigFileName, false);
831
832 m->dhcp.addArgPair(DHCPServerRunner::kDsrKeyConfig, m->szTempConfigFileName);
833 m->dhcp.addArgPair(DHCPServerRunner::kDsrKeyComment, m->networkName.c_str());
834
835#else /* !VBOX_WITH_DHCPD */
836 /* Main is needed for NATNetwork */
837 if (m->router)
838 m->dhcp.addArgPair(NetworkServiceRunner::kpszKeyNeedMain, "on");
839
840 /* Commmon Network Settings */
841 m->dhcp.addArgPair(NetworkServiceRunner::kpszKeyNetwork, aNetworkName.c_str());
842 if (!aTrunkName.isEmpty())
843 m->dhcp.addArgPair(NetworkServiceRunner::kpszTrunkName, aTrunkName.c_str());
844 m->dhcp.addArgPair(NetworkServiceRunner::kpszKeyTrunkType, aTrunkType.c_str());
845
846 /* XXX: should this MAC default initialization moved to NetworkServiceRunner? */
847 char strMAC[32];
848 Guid guid;
849 guid.create();
850 RTStrPrintf (strMAC, sizeof(strMAC), "08:00:27:%02X:%02X:%02X",
851 guid.raw()->au8[0], guid.raw()->au8[1], guid.raw()->au8[2]);
852 m->dhcp.addArgPair(NetworkServiceRunner::kpszMacAddress, strMAC);
853 m->dhcp.addArgPair(NetworkServiceRunner::kpszIpAddress, m->IPAddress.c_str());
854 m->dhcp.addArgPair(NetworkServiceRunner::kpszIpNetmask, m->GlobalDhcpOptions[DhcpOpt_SubnetMask].text.c_str());
855 m->dhcp.addArgPair(DHCPServerRunner::kDsrKeyLowerIp, m->lowerIP.c_str());
856 m->dhcp.addArgPair(DHCPServerRunner::kDsrKeyUpperIp, m->upperIP.c_str());
857#endif /* !VBOX_WITH_DHCPD */
858
859 /* XXX: This parameters Dhcp Server will fetch via API */
860 return RT_FAILURE(m->dhcp.start(!m->router /* KillProcOnExit */)) ? E_FAIL : S_OK;
861 //m->dhcp.detachFromServer(); /* need to do this to avoid server shutdown on runner destruction */
862}
863
864
865HRESULT DHCPServer::stop(void)
866{
867#ifdef VBOX_WITH_DHCPD
868 if (m->szTempConfigFileName[0])
869 {
870 RTFileDelete(m->szTempConfigFileName);
871 m->szTempConfigFileName[0] = 0;
872 }
873#endif /* VBOX_WITH_DHCPD */
874 return RT_FAILURE(m->dhcp.stop()) ? E_FAIL : S_OK;
875}
876
877
878HRESULT DHCPServer::findLeaseByMAC(const com::Utf8Str &aMac, LONG aType,
879 com::Utf8Str &aAddress, com::Utf8Str &aState, LONG64 *aIssued, LONG64 *aExpire)
880{
881 /* Reset output before we start */
882 *aIssued = 0;
883 *aExpire = 0;
884 aAddress.setNull();
885 aState.setNull();
886
887 /*
888 * Convert and check input.
889 */
890 RTMAC MacAddress;
891 int vrc = RTStrConvertHexBytes(aMac.c_str(), &MacAddress, sizeof(MacAddress), RTSTRCONVERTHEXBYTES_F_SEP_COLON);
892 if (vrc != VINF_SUCCESS)
893 return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid MAC address '%s': %Rrc"), aMac.c_str(), vrc);
894 if (aType != 0)
895 return setError(E_INVALIDARG, tr("flags must be zero (not %#x)"), aType);
896
897 /*
898 * Make sure we've got a lease filename to work with.
899 */
900 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
901 if (m->strLeasesFilename.isEmpty())
902 {
903 HRESULT hrc = i_calcLeasesFilename(m->networkName.isEmpty() ? mName : m->networkName);
904 if (FAILED(hrc))
905 return hrc;
906 }
907
908 /*
909 * Try at least twice to read the lease database, more if busy.
910 */
911 uint64_t const nsStart = RTTimeNanoTS();
912 for (uint32_t uReadAttempt = 0; ; uReadAttempt++)
913 {
914 /*
915 * Try read the file.
916 */
917 xml::Document doc;
918 try
919 {
920 xml::XmlFileParser parser;
921 parser.read(m->strLeasesFilename.c_str(), doc);
922 }
923 catch (const xml::EIPRTFailure &e)
924 {
925 vrc = e.rc();
926 LogThisFunc(("caught xml::EIPRTFailure: rc=%Rrc (attempt %u, msg=%s)\n", vrc, uReadAttempt, e.what()));
927 if ( ( vrc == VERR_FILE_NOT_FOUND
928 || vrc == VERR_OPEN_FAILED
929 || vrc == VERR_ACCESS_DENIED
930 || vrc == VERR_SHARING_VIOLATION
931 || vrc == VERR_READ_ERROR /*?*/)
932 && ( uReadAttempt == 0
933 || ( uReadAttempt < 64
934 && RTTimeNanoTS() - nsStart < RT_NS_1SEC / 4)) )
935 {
936 alock.release();
937
938 if (uReadAttempt > 0)
939 RTThreadYield();
940 RTThreadSleep(8/*ms*/);
941
942 alock.acquire();
943 LogThisFunc(("Retrying...\n"));
944 continue;
945 }
946 return setErrorBoth(VBOX_E_FILE_ERROR, vrc, "Reading '%s' failed: %Rrc - %s",
947 m->strLeasesFilename.c_str(), vrc, e.what());
948 }
949 catch (const RTCError &e)
950 {
951 if (e.what())
952 return setError(VBOX_E_FILE_ERROR, "Reading '%s' failed: %s", m->strLeasesFilename.c_str(), e.what());
953 return setError(VBOX_E_FILE_ERROR, "Reading '%s' failed: RTCError", m->strLeasesFilename.c_str());
954 }
955 catch (std::bad_alloc &)
956 {
957 return E_OUTOFMEMORY;
958 }
959 catch (...)
960 {
961 AssertFailed();
962 return setError(VBOX_E_FILE_ERROR, tr("Reading '%s' failed: Unexpected exception"), m->strLeasesFilename.c_str());
963 }
964
965 /*
966 * Look for that mac address.
967 */
968 xml::ElementNode *pElmRoot = doc.getRootElement();
969 if (pElmRoot && pElmRoot->nameEquals("Leases"))
970 {
971 xml::NodesLoop it(*pElmRoot);
972 const xml::ElementNode *pElmLease;
973 while ((pElmLease = it.forAllNodes()) != NULL)
974 if (pElmLease->nameEquals("Lease"))
975 {
976 const char *pszCurMacAddress = pElmLease->findAttributeValue("mac");
977 RTMAC CurMacAddress;
978 if ( pszCurMacAddress
979 && RT_SUCCESS(RTNetStrToMacAddr(pszCurMacAddress, &CurMacAddress))
980 && memcmp(&CurMacAddress, &MacAddress, sizeof(MacAddress)) == 0)
981 {
982 /*
983 * Found it!
984 */
985 xml::ElementNode const *pElmTime = pElmLease->findChildElement("Time");
986 int64_t secIssued = 0;
987 uint32_t cSecsToLive = 0;
988 if (pElmTime)
989 {
990 pElmTime->getAttributeValue("issued", &secIssued);
991 pElmTime->getAttributeValue("expiration", &cSecsToLive);
992 *aIssued = secIssued;
993 *aExpire = secIssued + cSecsToLive;
994 }
995 try
996 {
997 aAddress = pElmLease->findChildElementAttributeValue("Address", "value");
998 aState = pElmLease->findAttributeValue("state");
999 }
1000 catch (std::bad_alloc &)
1001 {
1002 return E_OUTOFMEMORY;
1003 }
1004
1005 /* Check if the lease has expired in the mean time. */
1006 HRESULT hrc = S_OK;
1007 RTTIMESPEC Now;
1008 if ( (aState.equals("acked") || aState.equals("offered") || aState.isEmpty())
1009 && secIssued + cSecsToLive < RTTimeSpecGetSeconds(RTTimeNow(&Now)))
1010 hrc = aState.assignNoThrow("expired");
1011 return hrc;
1012 }
1013 }
1014 }
1015 break;
1016 }
1017
1018 return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a lease for %RTmac"), &MacAddress);
1019}
1020
1021
1022/**
1023 * Calculates and updates the value of strLeasesFilename given @a aNetwork.
1024 */
1025HRESULT DHCPServer::i_calcLeasesFilename(const com::Utf8Str &aNetwork) RT_NOEXCEPT
1026{
1027 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1028
1029 /* The lease file must be the same as we used the last time, so careful when changing this code. */
1030 int vrc = m->strLeasesFilename.assignNoThrow(mVirtualBox->i_homeDir());
1031 if (RT_SUCCESS(vrc))
1032 vrc = RTPathAppendCxx(m->strLeasesFilename, aNetwork);
1033 if (RT_SUCCESS(vrc))
1034 vrc = m->strLeasesFilename.appendNoThrow("-Dhcpd.leases");
1035 if (RT_SUCCESS(vrc))
1036 {
1037 RTPathPurgeFilename(RTPathFilename(m->strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST);
1038 m->strLeasesFilename.jolt();
1039 return S_OK;
1040 }
1041 return setErrorBoth(E_FAIL, vrc, tr("Failed to construct lease filename: %Rrc"), vrc);
1042}
1043
1044settings::DhcpOptionMap &DHCPServer::i_findOptMapByVmNameSlot(const com::Utf8Str &aVmName,
1045 LONG aSlot)
1046{
1047 return m->VmSlot2Options[settings::VmNameSlotKey(aVmName, aSlot)];
1048}
1049
Note: See TracBrowser for help on using the repository browser.

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