VirtualBox

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

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

Dhcpd: Went over the Config code adding comments, eliminating unnecessary copying (xml string getting, ++) and such. bugref:9288

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