VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/Dhcpd/Config.cpp@ 79613

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

Dhcpd: Made the lease database filename configurable so Main can decide exactly where it is and what's it called in order to correctly implement IDCHPServer::FindLeaseByMAC. Use RTPathPurgeFilename to create the default lease filename. Added a todo regarding a bogus looking message option in an ACK. bugref:9288

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 31.2 KB
Line 
1/* $Id: Config.cpp 79613 2019-07-08 23:50:48Z vboxsync $ */
2/** @file
3 * DHCP server - server configuration
4 */
5
6/*
7 * Copyright (C) 2017-2019 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#include "DhcpdInternal.h"
23
24#include <iprt/ctype.h>
25#include <iprt/net.h> /* NB: must come before getopt.h */
26#include <iprt/getopt.h>
27#include <iprt/path.h>
28#include <iprt/message.h>
29#include <iprt/string.h>
30#include <iprt/uuid.h>
31
32#include <VBox/com/com.h> /* For log initialization. */
33
34#include "Config.h"
35
36
37/**
38 * Configuration file exception.
39 */
40class ConfigFileError
41 : public RTCError
42{
43public:
44#if 0 /* This just confuses the compiler. */
45 ConfigFileError(const char *a_pszMessage)
46 : RTCError(a_pszMessage)
47 {}
48#endif
49
50 ConfigFileError(const char *a_pszMsgFmt, ...)
51 : RTCError((char *)NULL)
52 {
53 va_list va;
54 va_start(va, a_pszMsgFmt);
55 m_strMsg.printfV(a_pszMsgFmt, va);
56 va_end(va);
57 }
58
59 ConfigFileError(const RTCString &a_rstrMessage)
60 : RTCError(a_rstrMessage)
61 {}
62};
63
64
65/**
66 * Private default constructor, external users use factor methods.
67 */
68Config::Config()
69 : m_strHome()
70 , m_strNetwork()
71 , m_strBaseName()
72 , m_strTrunk()
73 , m_enmTrunkType(kIntNetTrunkType_Invalid)
74 , m_MacAddress()
75 , m_IPv4Address()
76 , m_IPv4Netmask()
77 , m_IPv4PoolFirst()
78 , m_IPv4PoolLast()
79 , m_GlobalOptions()
80 , m_VMMap()
81{
82}
83
84
85/**
86 * Initializes the object.
87 *
88 * @returns IPRT status code.
89 */
90int Config::i_init() RT_NOEXCEPT
91{
92 return i_homeInit();
93}
94
95
96/**
97 * Initializes the m_strHome member with the path to ~/.VirtualBox or equivalent.
98 *
99 * @returns IPRT status code.
100 * @todo Too many init functions?
101 */
102int Config::i_homeInit() RT_NOEXCEPT
103{
104 char szHome[RTPATH_MAX];
105 int rc = com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome), false);
106 if (RT_SUCCESS(rc))
107 rc = m_strHome.assignNoThrow(szHome);
108 else
109 {
110 LogFunc(("unable to locate the VirtualBox home directory: %Rrc", rc)); /* no release log at this point. */
111 RTMsgError("unable to locate the VirtualBox home directory: %Rrs", rc);
112 }
113 return rc;
114}
115
116
117/**
118 * Internal worker for the public factory methods that creates an instance and
119 * calls i_init() on it.
120 *
121 * @returns Config instance on success, NULL on failure.
122 */
123/*static*/ Config *Config::i_createInstanceAndCallInit() RT_NOEXCEPT
124{
125 Config *pConfig;
126 try
127 {
128 pConfig = new Config();
129 }
130 catch (std::bad_alloc &)
131 {
132 return NULL;
133 }
134
135 int rc = pConfig->i_init();
136 if (RT_SUCCESS(rc))
137 return pConfig;
138 delete pConfig;
139 return NULL;
140}
141
142
143/**
144 * Internal network name (m_strNetwork) setter.
145 *
146 * Can only be called once. Will also invoke i_sanitizeBaseName to update
147 * m_strBaseName.
148 *
149 * @throws std::bad_alloc
150 */
151void Config::i_setNetwork(const RTCString &aStrNetwork)
152{
153 AssertReturnVoid(m_strNetwork.isEmpty());
154
155 m_strNetwork = aStrNetwork;
156 i_sanitizeBaseName();
157}
158
159
160/**
161 * Interal worker for i_setNetwork() that sets m_strBaseName to sanitized the
162 * version of m_strNetwork suitable for use as a path component.
163 *
164 * @throws std::bad_alloc
165 */
166void Config::i_sanitizeBaseName()
167{
168 if (m_strNetwork.isNotEmpty())
169 {
170 m_strBaseName = m_strNetwork;
171 RTPathPurgeFilename(m_strBaseName.mutableRaw(), RTPATH_STR_F_STYLE_HOST);
172 m_strBaseName.jolt();
173 }
174 else
175 m_strBaseName.setNull();
176}
177
178
179/**
180 * Worker for i_complete() that initializes the release log of the process.
181 *
182 * Requires network name to be known as the log file name depends on
183 * it. Alternatively, consider passing the log file name via the
184 * command line?
185 *
186 * @todo make the log file directly configurable?
187 */
188int Config::i_logInit() RT_NOEXCEPT
189{
190 if (m_strHome.isEmpty() || m_strBaseName.isEmpty())
191 return VERR_PATH_ZERO_LENGTH;
192
193 /* default log file name */
194 char szLogFile[RTPATH_MAX];
195 ssize_t cch = RTStrPrintf2(szLogFile, sizeof(szLogFile),
196 "%s%c%s-Dhcpd.log",
197 m_strHome.c_str(), RTPATH_DELIMITER, m_strBaseName.c_str());
198 if (cch <= 0)
199 return VERR_BUFFER_OVERFLOW;
200
201 /* Sanitize base name some more to be usable in an environment variable name: */
202 char szEnvVarBase[128];
203 cch = RTStrPrintf(szEnvVarBase, sizeof(szEnvVarBase), "VBOXDHCP_%s_RELEASE_LOG", m_strBaseName.c_str());
204 if (cch <= 0)
205 return VERR_BUFFER_OVERFLOW;
206 for (char *p = szEnvVarBase; *p != '\0'; ++p)
207 if (*p != '_' && !RT_C_IS_ALNUM(*p))
208 *p = '_';
209
210 int rc = com::VBoxLogRelCreate("DHCP Server",
211 szLogFile,
212 RTLOGFLAGS_PREFIX_TIME_PROG,
213 "all all.restrict -default.restrict",
214 szEnvVarBase,
215 RTLOGDEST_FILE
216#ifdef DEBUG
217 | RTLOGDEST_STDERR
218#endif
219 ,
220 32768 /* cMaxEntriesPerGroup */,
221 0 /* cHistory */,
222 0 /* uHistoryFileTime */,
223 0 /* uHistoryFileSize */,
224 NULL /* pErrInfo */);
225
226 return rc;
227}
228
229
230/**
231 * Post process and validate the configuration after it has been loaded.
232 */
233int Config::i_complete() RT_NOEXCEPT
234{
235 if (m_strNetwork.isEmpty())
236 {
237 LogRel(("network name is not specified\n"));
238 return false;
239 }
240
241 i_logInit();
242
243 bool fMACGenerated = false;
244 if ( m_MacAddress.au16[0] == 0
245 && m_MacAddress.au16[1] == 0
246 && m_MacAddress.au16[2] == 0)
247 {
248 RTUUID Uuid;
249 int rc = RTUuidCreate(&Uuid);
250 AssertRCReturn(rc, rc);
251
252 m_MacAddress.au8[0] = 0x08;
253 m_MacAddress.au8[1] = 0x00;
254 m_MacAddress.au8[2] = 0x27;
255 m_MacAddress.au8[3] = Uuid.Gen.au8Node[3];
256 m_MacAddress.au8[4] = Uuid.Gen.au8Node[4];
257 m_MacAddress.au8[5] = Uuid.Gen.au8Node[5];
258
259 LogRel(("MAC address is not specified: will use generated MAC %RTmac\n", &m_MacAddress));
260 fMACGenerated = true;
261 }
262
263 /* unicast MAC address */
264 if (m_MacAddress.au8[0] & 0x01)
265 {
266 LogRel(("MAC address is not unicast: %RTmac\n", &m_MacAddress));
267 return VERR_GENERAL_FAILURE;
268 }
269
270 /* unicast IP address */
271 if ((m_IPv4Address.au8[0] & 0xe0) == 0xe0)
272 {
273 LogRel(("IP address is not unicast: %RTnaipv4\n", m_IPv4Address.u));
274 return VERR_GENERAL_FAILURE;
275 }
276
277 /* valid netmask */
278 int cPrefixBits;
279 int rc = RTNetMaskToPrefixIPv4(&m_IPv4Netmask, &cPrefixBits);
280 if (RT_FAILURE(rc) || cPrefixBits == 0)
281 {
282 LogRel(("IP mask is not valid: %RTnaipv4\n", m_IPv4Netmask.u));
283 return VERR_GENERAL_FAILURE;
284 }
285
286 /* first IP is from the same network */
287 if ((m_IPv4PoolFirst.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u))
288 {
289 LogRel(("first pool address is outside the network %RTnaipv4/%d: %RTnaipv4\n",
290 (m_IPv4Address.u & m_IPv4Netmask.u), cPrefixBits, m_IPv4PoolFirst.u));
291 return VERR_GENERAL_FAILURE;
292 }
293
294 /* last IP is from the same network */
295 if ((m_IPv4PoolLast.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u))
296 {
297 LogRel(("last pool address is outside the network %RTnaipv4/%d: %RTnaipv4\n",
298 (m_IPv4Address.u & m_IPv4Netmask.u), cPrefixBits, m_IPv4PoolLast.u));
299 return VERR_GENERAL_FAILURE;
300 }
301
302 /* the pool is valid */
303 if (RT_N2H_U32(m_IPv4PoolLast.u) < RT_N2H_U32(m_IPv4PoolFirst.u))
304 {
305 LogRel(("pool range is invalid: %RTnaipv4 - %RTnaipv4\n",
306 m_IPv4PoolFirst.u, m_IPv4PoolLast.u));
307 return VERR_GENERAL_FAILURE;
308 }
309
310 /* our own address is not inside the pool */
311 if ( RT_N2H_U32(m_IPv4PoolFirst.u) <= RT_N2H_U32(m_IPv4Address.u)
312 && RT_N2H_U32(m_IPv4Address.u) <= RT_N2H_U32(m_IPv4PoolLast.u))
313 {
314 LogRel(("server address inside the pool range %RTnaipv4 - %RTnaipv4: %RTnaipv4\n",
315 m_IPv4PoolFirst.u, m_IPv4PoolLast.u, m_IPv4Address.u));
316 return VERR_GENERAL_FAILURE;
317 }
318
319 if (!fMACGenerated)
320 LogRel(("MAC address %RTmac\n", &m_MacAddress));
321 LogRel(("IP address %RTnaipv4/%d\n", m_IPv4Address.u, cPrefixBits));
322 LogRel(("address pool %RTnaipv4 - %RTnaipv4\n", m_IPv4PoolFirst.u, m_IPv4PoolLast.u));
323
324 return VINF_SUCCESS;
325}
326
327
328/*static*/ Config *Config::hardcoded() RT_NOEXCEPT
329{
330 std::unique_ptr<Config> config(i_createInstanceAndCallInit());
331 AssertReturn(config.get() != NULL, NULL);
332 try
333 {
334 config->i_setNetwork("HostInterfaceNetworking-vboxnet0");
335 config->m_strTrunk.assign("vboxnet0");
336 }
337 catch (std::bad_alloc &)
338 {
339 return NULL;
340 }
341 config->m_enmTrunkType = kIntNetTrunkType_NetFlt;
342
343 config->m_MacAddress.au8[0] = 0x08;
344 config->m_MacAddress.au8[1] = 0x00;
345 config->m_MacAddress.au8[2] = 0x27;
346 config->m_MacAddress.au8[3] = 0xa9;
347 config->m_MacAddress.au8[4] = 0xcf;
348 config->m_MacAddress.au8[5] = 0xef;
349
350
351 config->m_IPv4Address.u = RT_H2N_U32_C(0xc0a838fe); /* 192.168.56.254 */
352 config->m_IPv4Netmask.u = RT_H2N_U32_C(0xffffff00); /* 255.255.255.0 */
353
354 /* flip to test naks */
355#if 1
356 config->m_IPv4PoolFirst.u = RT_H2N_U32_C(0xc0a8385a); /* 192.168.56.90 */
357 config->m_IPv4PoolLast.u = RT_H2N_U32_C(0xc0a83863); /* 192.168.56.99 */
358#else
359 config->m_IPv4PoolFirst.u = RT_H2N_U32_C(0xc0a838c9); /* 192.168.56.201 */
360 config->m_IPv4PoolLast.u = RT_H2N_U32_C(0xc0a838dc); /* 192.168.56.220 */
361#endif
362
363 int rc = config->i_complete();
364 AssertRCReturn(rc, NULL);
365
366 return config.release();
367}
368
369
370/**
371 * Old VBoxNetDHCP style command line parsing.
372 *
373 * @throws std::bad_alloc
374 */
375/*static*/ Config *Config::compat(int argc, char **argv)
376{
377 /* compatibility with old VBoxNetDHCP */
378 static const RTGETOPTDEF s_aCompatOptions[] =
379 {
380 { "--ip-address", 'i', RTGETOPT_REQ_IPV4ADDR },
381 { "--lower-ip", 'l', RTGETOPT_REQ_IPV4ADDR },
382 { "--mac-address", 'a', RTGETOPT_REQ_MACADDR },
383 { "--need-main", 'M', RTGETOPT_REQ_BOOL },
384 { "--netmask", 'm', RTGETOPT_REQ_IPV4ADDR },
385 { "--network", 'n', RTGETOPT_REQ_STRING },
386 { "--trunk-name", 't', RTGETOPT_REQ_STRING },
387 { "--trunk-type", 'T', RTGETOPT_REQ_STRING },
388 { "--upper-ip", 'u', RTGETOPT_REQ_IPV4ADDR },
389 };
390
391 RTGETOPTSTATE State;
392 int rc = RTGetOptInit(&State, argc, argv, s_aCompatOptions, RT_ELEMENTS(s_aCompatOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
393 AssertRCReturn(rc, NULL);
394
395 std::unique_ptr<Config> config(i_createInstanceAndCallInit());
396 AssertReturn(config.get() != NULL, NULL);
397
398 for (;;)
399 {
400 RTGETOPTUNION ValueUnion;
401 rc = RTGetOpt(&State, &ValueUnion);
402 if (rc == 0) /* done */
403 break;
404
405 switch (rc)
406 {
407 case 'a': /* --mac-address */
408 if ( config->m_MacAddress.au16[0] != 0
409 || config->m_MacAddress.au16[1] != 0
410 || config->m_MacAddress.au16[2] != 0)
411 {
412 RTMsgError("Duplicate --mac-address option");
413 return NULL;
414 }
415 config->m_MacAddress = ValueUnion.MacAddr;
416 break;
417
418 case 'i': /* --ip-address */
419 if (config->m_IPv4Address.u != 0)
420 {
421 RTMsgError("Duplicate --ip-address option");
422 return NULL;
423 }
424 config->m_IPv4Address = ValueUnion.IPv4Addr;
425 break;
426
427 case 'l': /* --lower-ip */
428 if (config->m_IPv4PoolFirst.u != 0)
429 {
430 RTMsgError("Duplicate --lower-ip option");
431 return NULL;
432 }
433 config->m_IPv4PoolFirst = ValueUnion.IPv4Addr;
434 break;
435
436 case 'M': /* --need-main */
437 /* for backward compatibility, ignored */
438 break;
439
440 case 'm': /* --netmask */
441 if (config->m_IPv4Netmask.u != 0)
442 {
443 RTMsgError("Duplicate --netmask option");
444 return NULL;
445 }
446 config->m_IPv4Netmask = ValueUnion.IPv4Addr;
447 break;
448
449 case 'n': /* --network */
450 if (!config->m_strNetwork.isEmpty())
451 {
452 RTMsgError("Duplicate --network option");
453 return NULL;
454 }
455 config->i_setNetwork(ValueUnion.psz);
456 break;
457
458 case 't': /* --trunk-name */
459 if (!config->m_strTrunk.isEmpty())
460 {
461 RTMsgError("Duplicate --trunk-name option");
462 return NULL;
463 }
464 config->m_strTrunk.assign(ValueUnion.psz);
465 break;
466
467 case 'T': /* --trunk-type */
468 if (config->m_enmTrunkType != kIntNetTrunkType_Invalid)
469 {
470 RTMsgError("Duplicate --trunk-type option");
471 return NULL;
472 }
473 else if (strcmp(ValueUnion.psz, "none") == 0)
474 config->m_enmTrunkType = kIntNetTrunkType_None;
475 else if (strcmp(ValueUnion.psz, "whatever") == 0)
476 config->m_enmTrunkType = kIntNetTrunkType_WhateverNone;
477 else if (strcmp(ValueUnion.psz, "netflt") == 0)
478 config->m_enmTrunkType = kIntNetTrunkType_NetFlt;
479 else if (strcmp(ValueUnion.psz, "netadp") == 0)
480 config->m_enmTrunkType = kIntNetTrunkType_NetAdp;
481 else
482 {
483 RTMsgError("Unknown trunk type '%s'", ValueUnion.psz);
484 return NULL;
485 }
486 break;
487
488 case 'u': /* --upper-ip */
489 if (config->m_IPv4PoolLast.u != 0)
490 {
491 RTMsgError("Duplicate --upper-ip option");
492 return NULL;
493 }
494 config->m_IPv4PoolLast = ValueUnion.IPv4Addr;
495 break;
496
497 case VINF_GETOPT_NOT_OPTION:
498 RTMsgError("%s: Unexpected command line argument", ValueUnion.psz);
499 return NULL;
500
501 default:
502 RTGetOptPrintError(rc, &ValueUnion);
503 return NULL;
504 }
505 }
506
507 rc = config->i_complete();
508 if (RT_FAILURE(rc))
509 return NULL;
510
511 return config.release();
512}
513
514
515Config *Config::create(int argc, char **argv) RT_NOEXCEPT
516{
517 static const RTGETOPTDEF s_aOptions[] =
518 {
519 { "--config", 'c', RTGETOPT_REQ_STRING },
520 { "--comment", '#', RTGETOPT_REQ_STRING }
521 };
522
523 RTGETOPTSTATE State;
524 int rc = RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
525 AssertRCReturn(rc, NULL);
526
527 std::unique_ptr<Config> config;
528
529 for (;;)
530 {
531 RTGETOPTUNION ValueUnion;
532 rc = RTGetOpt(&State, &ValueUnion);
533 if (rc == 0) /* done */
534 break;
535
536 switch (rc)
537 {
538 case 'c': /* --config */
539 if (config.get() != NULL)
540 {
541 RTMsgError("Duplicate option: --config '%s'\n", ValueUnion.psz);
542 return NULL;
543 }
544
545 RTMsgInfo("reading config from '%s'...\n", ValueUnion.psz);
546 config.reset(Config::i_read(ValueUnion.psz));
547 if (config.get() == NULL)
548 return NULL;
549 break;
550
551 case '#': /* --comment */
552 /* The sole purpose of this option is to allow identification of DHCP
553 * server instances in the process list. We ignore the required string
554 * argument of this option.
555 */
556 continue;
557
558 case VINF_GETOPT_NOT_OPTION:
559 RTMsgError("Unexpected command line argument: '%s'", ValueUnion.psz);
560 return NULL;
561
562 default:
563 RTGetOptPrintError(rc, &ValueUnion);
564 return NULL;
565 }
566 }
567
568 if (config.get() != NULL)
569 {
570 rc = config->i_complete();
571 if (RT_SUCCESS(rc))
572 return config.release();
573 }
574 else
575 RTMsgError("No configuration file specified (--config file)!\n");
576 return NULL;
577}
578
579
580/**
581 *
582 * @note The release log has is not operational when this method is called.
583 */
584Config *Config::i_read(const char *pszFileName) RT_NOEXCEPT
585{
586 if (pszFileName == NULL || pszFileName[0] == '\0')
587 {
588 RTMsgError("Empty configuration filename");
589 return NULL;
590 }
591
592 xml::Document doc;
593 try
594 {
595 xml::XmlFileParser parser;
596 parser.read(pszFileName, doc);
597 }
598 catch (const xml::EIPRTFailure &e)
599 {
600 LogFunc(("%s\n", e.what()));
601 RTMsgError("%s\n", e.what());
602 return NULL;
603 }
604 catch (const RTCError &e)
605 {
606 LogFunc(("%s\n", e.what()));
607 RTMsgError("%s\n", e.what());
608 return NULL;
609 }
610 catch (...)
611 {
612 LogFunc(("Unknown exception while reading and parsing '%s'\n", pszFileName));
613 RTMsgError("Unknown exception while reading and parsing '%s'\n", pszFileName);
614 return NULL;
615 }
616
617 std::unique_ptr<Config> config(i_createInstanceAndCallInit());
618 AssertReturn(config.get() != NULL, NULL);
619
620 try
621 {
622 config->i_parseConfig(doc.getRootElement());
623 }
624 catch (const RTCError &e)
625 {
626 LogFunc(("%s\n", e.what()));
627 RTMsgError("%s\n", e.what());
628 return NULL;
629 }
630 catch (std::bad_alloc &)
631 {
632 LogFunc(("std::bad_alloc\n"));
633 RTMsgError("std::bad_alloc reading config\n");
634 return NULL;
635 }
636 catch (...)
637 {
638 LogFunc(("Unexpected exception\n"));
639 RTMsgError("Unexpected exception\n");
640 return NULL;
641 }
642
643 return config.release();
644}
645
646
647/**
648 * Internal worker for i_read() that parses the root element and everything
649 * below it.
650 *
651 * @param pElmRoot The root element.
652 * @throws std::bad_alloc, ConfigFileError
653 */
654void Config::i_parseConfig(const xml::ElementNode *pElmRoot)
655{
656 /*
657 * Check the root element and call i_parseServer to do real work.
658 */
659 if (pElmRoot == NULL)
660 throw ConfigFileError("Empty config file");
661
662 /** @todo XXX: NAMESPACE API IS COMPLETELY BROKEN, SO IGNORE IT FOR NOW */
663
664 if (!pElmRoot->nameEquals("DHCPServer"))
665 throw ConfigFileError("Unexpected root element '%s'", pElmRoot->getName());
666
667 i_parseServer(pElmRoot);
668
669#if 0 /** @todo convert to LogRel2 stuff */
670 // XXX: debug
671 for (optmap_t::const_iterator it = m_GlobalOptions.begin(); it != m_GlobalOptions.end(); ++it) {
672 std::shared_ptr<DhcpOption> opt(it->second);
673
674 octets_t data;
675 opt->encode(data);
676
677 bool space = false;
678 for (octets_t::const_iterator itData = data.begin(); itData != data.end(); ++itData) {
679 uint8_t c = *itData;
680 if (space)
681 std::cout << " ";
682 else
683 space = true;
684 std::cout << (int)c;
685 }
686 std::cout << std::endl;
687 }
688#endif
689}
690
691
692/**
693 * Internal worker for parsing the elements under /DHCPServer/.
694 *
695 * @param pElmServer The DHCPServer element.
696 * @throws std::bad_alloc, ConfigFileError
697 */
698void Config::i_parseServer(const xml::ElementNode *pElmServer)
699{
700 /*
701 * <DHCPServer> attributes
702 */
703 const char *pszNetworkName;
704 if (pElmServer->getAttributeValue("networkName", &pszNetworkName))
705 i_setNetwork(pszNetworkName);
706 else
707 throw ConfigFileError("DHCPServer/@networkName missing");
708 /** @todo log file override. */
709 /** @todo log level (dhcpd group flags). */
710 /** @todo log flags. */
711 /** @todo control logging to stderr/out. */
712 /** @todo if we like we could open the release log now. */
713
714 const char *pszTrunkType;
715 if (!pElmServer->getAttributeValue("trunkType", &pszTrunkType))
716 throw ConfigFileError("DHCPServer/@trunkType missing");
717 if (strcmp(pszTrunkType, "none") == 0)
718 m_enmTrunkType = kIntNetTrunkType_None;
719 else if (strcmp(pszTrunkType, "whatever") == 0)
720 m_enmTrunkType = kIntNetTrunkType_WhateverNone;
721 else if (strcmp(pszTrunkType, "netflt") == 0)
722 m_enmTrunkType = kIntNetTrunkType_NetFlt;
723 else if (strcmp(pszTrunkType, "netadp") == 0)
724 m_enmTrunkType = kIntNetTrunkType_NetAdp;
725 else
726 throw ConfigFileError("Invalid DHCPServer/@trunkType value: %s", pszTrunkType);
727
728 if ( m_enmTrunkType == kIntNetTrunkType_NetFlt
729 || m_enmTrunkType == kIntNetTrunkType_NetAdp)
730 {
731 if (!pElmServer->getAttributeValue("trunkName", &m_strTrunk))
732 throw ConfigFileError("DHCPServer/@trunkName missing");
733 }
734 else
735 m_strTrunk = "";
736
737 m_strLeaseFilename = pElmServer->findAttributeValue("leaseFilename"); /* optional */
738
739 i_getIPv4AddrAttribute(pElmServer, "IPAddress", &m_IPv4Address);
740 i_getIPv4AddrAttribute(pElmServer, "networkMask", &m_IPv4Netmask);
741 i_getIPv4AddrAttribute(pElmServer, "lowerIP", &m_IPv4PoolFirst);
742 i_getIPv4AddrAttribute(pElmServer, "upperIP", &m_IPv4PoolLast);
743
744 /*
745 * <DHCPServer> children
746 */
747 xml::NodesLoop it(*pElmServer);
748 const xml::ElementNode *pElmChild;
749 while ((pElmChild = it.forAllNodes()) != NULL)
750 {
751 /*
752 * Global options
753 */
754 if (pElmChild->nameEquals("Options"))
755 i_parseGlobalOptions(pElmChild);
756 /*
757 * Per-VM configuration
758 */
759 else if (pElmChild->nameEquals("Config"))
760 i_parseVMConfig(pElmChild);
761 else
762 LogRel(("Ignoring unexpected DHCPServer child: %s\n", pElmChild->getName()));
763 }
764}
765
766
767/**
768 * Internal worker for parsing the elements under /DHCPServer/Options/.
769 *
770 * @param pElmServer The <Options> element.
771 * @throws std::bad_alloc, ConfigFileError
772 */
773void Config::i_parseGlobalOptions(const xml::ElementNode *options)
774{
775 xml::NodesLoop it(*options);
776 const xml::ElementNode *pElmChild;
777 while ((pElmChild = it.forAllNodes()) != NULL)
778 {
779 if (pElmChild->nameEquals("Option"))
780 i_parseOption(pElmChild, m_GlobalOptions);
781 else
782 throw ConfigFileError("Unexpected element <%s>", pElmChild->getName());
783 }
784}
785
786
787/**
788 * Internal worker for parsing the elements under /DHCPServer/Config/.
789 *
790 * VM Config entries are generated automatically from VirtualBox.xml
791 * with the MAC fetched from the VM config. The client id is nowhere
792 * in the picture there, so VM config is indexed with plain RTMAC, not
793 * ClientId (also see getOptions below).
794 *
795 * @param pElmServer The <Config> element.
796 * @throws std::bad_alloc, ConfigFileError
797 */
798void Config::i_parseVMConfig(const xml::ElementNode *pElmConfig)
799{
800 /*
801 * Attributes:
802 */
803 /* The MAC address: */
804 RTMAC MacAddr;
805 i_getMacAddressAttribute(pElmConfig, "MACAddress", &MacAddr);
806
807 vmmap_t::iterator vmit( m_VMMap.find(MacAddr) );
808 if (vmit != m_VMMap.end())
809 throw ConfigFileError("Duplicate Config for MACAddress %RTmac", &MacAddr);
810
811 optmap_t &vmopts = m_VMMap[MacAddr];
812
813 /* Name - optional: */
814 const char *pszName = NULL;
815 if (pElmConfig->getAttributeValue("name", &pszName))
816 {
817 /** @todo */
818 }
819
820 /* Fixed IP address assignment - optional: */
821 if (pElmConfig->findAttribute("FixedIPAddress") != NULL)
822 {
823 /** @todo */
824 }
825
826 /*
827 * Process the children.
828 */
829 xml::NodesLoop it(*pElmConfig);
830 const xml::ElementNode *pElmChild;
831 while ((pElmChild = it.forAllNodes()) != NULL)
832 if (pElmChild->nameEquals("Option"))
833 i_parseOption(pElmChild, vmopts);
834 else
835 throw ConfigFileError("Unexpected element '%s' under '%s'", pElmChild->getName(), pElmConfig->getName());
836}
837
838
839/**
840 * Internal worker for parsing <Option> elements found under
841 * /DHCPServer/Options/ and /DHCPServer/Config/
842 *
843 * @param pElmServer The <Option> element.
844 * @param optmap The option map to add the option to.
845 * @throws std::bad_alloc, ConfigFileError
846 */
847void Config::i_parseOption(const xml::ElementNode *pElmOption, optmap_t &optmap)
848{
849 /* The 'name' attribute: */
850 const char *pszName;
851 if (!pElmOption->getAttributeValue("name", &pszName))
852 throw ConfigFileError("missing option name");
853
854 uint8_t u8Opt;
855 int rc = RTStrToUInt8Full(pszName, 10, &u8Opt);
856 if (rc != VINF_SUCCESS) /* no warnings either */
857 throw ConfigFileError("Bad option name '%s'", pszName);
858
859 /* The opional 'encoding' attribute: */
860 uint32_t u32Enc = 0; /* XXX: DhcpOptEncoding_Legacy */
861 const char *pszEncoding;
862 if (pElmOption->getAttributeValue("encoding", &pszEncoding))
863 {
864 rc = RTStrToUInt32Full(pszEncoding, 10, &u32Enc);
865 if (rc != VINF_SUCCESS) /* no warnings either */
866 throw ConfigFileError("Bad option encoding '%s'", pszEncoding);
867
868 switch (u32Enc)
869 {
870 case 0: /* XXX: DhcpOptEncoding_Legacy */
871 case 1: /* XXX: DhcpOptEncoding_Hex */
872 break;
873 default:
874 throw ConfigFileError("Unknown encoding '%s'", pszEncoding);
875 }
876 }
877
878 /* The 'value' attribute. May be omitted for OptNoValue options like rapid commit. */
879 const char *pszValue;
880 if (!pElmOption->getAttributeValue("value", &pszValue))
881 pszValue = "";
882
883 /** @todo XXX: TODO: encoding, handle hex */
884 DhcpOption *opt = DhcpOption::parse(u8Opt, u32Enc, pszValue);
885 if (opt == NULL)
886 throw ConfigFileError("Bad option '%s' (encoding %u): '%s' ", pszName, u32Enc, pszValue ? pszValue : "");
887
888 /* Add it to the map: */
889 optmap << opt;
890}
891
892
893/**
894 * Helper for retrieving a IPv4 attribute.
895 *
896 * @param pElm The element to get the attribute from.
897 * @param pszAttrName The name of the attribute
898 * @param pAddr Where to return the address.
899 * @throws ConfigFileError
900 */
901/*static*/ void Config::i_getIPv4AddrAttribute(const xml::ElementNode *pElm, const char *pszAttrName, PRTNETADDRIPV4 pAddr)
902{
903 const char *pszAttrValue;
904 if (pElm->getAttributeValue(pszAttrName, &pszAttrValue))
905 {
906 int rc = RTNetStrToIPv4Addr(pszAttrValue, pAddr);
907 if (RT_SUCCESS(rc))
908 return;
909 throw ConfigFileError("%s attribute %s is not a valid IPv4 address: '%s' -> %Rrc",
910 pElm->getName(), pszAttrName, pszAttrValue, rc);
911 }
912 else
913 throw ConfigFileError("Required %s attribute missing on element %s", pszAttrName, pElm->getName());
914}
915
916
917/**
918 * Helper for retrieving a MAC address attribute.
919 *
920 * @param pElm The element to get the attribute from.
921 * @param pszAttrName The name of the attribute
922 * @param pMacAddr Where to return the MAC address.
923 * @throws ConfigFileError
924 */
925/*static*/ void Config::i_getMacAddressAttribute(const xml::ElementNode *pElm, const char *pszAttrName, PRTMAC pMacAddr)
926{
927 const char *pszAttrValue;
928 if (pElm->getAttributeValue(pszAttrName, &pszAttrValue))
929 {
930 int rc = RTNetStrToMacAddr(pszAttrValue, pMacAddr);
931 if (RT_SUCCESS(rc) && rc != VWRN_TRAILING_CHARS)
932 return;
933 throw ConfigFileError("%s attribute %s is not a valid MAC address: '%s' -> %Rrc",
934 pElm->getName(), pszAttrName, pszAttrValue, rc);
935 }
936 else
937 throw ConfigFileError("Required %s attribute missing on element %s", pszAttrName, pElm->getName());
938}
939
940
941/**
942 * Method used by DHCPD to assemble a list of options for the client.
943 *
944 * @returns a_rRetOpts for convenience
945 * @param a_rRetOpts Where to put the requested options.
946 * @param reqOpts The requested options.
947 * @param id The client ID.
948 * @param idVendorClass The vendor class ID.
949 * @param idUserClass The user class ID.
950 *
951 * @throws std::bad_alloc
952 */
953optmap_t &Config::getOptions(optmap_t &a_rRetOpts, const OptParameterRequest &reqOpts, const ClientId &id,
954 const OptVendorClassId &idVendorClass /*= OptVendorClassId()*/,
955 const OptUserClassId &idUserClass /*= OptUserClassId()*/) const
956{
957 const optmap_t *vmopts = NULL;
958 vmmap_t::const_iterator vmit( m_VMMap.find(id.mac()) );
959 if (vmit != m_VMMap.end())
960 vmopts = &vmit->second;
961
962 RT_NOREF(idVendorClass, idUserClass); /* not yet */
963
964 a_rRetOpts << new OptSubnetMask(m_IPv4Netmask);
965
966 const OptParameterRequest::value_t& reqValue = reqOpts.value();
967 for (octets_t::const_iterator itOptReq = reqValue.begin(); itOptReq != reqValue.end(); ++itOptReq)
968 {
969 uint8_t optreq = *itOptReq;
970 LogRel2((">>> requested option %d (%#x)\n", optreq, optreq));
971
972 if (optreq == OptSubnetMask::optcode)
973 {
974 LogRel2(("... always supplied\n"));
975 continue;
976 }
977
978 if (vmopts != NULL)
979 {
980 optmap_t::const_iterator it( vmopts->find(optreq) );
981 if (it != vmopts->end())
982 {
983 a_rRetOpts << it->second;
984 LogRel2(("... found in VM options\n"));
985 continue;
986 }
987 }
988
989 optmap_t::const_iterator it( m_GlobalOptions.find(optreq) );
990 if (it != m_GlobalOptions.end())
991 {
992 a_rRetOpts << it->second;
993 LogRel2(("... found in global options\n"));
994 continue;
995 }
996
997 LogRel3(("... not found\n"));
998 }
999
1000
1001#if 0 /* bird disabled this as it looks dubious and testing only. */
1002 /** @todo XXX: testing ... */
1003 if (vmopts != NULL)
1004 {
1005 for (optmap_t::const_iterator it = vmopts->begin(); it != vmopts->end(); ++it)
1006 {
1007 std::shared_ptr<DhcpOption> opt(it->second);
1008 if (a_rRetOpts.count(opt->optcode()) == 0 && opt->optcode() > 127)
1009 {
1010 a_rRetOpts << opt;
1011 LogRel2(("... forcing VM option %d (%#x)\n", opt->optcode(), opt->optcode()));
1012 }
1013 }
1014 }
1015
1016 for (optmap_t::const_iterator it = m_GlobalOptions.begin(); it != m_GlobalOptions.end(); ++it)
1017 {
1018 std::shared_ptr<DhcpOption> opt(it->second);
1019 if (a_rRetOpts.count(opt->optcode()) == 0 && opt->optcode() > 127)
1020 {
1021 a_rRetOpts << opt;
1022 LogRel2(("... forcing global option %d (%#x)", opt->optcode(), opt->optcode()));
1023 }
1024 }
1025#endif
1026
1027 return a_rRetOpts;
1028}
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