VirtualBox

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

Last change on this file since 79810 was 79810, checked in by vboxsync, 5 years ago

Dhcpd: Group conditions. bugref:9288

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 40.6 KB
Line 
1/* $Id: Config.cpp 79810 2019-07-16 14:05:45Z 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/*static*/ uint32_t GroupConfig::s_uGroupNo = 0;
43
44
45/**
46 * Configuration file exception.
47 */
48class ConfigFileError
49 : public RTCError
50{
51public:
52#if 0 /* This just confuses the compiler. */
53 ConfigFileError(const char *a_pszMessage)
54 : RTCError(a_pszMessage)
55 {}
56#endif
57
58 explicit ConfigFileError(xml::Node const *pNode, const char *a_pszMsgFmt, ...)
59 : RTCError((char *)NULL)
60 {
61
62 i_buildPath(pNode);
63 m_strMsg.append(": ");
64
65 va_list va;
66 va_start(va, a_pszMsgFmt);
67 m_strMsg.appendPrintfV(a_pszMsgFmt, va);
68 va_end(va);
69 }
70
71
72 ConfigFileError(const char *a_pszMsgFmt, ...)
73 : RTCError((char *)NULL)
74 {
75 va_list va;
76 va_start(va, a_pszMsgFmt);
77 m_strMsg.printfV(a_pszMsgFmt, va);
78 va_end(va);
79 }
80
81 ConfigFileError(const RTCString &a_rstrMessage)
82 : RTCError(a_rstrMessage)
83 {}
84
85private:
86 void i_buildPath(xml::Node const *pNode)
87 {
88 if (pNode)
89 {
90 i_buildPath(pNode->getParent());
91 m_strMsg.append('/');
92 m_strMsg.append(pNode->getName());
93 if (pNode->isElement() && pNode->getParent())
94 {
95 xml::ElementNode const *pElm = (xml::ElementNode const *)pNode;
96 for (xml::Node const *pAttrib = pElm->getFirstAttribute(); pAttrib != NULL;
97 pAttrib = pAttrib->getNextSibiling())
98 if (pAttrib->isAttribute())
99 {
100 m_strMsg.append("[@");
101 m_strMsg.append(pAttrib->getName());
102 m_strMsg.append('=');
103 m_strMsg.append(pAttrib->getValue());
104 m_strMsg.append(']');
105 }
106 }
107 }
108 }
109
110};
111
112
113/**
114 * Private default constructor, external users use factor methods.
115 */
116Config::Config()
117 : m_strHome()
118 , m_strNetwork()
119 , m_strTrunk()
120 , m_enmTrunkType(kIntNetTrunkType_Invalid)
121 , m_MacAddress()
122 , m_IPv4Address()
123 , m_IPv4Netmask()
124 , m_IPv4PoolFirst()
125 , m_IPv4PoolLast()
126 , m_GlobalConfig()
127 , m_GroupConfigs()
128 , m_HostConfigs()
129{
130}
131
132
133/**
134 * Initializes the object.
135 *
136 * @returns IPRT status code.
137 */
138int Config::i_init() RT_NOEXCEPT
139{
140 return i_homeInit();
141}
142
143
144/**
145 * Initializes the m_strHome member with the path to ~/.VirtualBox or equivalent.
146 *
147 * @returns IPRT status code.
148 * @todo Too many init functions?
149 */
150int Config::i_homeInit() RT_NOEXCEPT
151{
152 char szHome[RTPATH_MAX];
153 int rc = com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome), false);
154 if (RT_SUCCESS(rc))
155 rc = m_strHome.assignNoThrow(szHome);
156 else
157 DHCP_LOG_MSG_ERROR(("unable to locate the VirtualBox home directory: %Rrc\n", rc));
158 return rc;
159}
160
161
162/**
163 * Internal worker for the public factory methods that creates an instance and
164 * calls i_init() on it.
165 *
166 * @returns Config instance on success, NULL on failure.
167 */
168/*static*/ Config *Config::i_createInstanceAndCallInit() RT_NOEXCEPT
169{
170 Config *pConfig;
171 try
172 {
173 pConfig = new Config();
174 }
175 catch (std::bad_alloc &)
176 {
177 return NULL;
178 }
179
180 int rc = pConfig->i_init();
181 if (RT_SUCCESS(rc))
182 return pConfig;
183 delete pConfig;
184 return NULL;
185}
186
187
188/**
189 * Worker for i_complete() that initializes the release log of the process.
190 *
191 * Requires network name to be known as the log file name depends on
192 * it. Alternatively, consider passing the log file name via the
193 * command line?
194 *
195 * @note This is only used when no --log parameter was given.
196 */
197int Config::i_logInit() RT_NOEXCEPT
198{
199 if (g_fInitializedLog)
200 return VINF_SUCCESS;
201
202 if (m_strHome.isEmpty() || m_strNetwork.isEmpty())
203 return VERR_PATH_ZERO_LENGTH;
204
205 /* default log file name */
206 char szLogFile[RTPATH_MAX];
207 ssize_t cch = RTStrPrintf2(szLogFile, sizeof(szLogFile),
208 "%s%c%s-Dhcpd.log",
209 m_strHome.c_str(), RTPATH_DELIMITER, m_strNetwork.c_str());
210 if (cch > 0)
211 {
212 RTPathPurgeFilename(RTPathFilename(szLogFile), RTPATH_STR_F_STYLE_HOST);
213 return i_logInitWithFilename(szLogFile);
214 }
215 return VERR_BUFFER_OVERFLOW;
216}
217
218
219/**
220 * Worker for i_logInit and for handling --log on the command line.
221 *
222 * @returns IPRT status code.
223 * @param pszFilename The log filename.
224 */
225/*static*/ int Config::i_logInitWithFilename(const char *pszFilename) RT_NOEXCEPT
226{
227 AssertReturn(!g_fInitializedLog, VERR_WRONG_ORDER);
228
229 int rc = com::VBoxLogRelCreate("DHCP Server",
230 pszFilename,
231 RTLOGFLAGS_PREFIX_TIME_PROG,
232 "all net_dhcpd.e.l",
233 "VBOXDHCP_RELEASE_LOG",
234 RTLOGDEST_FILE
235#ifdef DEBUG
236 | RTLOGDEST_STDERR
237#endif
238 ,
239 32768 /* cMaxEntriesPerGroup */,
240 5 /* cHistory */,
241 RT_SEC_1DAY /* uHistoryFileTime */,
242 _32M /* uHistoryFileSize */,
243 NULL /* pErrInfo */);
244 if (RT_SUCCESS(rc))
245 g_fInitializedLog = true;
246 else
247 RTMsgError("Log initialization failed: %Rrc, log file '%s'", rc, pszFilename);
248 return rc;
249
250}
251
252
253/**
254 * Post process and validate the configuration after it has been loaded.
255 */
256int Config::i_complete() RT_NOEXCEPT
257{
258 if (m_strNetwork.isEmpty())
259 {
260 LogRel(("network name is not specified\n"));
261 return false;
262 }
263
264 i_logInit();
265
266 bool fMACGenerated = false;
267 if ( m_MacAddress.au16[0] == 0
268 && m_MacAddress.au16[1] == 0
269 && m_MacAddress.au16[2] == 0)
270 {
271 RTUUID Uuid;
272 int rc = RTUuidCreate(&Uuid);
273 AssertRCReturn(rc, rc);
274
275 m_MacAddress.au8[0] = 0x08;
276 m_MacAddress.au8[1] = 0x00;
277 m_MacAddress.au8[2] = 0x27;
278 m_MacAddress.au8[3] = Uuid.Gen.au8Node[3];
279 m_MacAddress.au8[4] = Uuid.Gen.au8Node[4];
280 m_MacAddress.au8[5] = Uuid.Gen.au8Node[5];
281
282 LogRel(("MAC address is not specified: will use generated MAC %RTmac\n", &m_MacAddress));
283 fMACGenerated = true;
284 }
285
286 /* unicast MAC address */
287 if (m_MacAddress.au8[0] & 0x01)
288 {
289 LogRel(("MAC address is not unicast: %RTmac\n", &m_MacAddress));
290 return VERR_GENERAL_FAILURE;
291 }
292
293 /* unicast IP address */
294 if ((m_IPv4Address.au8[0] & 0xe0) == 0xe0)
295 {
296 LogRel(("IP address is not unicast: %RTnaipv4\n", m_IPv4Address.u));
297 return VERR_GENERAL_FAILURE;
298 }
299
300 /* valid netmask */
301 int cPrefixBits;
302 int rc = RTNetMaskToPrefixIPv4(&m_IPv4Netmask, &cPrefixBits);
303 if (RT_FAILURE(rc) || cPrefixBits == 0)
304 {
305 LogRel(("IP mask is not valid: %RTnaipv4\n", m_IPv4Netmask.u));
306 return VERR_GENERAL_FAILURE;
307 }
308
309 /* first IP is from the same network */
310 if ((m_IPv4PoolFirst.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u))
311 {
312 LogRel(("first pool address is outside the network %RTnaipv4/%d: %RTnaipv4\n",
313 (m_IPv4Address.u & m_IPv4Netmask.u), cPrefixBits, m_IPv4PoolFirst.u));
314 return VERR_GENERAL_FAILURE;
315 }
316
317 /* last IP is from the same network */
318 if ((m_IPv4PoolLast.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u))
319 {
320 LogRel(("last pool address is outside the network %RTnaipv4/%d: %RTnaipv4\n",
321 (m_IPv4Address.u & m_IPv4Netmask.u), cPrefixBits, m_IPv4PoolLast.u));
322 return VERR_GENERAL_FAILURE;
323 }
324
325 /* the pool is valid */
326 if (RT_N2H_U32(m_IPv4PoolLast.u) < RT_N2H_U32(m_IPv4PoolFirst.u))
327 {
328 LogRel(("pool range is invalid: %RTnaipv4 - %RTnaipv4\n",
329 m_IPv4PoolFirst.u, m_IPv4PoolLast.u));
330 return VERR_GENERAL_FAILURE;
331 }
332
333 /* our own address is not inside the pool */
334 if ( RT_N2H_U32(m_IPv4PoolFirst.u) <= RT_N2H_U32(m_IPv4Address.u)
335 && RT_N2H_U32(m_IPv4Address.u) <= RT_N2H_U32(m_IPv4PoolLast.u))
336 {
337 LogRel(("server address inside the pool range %RTnaipv4 - %RTnaipv4: %RTnaipv4\n",
338 m_IPv4PoolFirst.u, m_IPv4PoolLast.u, m_IPv4Address.u));
339 return VERR_GENERAL_FAILURE;
340 }
341
342 if (!fMACGenerated)
343 LogRel(("MAC address %RTmac\n", &m_MacAddress));
344 LogRel(("IP address %RTnaipv4/%d\n", m_IPv4Address.u, cPrefixBits));
345 LogRel(("address pool %RTnaipv4 - %RTnaipv4\n", m_IPv4PoolFirst.u, m_IPv4PoolLast.u));
346
347 return VINF_SUCCESS;
348}
349
350
351/**
352 * Parses the command line and loads the configuration.
353 *
354 * @returns The configuration, NULL if we ran into some fatal problem.
355 * @param argc The argc from main().
356 * @param argv The argv from main().
357 */
358Config *Config::create(int argc, char **argv) RT_NOEXCEPT
359{
360 /*
361 * Parse the command line.
362 */
363 static const RTGETOPTDEF s_aOptions[] =
364 {
365 { "--comment", '#', RTGETOPT_REQ_STRING },
366 { "--config", 'c', RTGETOPT_REQ_STRING },
367 { "--log", 'l', RTGETOPT_REQ_STRING },
368 { "--log-destinations", 'd', RTGETOPT_REQ_STRING },
369 { "--log-flags", 'f', RTGETOPT_REQ_STRING },
370 { "--log-group-settings", 'g', RTGETOPT_REQ_STRING },
371 { "--relaxed", 'r', RTGETOPT_REQ_NOTHING },
372 { "--strict", 's', RTGETOPT_REQ_NOTHING },
373 };
374
375 RTGETOPTSTATE State;
376 int rc = RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
377 AssertRCReturn(rc, NULL);
378
379 const char *pszLogFile = NULL;
380 const char *pszLogGroupSettings = NULL;
381 const char *pszLogDestinations = NULL;
382 const char *pszLogFlags = NULL;
383 const char *pszConfig = NULL;
384 const char *pszComment = NULL;
385 bool fStrict = true;
386
387 for (;;)
388 {
389 RTGETOPTUNION ValueUnion;
390 rc = RTGetOpt(&State, &ValueUnion);
391 if (rc == 0) /* done */
392 break;
393
394 switch (rc)
395 {
396 case 'c': /* --config */
397 pszConfig = ValueUnion.psz;
398 break;
399
400 case 'l':
401 pszLogFile = ValueUnion.psz;
402 break;
403
404 case 'd':
405 pszLogDestinations = ValueUnion.psz;
406 break;
407
408 case 'f':
409 pszLogFlags = ValueUnion.psz;
410 break;
411
412 case 'g':
413 pszLogGroupSettings = ValueUnion.psz;
414 break;
415
416 case 'r':
417 fStrict = false;
418 break;
419
420 case 's':
421 fStrict = true;
422 break;
423
424 case '#': /* --comment */
425 /* The sole purpose of this option is to allow identification of DHCP
426 * server instances in the process list. We ignore the required string
427 * argument of this option. */
428 pszComment = ValueUnion.psz;
429 break;
430
431 default:
432 RTGetOptPrintError(rc, &ValueUnion);
433 return NULL;
434 }
435 }
436
437 if (!pszConfig)
438 {
439 RTMsgError("No configuration file specified (--config file)!\n");
440 return NULL;
441 }
442
443 /*
444 * Init the log if a log file was specified.
445 */
446 if (pszLogFile)
447 {
448 rc = i_logInitWithFilename(pszLogFile);
449 if (RT_FAILURE(rc))
450 RTMsgError("Failed to initialize log file '%s': %Rrc", pszLogFile, rc);
451
452 if (pszLogDestinations)
453 RTLogDestinations(RTLogRelGetDefaultInstance(), pszLogDestinations);
454 if (pszLogFlags)
455 RTLogFlags(RTLogRelGetDefaultInstance(), pszLogFlags);
456 if (pszLogGroupSettings)
457 RTLogGroupSettings(RTLogRelGetDefaultInstance(), pszLogGroupSettings);
458
459 LogRel(("--config: %s\n", pszComment));
460 if (pszComment)
461 LogRel(("--comment: %s\n", pszComment));
462 }
463
464 /*
465 * Read the log file.
466 */
467 RTMsgInfo("reading config from '%s'...\n", pszConfig);
468 std::unique_ptr<Config> ptrConfig;
469 ptrConfig.reset(Config::i_read(pszConfig, fStrict));
470 if (ptrConfig.get() != NULL)
471 {
472 rc = ptrConfig->i_complete();
473 if (RT_SUCCESS(rc))
474 return ptrConfig.release();
475 }
476 return NULL;
477}
478
479
480/**
481 *
482 * @note The release log has is not operational when this method is called.
483 */
484Config *Config::i_read(const char *pszFileName, bool fStrict) RT_NOEXCEPT
485{
486 if (pszFileName == NULL || pszFileName[0] == '\0')
487 {
488 DHCP_LOG_MSG_ERROR(("Config::i_read: Empty configuration filename\n"));
489 return NULL;
490 }
491
492 xml::Document doc;
493 try
494 {
495 xml::XmlFileParser parser;
496 parser.read(pszFileName, doc);
497 }
498 catch (const xml::EIPRTFailure &e)
499 {
500 DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
501 return NULL;
502 }
503 catch (const RTCError &e)
504 {
505 DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
506 return NULL;
507 }
508 catch (...)
509 {
510 DHCP_LOG_MSG_ERROR(("Config::i_read: Unknown exception while reading and parsing '%s'\n", pszFileName));
511 return NULL;
512 }
513
514 std::unique_ptr<Config> config(i_createInstanceAndCallInit());
515 AssertReturn(config.get() != NULL, NULL);
516
517 try
518 {
519 config->i_parseConfig(doc.getRootElement(), fStrict);
520 }
521 catch (const RTCError &e)
522 {
523 DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
524 return NULL;
525 }
526 catch (std::bad_alloc &)
527 {
528 DHCP_LOG_MSG_ERROR(("Config::i_read: std::bad_alloc\n"));
529 return NULL;
530 }
531 catch (...)
532 {
533 DHCP_LOG_MSG_ERROR(("Config::i_read: Unexpected exception\n"));
534 return NULL;
535 }
536
537 return config.release();
538}
539
540
541/**
542 * Helper for retrieving a IPv4 attribute.
543 *
544 * @param pElm The element to get the attribute from.
545 * @param pszAttrName The name of the attribute
546 * @param pAddr Where to return the address.
547 * @throws ConfigFileError
548 */
549static void getIPv4AddrAttribute(const xml::ElementNode *pElm, const char *pszAttrName, PRTNETADDRIPV4 pAddr)
550{
551 const char *pszAttrValue;
552 if (pElm->getAttributeValue(pszAttrName, &pszAttrValue))
553 {
554 int rc = RTNetStrToIPv4Addr(pszAttrValue, pAddr);
555 if (RT_SUCCESS(rc))
556 return;
557 throw ConfigFileError(pElm, "Attribute %s is not a valid IPv4 address: '%s' -> %Rrc", pszAttrName, pszAttrValue, rc);
558 }
559 throw ConfigFileError(pElm, "Required %s attribute missing", pszAttrName);
560}
561
562
563/**
564 * Helper for retrieving a MAC address attribute.
565 *
566 * @param pElm The element to get the attribute from.
567 * @param pszAttrName The name of the attribute
568 * @param pMacAddr Where to return the MAC address.
569 * @throws ConfigFileError
570 */
571static void getMacAddressAttribute(const xml::ElementNode *pElm, const char *pszAttrName, PRTMAC pMacAddr)
572{
573 const char *pszAttrValue;
574 if (pElm->getAttributeValue(pszAttrName, &pszAttrValue))
575 {
576 int rc = RTNetStrToMacAddr(pszAttrValue, pMacAddr);
577 if (RT_SUCCESS(rc) && rc != VWRN_TRAILING_CHARS)
578 return;
579 throw ConfigFileError(pElm, "attribute %s is not a valid MAC address: '%s' -> %Rrc", pszAttrName, pszAttrValue, rc);
580 }
581 throw ConfigFileError(pElm, "Required %s attribute missing", pszAttrName);
582}
583
584
585/**
586 * Internal worker for i_read() that parses the root element and everything
587 * below it.
588 *
589 * @param pElmRoot The root element.
590 * @param fStrict Set if we're in strict mode, clear if we just
591 * want to get on with it if we can.
592 * @throws std::bad_alloc, ConfigFileError
593 */
594void Config::i_parseConfig(const xml::ElementNode *pElmRoot, bool fStrict)
595{
596 /*
597 * Check the root element and call i_parseServer to do real work.
598 */
599 if (pElmRoot == NULL)
600 throw ConfigFileError("Empty config file");
601
602 /** @todo XXX: NAMESPACE API IS COMPLETELY BROKEN, SO IGNORE IT FOR NOW */
603
604 if (!pElmRoot->nameEquals("DHCPServer"))
605 throw ConfigFileError("Unexpected root element '%s'", pElmRoot->getName());
606
607 i_parseServer(pElmRoot, fStrict);
608
609#if 0 /** @todo convert to LogRel2 stuff */
610 // XXX: debug
611 for (optmap_t::const_iterator it = m_GlobalOptions.begin(); it != m_GlobalOptions.end(); ++it) {
612 std::shared_ptr<DhcpOption> opt(it->second);
613
614 octets_t data;
615 opt->encode(data);
616
617 bool space = false;
618 for (octets_t::const_iterator itData = data.begin(); itData != data.end(); ++itData) {
619 uint8_t c = *itData;
620 if (space)
621 std::cout << " ";
622 else
623 space = true;
624 std::cout << (int)c;
625 }
626 std::cout << std::endl;
627 }
628#endif
629}
630
631
632/**
633 * Internal worker for parsing the elements under /DHCPServer/.
634 *
635 * @param pElmServer The DHCPServer element.
636 * @param fStrict Set if we're in strict mode, clear if we just
637 * want to get on with it if we can.
638 * @throws std::bad_alloc, ConfigFileError
639 */
640void Config::i_parseServer(const xml::ElementNode *pElmServer, bool fStrict)
641{
642 /*
643 * <DHCPServer> attributes
644 */
645 if (!pElmServer->getAttributeValue("networkName", m_strNetwork))
646 throw ConfigFileError("DHCPServer/@networkName missing");
647 if (m_strNetwork.isEmpty())
648 throw ConfigFileError("DHCPServer/@networkName is empty");
649
650 const char *pszTrunkType;
651 if (!pElmServer->getAttributeValue("trunkType", &pszTrunkType))
652 throw ConfigFileError("DHCPServer/@trunkType missing");
653 if (strcmp(pszTrunkType, "none") == 0)
654 m_enmTrunkType = kIntNetTrunkType_None;
655 else if (strcmp(pszTrunkType, "whatever") == 0)
656 m_enmTrunkType = kIntNetTrunkType_WhateverNone;
657 else if (strcmp(pszTrunkType, "netflt") == 0)
658 m_enmTrunkType = kIntNetTrunkType_NetFlt;
659 else if (strcmp(pszTrunkType, "netadp") == 0)
660 m_enmTrunkType = kIntNetTrunkType_NetAdp;
661 else
662 throw ConfigFileError("Invalid DHCPServer/@trunkType value: %s", pszTrunkType);
663
664 if ( m_enmTrunkType == kIntNetTrunkType_NetFlt
665 || m_enmTrunkType == kIntNetTrunkType_NetAdp)
666 {
667 if (!pElmServer->getAttributeValue("trunkName", &m_strTrunk))
668 throw ConfigFileError("DHCPServer/@trunkName missing");
669 }
670 else
671 m_strTrunk = "";
672
673 m_strLeasesFilename = pElmServer->findAttributeValue("leasesFilename"); /* optional */
674 if (m_strLeasesFilename.isEmpty())
675 {
676 int rc = m_strLeasesFilename.assignNoThrow(getHome());
677 if (RT_SUCCESS(rc))
678 rc = RTPathAppendCxx(m_strLeasesFilename, m_strNetwork);
679 if (RT_SUCCESS(rc))
680 rc = m_strLeasesFilename.appendNoThrow("-Dhcpd.leases");
681 if (RT_FAILURE(rc))
682 throw ConfigFileError("Unexpected error constructing default m_strLeasesFilename value: %Rrc", rc);
683 RTPathPurgeFilename(RTPathFilename(m_strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST);
684 m_strLeasesFilename.jolt();
685 }
686
687 ::getIPv4AddrAttribute(pElmServer, "IPAddress", &m_IPv4Address);
688 ::getIPv4AddrAttribute(pElmServer, "networkMask", &m_IPv4Netmask);
689 ::getIPv4AddrAttribute(pElmServer, "lowerIP", &m_IPv4PoolFirst);
690 ::getIPv4AddrAttribute(pElmServer, "upperIP", &m_IPv4PoolLast);
691
692 /*
693 * <DHCPServer> children
694 */
695 xml::NodesLoop it(*pElmServer);
696 const xml::ElementNode *pElmChild;
697 while ((pElmChild = it.forAllNodes()) != NULL)
698 {
699 /* Global options: */
700 if (pElmChild->nameEquals("Options"))
701 m_GlobalConfig.initFromXml(pElmChild, fStrict);
702 /* Group w/ options: */
703 else if (pElmChild->nameEquals("Group"))
704 {
705 std::unique_ptr<GroupConfig> ptrGroup(new GroupConfig());
706 ptrGroup->initFromXml(pElmChild, fStrict);
707 if (m_GroupConfigs.find(ptrGroup->getGroupName()) == m_GroupConfigs.end())
708 {
709 m_GroupConfigs[ptrGroup->getGroupName()] = ptrGroup.get();
710 ptrGroup.release();
711 }
712 else if (!fStrict)
713 LogRelFunc(("Ignoring duplicate group name: %s", ptrGroup->getGroupName().c_str()));
714 else
715 throw ConfigFileError("Duplicate group name: %s", ptrGroup->getGroupName().c_str());
716 }
717 /*
718 * MAC address and per VM NIC configurations:
719 */
720 else if (pElmChild->nameEquals("Config"))
721 {
722 std::unique_ptr<HostConfig> ptrHost(new HostConfig());
723 ptrHost->initFromXml(pElmChild, fStrict);
724 if (m_HostConfigs.find(ptrHost->getMACAddress()) == m_HostConfigs.end())
725 {
726 m_HostConfigs[ptrHost->getMACAddress()] = ptrHost.get();
727 ptrHost.release();
728 }
729 else if (!fStrict)
730 LogRelFunc(("Ignorining duplicate MAC address (Config): %RTmac", &ptrHost->getMACAddress()));
731 else
732 throw ConfigFileError("Duplicate MAC address (Config): %RTmac", &ptrHost->getMACAddress());
733 }
734 else if (!fStrict)
735 LogRel(("Ignoring unexpected DHCPServer child: %s\n", pElmChild->getName()));
736 else
737 throw ConfigFileError("Unexpected DHCPServer child <%s>'", pElmChild->getName());
738 }
739}
740
741
742/**
743 * Internal worker for parsing \<Option\> elements found under
744 * /DHCPServer/Options/, /DHCPServer/Group/ and /DHCPServer/Config/.
745 *
746 * @param pElmOption An \<Option\> element.
747 * @throws std::bad_alloc, ConfigFileError
748 */
749void ConfigLevelBase::i_parseOption(const xml::ElementNode *pElmOption)
750{
751 /* The 'name' attribute: */
752 const char *pszName;
753 if (!pElmOption->getAttributeValue("name", &pszName))
754 throw ConfigFileError(pElmOption, "missing option name");
755
756 uint8_t u8Opt;
757 int rc = RTStrToUInt8Full(pszName, 10, &u8Opt);
758 if (rc != VINF_SUCCESS) /* no warnings either */
759 throw ConfigFileError(pElmOption, "Bad option name '%s': %Rrc", pszName, rc);
760
761 /* The opional 'encoding' attribute: */
762 uint32_t u32Enc = 0; /* XXX: DHCPOptionEncoding_Normal */
763 const char *pszEncoding;
764 if (pElmOption->getAttributeValue("encoding", &pszEncoding))
765 {
766 rc = RTStrToUInt32Full(pszEncoding, 10, &u32Enc);
767 if (rc != VINF_SUCCESS) /* no warnings either */
768 throw ConfigFileError(pElmOption, "Bad option encoding '%s': %Rrc", pszEncoding, rc);
769
770 switch (u32Enc)
771 {
772 case 0: /* XXX: DHCPOptionEncoding_Normal */
773 case 1: /* XXX: DHCPOptionEncoding_Hex */
774 break;
775 default:
776 throw ConfigFileError(pElmOption, "Unknown encoding '%s'", pszEncoding);
777 }
778 }
779
780 /* The 'value' attribute. May be omitted for OptNoValue options like rapid commit. */
781 const char *pszValue;
782 if (!pElmOption->getAttributeValue("value", &pszValue))
783 pszValue = "";
784
785 /** @todo XXX: TODO: encoding, handle hex */
786 DhcpOption *opt = DhcpOption::parse(u8Opt, u32Enc, pszValue);
787 if (opt == NULL)
788 throw ConfigFileError(pElmOption, "Bad option '%s' (encoding %u): '%s' ", pszName, u32Enc, pszValue ? pszValue : "");
789
790 /* Add it to the map: */
791 m_Options << opt;
792}
793
794
795/**
796 * Final children parser, handling only \<Option\> and barfing at anything else.
797 *
798 * @param pElmChild The child element to handle.
799 * @param fStrict Set if we're in strict mode, clear if we just
800 * want to get on with it if we can.
801 * @throws std::bad_alloc, ConfigFileError
802 */
803void ConfigLevelBase::i_parseChild(const xml::ElementNode *pElmChild, bool fStrict)
804{
805 if (pElmChild->nameEquals("Option"))
806 {
807 try
808 {
809 i_parseOption(pElmChild);
810 }
811 catch (ConfigFileError &rXcpt)
812 {
813 if (fStrict)
814 throw rXcpt;
815 LogRelFunc(("Ignoring option: %s\n", rXcpt.what()));
816 }
817 }
818 else if (!fStrict)
819 {
820 ConfigFileError Dummy(pElmChild->getParent(), "Unexpected child '%s'", pElmChild->getName());
821 LogRelFunc(("%s\n", Dummy.what()));
822 }
823 else
824 throw ConfigFileError(pElmChild->getParent(), "Unexpected child '%s'", pElmChild->getName());
825}
826
827
828/**
829 * Base class initialization taking a /DHCPServer/Options, /DHCPServer/Group or
830 * /DHCPServer/Config element as input and handling common attributes as well as
831 * any \<Option\> children.
832 *
833 * @param pElmConfig The configuration element to parse.
834 * @param fStrict Set if we're in strict mode, clear if we just
835 * want to get on with it if we can.
836 * @throws std::bad_alloc, ConfigFileError
837 */
838void ConfigLevelBase::initFromXml(const xml::ElementNode *pElmConfig, bool fStrict)
839{
840 /*
841 * Common attributes:
842 */
843 if (!pElmConfig->getAttributeValue("secMinLeaseTime", &m_secMinLeaseTime))
844 m_secMinLeaseTime = 0;
845 if (!pElmConfig->getAttributeValue("secDefaultLeaseTime", &m_secDefaultLeaseTime))
846 m_secDefaultLeaseTime = 0;
847 if (!pElmConfig->getAttributeValue("secMaxLeaseTime", &m_secMaxLeaseTime))
848 m_secMaxLeaseTime = 0;
849
850 /*
851 * Parse children.
852 */
853 xml::NodesLoop it(*pElmConfig);
854 const xml::ElementNode *pElmChild;
855 while ((pElmChild = it.forAllNodes()) != NULL)
856 i_parseChild(pElmChild, fStrict);
857}
858
859
860/**
861 * Internal worker for parsing the elements under /DHCPServer/Options/.
862 *
863 * @param pElmOptions The <Options> element.
864 * @param fStrict Set if we're in strict mode, clear if we just
865 * want to get on with it if we can.
866 * @throws std::bad_alloc, ConfigFileError
867 */
868void GlobalConfig::initFromXml(const xml::ElementNode *pElmOptions, bool fStrict)
869{
870 ConfigLevelBase::initFromXml(pElmOptions, fStrict);
871}
872
873
874/**
875 * Overrides base class to handle the condition elements under \<Group\>.
876 *
877 * @param pElmChild The child element.
878 * @param fStrict Set if we're in strict mode, clear if we just
879 * want to get on with it if we can.
880 * @throws std::bad_alloc, ConfigFileError
881 */
882void GroupConfig::i_parseChild(const xml::ElementNode *pElmChild, bool fStrict)
883{
884 /*
885 * Match the condition
886 */
887 std::unique_ptr<GroupCondition> ptrCondition;
888 if (pElmChild->nameEquals("ConditionMAC"))
889 ptrCondition.reset(new GroupConditionMAC());
890 else if (pElmChild->nameEquals("ConditionMACWildcard"))
891 ptrCondition.reset(new GroupConditionMACWildcard());
892 else if (pElmChild->nameEquals("ConditionVendorClassID"))
893 ptrCondition.reset(new GroupConditionVendorClassID());
894 else if (pElmChild->nameEquals("ConditionVendorClassIDWildcard"))
895 ptrCondition.reset(new GroupConditionVendorClassIDWildcard());
896 else if (pElmChild->nameEquals("ConditionUserClassID"))
897 ptrCondition.reset(new GroupConditionUserClassID());
898 else if (pElmChild->nameEquals("ConditionUserClassIDWildcard"))
899 ptrCondition.reset(new GroupConditionUserClassIDWildcard());
900 else
901 {
902 /*
903 * Not a condition, pass it on to the base class.
904 */
905 ConfigLevelBase::i_parseChild(pElmChild, fStrict);
906 return;
907 }
908
909 /*
910 * Get the attributes and initialize the condition.
911 */
912 bool fInclusive;
913 if (!pElmChild->getAttributeValue("inclusive", fInclusive))
914 fInclusive = true;
915 const char *pszValue = pElmChild->findAttributeValue("value");
916 if (pszValue && *pszValue)
917 {
918 int rc = ptrCondition->initCondition(pszValue, fInclusive);
919 if (RT_SUCCESS(rc))
920 {
921 /*
922 * Add it to the appropriate vector.
923 */
924 if (fInclusive)
925 m_Inclusive.push_back(ptrCondition.release());
926 else
927 m_Exclusive.push_back(ptrCondition.release());
928 }
929 else
930 {
931 ConfigFileError Xcpt(pElmChild, "initCondition failed with %Rrc for '%s' and %RTbool", rc, pszValue, fInclusive);
932 if (!fStrict)
933 LogRelFunc(("%s, ignoring condition\n", Xcpt.what()));
934 else
935 throw ConfigFileError(Xcpt);
936 }
937 }
938 else
939 {
940 ConfigFileError Xcpt(pElmChild, "condition value is empty or missing (inclusive=%RTbool)", fInclusive);
941 if (!fStrict)
942 LogRelFunc(("%s, ignoring condition\n", Xcpt.what()));
943 else
944 throw Xcpt;
945 }
946}
947
948
949/**
950 * Internal worker for parsing the elements under /DHCPServer/Group/.
951 *
952 * @param pElmGroup The \<Group\> element.
953 * @param fStrict Set if we're in strict mode, clear if we just
954 * want to get on with it if we can.
955 * @throws std::bad_alloc, ConfigFileError
956 */
957void GroupConfig::initFromXml(const xml::ElementNode *pElmGroup, bool fStrict)
958{
959 /*
960 * Attributes:
961 */
962 if (!pElmGroup->getAttributeValue("name", m_strName) || m_strName.isEmpty())
963 {
964 if (fStrict)
965 throw ConfigFileError(pElmGroup, "Group as no name or the name is empty");
966 m_strName.printf("Group#%u", s_uGroupNo++);
967 }
968
969 /*
970 * Do common initialization (including children).
971 */
972 ConfigLevelBase::initFromXml(pElmGroup, fStrict);
973}
974
975
976/**
977 * Internal worker for parsing the elements under /DHCPServer/Config/.
978 *
979 * VM Config entries are generated automatically from VirtualBox.xml
980 * with the MAC fetched from the VM config. The client id is nowhere
981 * in the picture there, so VM config is indexed with plain RTMAC, not
982 * ClientId (also see getOptions below).
983 *
984 * @param pElmConfig The \<Config\> element.
985 * @param fStrict Set if we're in strict mode, clear if we just
986 * want to get on with it if we can.
987 * @throws std::bad_alloc, ConfigFileError
988 */
989void HostConfig::initFromXml(const xml::ElementNode *pElmConfig, bool fStrict)
990{
991 /*
992 * Attributes:
993 */
994 /* The MAC address: */
995 ::getMacAddressAttribute(pElmConfig, "MACAddress", &m_MACAddress);
996
997 /* Name - optional: */
998 if (!pElmConfig->getAttributeValue("name", m_strName))
999 m_strName.printf("MAC:%RTmac", m_MACAddress);
1000
1001 /* Fixed IP address assignment - optional: */
1002 const char *pszFixedAddress = pElmConfig->findAttributeValue("FixedIPAddress");
1003 if (!pszFixedAddress || *RTStrStripL(pszFixedAddress) == '\0')
1004 m_fHaveFixedAddress = false;
1005 else
1006 {
1007 m_fHaveFixedAddress = false;
1008 ::getIPv4AddrAttribute(pElmConfig, "FixedIPAddress", &m_FixedAddress);
1009 }
1010
1011 /*
1012 * Do common initialization.
1013 */
1014 ConfigLevelBase::initFromXml(pElmConfig, fStrict);
1015}
1016
1017
1018/**
1019 * Assembles a priorities vector of configurations for the client.
1020 *
1021 * @returns a_rRetConfigs for convenience.
1022 * @param a_rRetConfigs Where to return the configurations.
1023 * @param a_ridClient The client ID.
1024 * @param a_ridVendorClass The vendor class ID if present.
1025 * @param a_ridUserClass The user class ID if present
1026 */
1027Config::ConfigVec &Config::getConfigsForClient(Config::ConfigVec &a_rRetConfigs, const ClientId &a_ridClient,
1028 const OptVendorClassId &a_ridVendorClass,
1029 const OptUserClassId &a_ridUserClass) const
1030{
1031 /* Host specific config first: */
1032 HostConfigMap::const_iterator itHost = m_HostConfigs.find(a_ridClient.mac());
1033 if (itHost != m_HostConfigs.end())
1034 a_rRetConfigs.push_back(itHost->second);
1035
1036 /* Groups: */
1037 for (GroupConfigMap::const_iterator itGrp = m_GroupConfigs.begin(); itGrp != m_GroupConfigs.end(); ++itGrp)
1038 if (itGrp->second->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
1039 a_rRetConfigs.push_back(itGrp->second);
1040
1041 /* Global: */
1042 a_rRetConfigs.push_back(&m_GlobalConfig);
1043
1044 return a_rRetConfigs;
1045}
1046
1047
1048/**
1049 * Method used by DHCPD to assemble a list of options for the client.
1050 *
1051 * @returns a_rRetOpts for convenience
1052 * @param a_rRetOpts Where to put the requested options.
1053 * @param a_rReqOpts The requested options.
1054 * @param a_rConfigs Relevant configurations returned by
1055 * Config::getConfigsForClient().
1056 *
1057 * @throws std::bad_alloc
1058 */
1059optmap_t &Config::getOptionsForClient(optmap_t &a_rRetOpts, const OptParameterRequest &a_rReqOpts, ConfigVec &a_rConfigs) const
1060{
1061 /*
1062 * Always supply the subnet:
1063 */
1064 a_rRetOpts << new OptSubnetMask(m_IPv4Netmask);
1065
1066 /*
1067 * Try provide the requested options:
1068 */
1069 const OptParameterRequest::value_t &reqValue = a_rReqOpts.value();
1070 for (octets_t::const_iterator itOptReq = reqValue.begin(); itOptReq != reqValue.end(); ++itOptReq)
1071 {
1072 uint8_t bOptReq = *itOptReq;
1073 LogRel2((">>> requested option %d (%#x)\n", bOptReq, bOptReq));
1074
1075 if (bOptReq != OptSubnetMask::optcode)
1076 {
1077 bool fFound = false;
1078 for (size_t i = 0; i < a_rConfigs.size(); i++)
1079 {
1080 optmap_t::const_iterator itFound;
1081 if (a_rConfigs[i]->findOption(bOptReq, itFound)) /* crap interface */
1082 {
1083 LogRel2(("... found in %s (type %s)\n", a_rConfigs[i]->getName(), a_rConfigs[i]->getType()));
1084 a_rRetOpts << itFound->second;
1085 fFound = true;
1086 break;
1087 }
1088 }
1089 if (!fFound)
1090 LogRel3(("... not found\n"));
1091 }
1092 else
1093 LogRel2(("... always supplied\n"));
1094 }
1095
1096
1097#if 0 /* bird disabled this as it looks dubious and testing only. */
1098 /** @todo XXX: testing ... */
1099 if (vmopts != NULL)
1100 {
1101 for (optmap_t::const_iterator it = vmopts->begin(); it != vmopts->end(); ++it)
1102 {
1103 std::shared_ptr<DhcpOption> opt(it->second);
1104 if (a_rRetOpts.count(opt->optcode()) == 0 && opt->optcode() > 127)
1105 {
1106 a_rRetOpts << opt;
1107 LogRel2(("... forcing VM option %d (%#x)\n", opt->optcode(), opt->optcode()));
1108 }
1109 }
1110 }
1111
1112 for (optmap_t::const_iterator it = m_GlobalOptions.begin(); it != m_GlobalOptions.end(); ++it)
1113 {
1114 std::shared_ptr<DhcpOption> opt(it->second);
1115 if (a_rRetOpts.count(opt->optcode()) == 0 && opt->optcode() > 127)
1116 {
1117 a_rRetOpts << opt;
1118 LogRel2(("... forcing global option %d (%#x)", opt->optcode(), opt->optcode()));
1119 }
1120 }
1121#endif
1122
1123 return a_rRetOpts;
1124}
1125
1126
1127
1128/*********************************************************************************************************************************
1129* Group Condition Matching *
1130*********************************************************************************************************************************/
1131
1132bool GroupConfig::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1133 const OptUserClassId &a_ridUserClass) const
1134{
1135 /*
1136 * Check the inclusive ones first, only one need to match.
1137 */
1138 for (GroupConditionVec::const_iterator itIncl = m_Inclusive.begin(); itIncl != m_Inclusive.end(); ++itIncl)
1139 if ((*itIncl)->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
1140 {
1141 /*
1142 * Now make sure it isn't excluded by any of the exclusion condition.
1143 */
1144 for (GroupConditionVec::const_iterator itExcl = m_Exclusive.begin(); itExcl != m_Exclusive.end(); ++itExcl)
1145 if ((*itIncl)->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
1146 return false;
1147 return true;
1148 }
1149
1150 return false;
1151}
1152
1153
1154int GroupCondition::initCondition(const char *a_pszValue, bool a_fInclusive)
1155{
1156 m_fInclusive = a_fInclusive;
1157 return m_strValue.assignNoThrow(a_pszValue);
1158}
1159
1160
1161bool GroupCondition::matchClassId(bool a_fPresent, const std::vector<uint8_t> &a_rBytes, bool fWildcard) const RT_NOEXCEPT
1162{
1163 if (a_fPresent)
1164 {
1165 size_t const cbBytes = a_rBytes.size();
1166 if (cbBytes > 0)
1167 {
1168 if (a_rBytes[cbBytes - 1] == '\0')
1169 {
1170 uint8_t const *pb = &a_rBytes.front();
1171 if (!fWildcard)
1172 return m_strValue.equals((const char *)pb);
1173 return RTStrSimplePatternMatch(m_strValue.c_str(), (const char *)pb);
1174 }
1175
1176 if (cbBytes <= 255)
1177 {
1178 char szTmp[256];
1179 memcpy(szTmp, &a_rBytes.front(), cbBytes);
1180 szTmp[cbBytes] = '\0';
1181 if (!fWildcard)
1182 return m_strValue.equals(szTmp);
1183 return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp);
1184 }
1185 }
1186 }
1187 return false;
1188
1189}
1190
1191
1192int GroupConditionMAC::initCondition(const char *a_pszValue, bool a_fInclusive)
1193{
1194 int vrc = RTNetStrToMacAddr(a_pszValue, &m_MACAddress);
1195 if (RT_SUCCESS(vrc))
1196 return GroupCondition::initCondition(a_pszValue, a_fInclusive);
1197 return vrc;
1198}
1199
1200
1201bool GroupConditionMAC::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1202 const OptUserClassId &a_ridUserClass) const
1203{
1204 RT_NOREF(a_ridVendorClass, a_ridUserClass);
1205 return a_ridClient.mac() == m_MACAddress;
1206}
1207
1208
1209bool GroupConditionMACWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1210 const OptUserClassId &a_ridUserClass) const
1211{
1212 RT_NOREF(a_ridVendorClass, a_ridUserClass);
1213 char szTmp[32];
1214 RTStrPrintf(szTmp, sizeof(szTmp), "%RTmac", &a_ridClient.mac());
1215 return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp);
1216}
1217
1218
1219bool GroupConditionVendorClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1220 const OptUserClassId &a_ridUserClass) const
1221{
1222 RT_NOREF(a_ridClient, a_ridUserClass);
1223 return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value());
1224}
1225
1226
1227bool GroupConditionVendorClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1228 const OptUserClassId &a_ridUserClass) const
1229{
1230 RT_NOREF(a_ridClient, a_ridUserClass);
1231 return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value(), true /*fWildcard*/);
1232}
1233
1234
1235bool GroupConditionUserClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1236 const OptUserClassId &a_ridUserClass) const
1237{
1238 RT_NOREF(a_ridClient, a_ridVendorClass);
1239 return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value());
1240}
1241
1242
1243bool GroupConditionUserClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1244 const OptUserClassId &a_ridUserClass) const
1245{
1246 RT_NOREF(a_ridClient, a_ridVendorClass);
1247 return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value(), true /*fWildcard*/);
1248}
1249
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