VirtualBox

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

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

Main: s/DHCPOptionEncoding_Legacy/DHCPOptionEncoding_Normal/g as 'Legacy' does not seem a good fit for the more userfriendly value encoding. 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 79778 2019-07-15 00:36:08Z 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: DHCPOptionEncoding_Normal */
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: DHCPOptionEncoding_Normal */
722 case 1: /* XXX: DHCPOptionEncoding_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.

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