VirtualBox

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

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

Main/DHCPServer,Dhcpd,VBoxManage: Added --log option to the DHCP server so we can start logging early. Added log rotation and limits. Put the config file next to the log and leases file. Validate DHCP options by reusing the parser code from the server, adding a bunch more DHCP options to the parser. Removed legacy and hardcoded configuration options from the dhcp server, it's all config file now. Fixed a bug in the option parsing of the VBoxManage dhcpserver add/modify commands. bugref:9288

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