VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/Dhcpd/Db.cpp@ 95259

Last change on this file since 95259 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 31.3 KB
Line 
1/* $Id: Db.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * DHCP server - address database
4 */
5
6/*
7 * Copyright (C) 2017-2022 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#include <iprt/errcore.h>
24
25#include "Db.h"
26
27
28/*********************************************************************************************************************************
29* Global Variables *
30*********************************************************************************************************************************/
31/** Indicates whether has been called successfully yet. */
32bool Binding::g_fFormatRegistered = false;
33
34
35/**
36 * Registers the ClientId format type callback ("%R[binding]").
37 */
38void Binding::registerFormat() RT_NOEXCEPT
39{
40 if (!g_fFormatRegistered)
41 {
42 int rc = RTStrFormatTypeRegister("binding", rtStrFormat, NULL);
43 AssertRC(rc);
44 g_fFormatRegistered = true;
45 }
46}
47
48
49/**
50 * @callback_method_impl{FNRTSTRFORMATTYPE, Formats ClientId via "%R[binding]".}
51 */
52DECLCALLBACK(size_t)
53Binding::rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
54 const char *pszType, void const *pvValue,
55 int cchWidth, int cchPrecision, unsigned fFlags,
56 void *pvUser)
57{
58
59 AssertReturn(strcmp(pszType, "binding") == 0, 0);
60 RT_NOREF(pszType);
61
62 RT_NOREF(cchWidth, cchPrecision, fFlags);
63 RT_NOREF(pvUser);
64
65 const Binding *b = static_cast<const Binding *>(pvValue);
66 if (b == NULL)
67 return pfnOutput(pvArgOutput, RT_STR_TUPLE("<NULL>"));
68
69 size_t cb = RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%RTnaipv4", b->m_addr.u);
70 if (b->m_state == Binding::FREE)
71 cb += pfnOutput(pvArgOutput, RT_STR_TUPLE(" free"));
72 else if (b->m_fFixed)
73 cb += pfnOutput(pvArgOutput, RT_STR_TUPLE(" fixed"));
74 else
75 {
76 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, " to %R[id], %s, valid from ", &b->m_id, b->stateName());
77
78 Timestamp tsIssued = b->issued();
79 cb += tsIssued.strFormatHelper(pfnOutput, pvArgOutput);
80
81 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, " for %ds until ", b->leaseTime());
82
83 Timestamp tsValid = b->issued();
84 tsValid.addSeconds(b->leaseTime());
85 cb += tsValid.strFormatHelper(pfnOutput, pvArgOutput);
86 }
87
88 return cb;
89}
90
91
92/**
93 * Used to update the client ID of a fixed address assignment.
94 *
95 * We only have the MAC address when prepraring the binding, so the full client
96 * ID must be supplied when the client requests it.
97 *
98 * @param a_ridClient The client ID.
99 * @throws std::bad_alloc
100 */
101void Binding::idUpdate(const ClientId &a_ridClient)
102{
103 AssertReturnVoid(isFixed());
104 m_id = a_ridClient;
105}
106
107
108/**
109 * Get the state as a string for the XML lease database.
110 */
111const char *Binding::stateName() const RT_NOEXCEPT
112{
113 switch (m_state)
114 {
115 case FREE:
116 return "free";
117 case RELEASED:
118 return "released";
119 case EXPIRED:
120 return "expired";
121 case OFFERED:
122 return "offered";
123 case ACKED:
124 return "acked";
125 default:
126 AssertMsgFailed(("%d\n", m_state));
127 return "released";
128 }
129}
130
131
132/**
133 * Sets the state by name (reverse of Binding::stateName()).
134 */
135Binding &Binding::setState(const char *pszStateName) RT_NOEXCEPT
136{
137 if (strcmp(pszStateName, "free") == 0)
138 m_state = Binding::FREE;
139 else if (strcmp(pszStateName, "released") == 0)
140 m_state = Binding::RELEASED;
141 else if (strcmp(pszStateName, "expired") == 0)
142 m_state = Binding::EXPIRED;
143 else if (strcmp(pszStateName, "offered") == 0)
144 m_state = Binding::OFFERED;
145 else if (strcmp(pszStateName, "acked") == 0)
146 m_state = Binding::ACKED;
147 else
148 {
149 AssertMsgFailed(("%d\n", m_state));
150 m_state = Binding::RELEASED;
151 }
152
153 return *this;
154}
155
156
157/**
158 * Expires the binding if it's past the specified deadline.
159 *
160 * @returns False if already expired, released or freed, otherwise true (i.e.
161 * does not indicate whether action was taken or not).
162 * @param tsDeadline The expiry deadline to use.
163 */
164bool Binding::expire(Timestamp tsDeadline) RT_NOEXCEPT
165{
166 if (m_state <= Binding::EXPIRED || m_fFixed)
167 return false;
168
169 Timestamp tsExpire = m_issued;
170 tsExpire.addSeconds(m_secLease);
171
172 if (tsExpire < tsDeadline)
173 {
174 if (m_state == Binding::OFFERED)
175 setState(Binding::FREE);
176 else
177 setState(Binding::EXPIRED);
178 }
179 return true;
180}
181
182
183/**
184 * Serializes the binding to XML for the lease database.
185 *
186 * @throw std::bad_alloc
187 * @note DHCPServerImpl.cpp contains a reader, keep it in sync.
188 */
189void Binding::toXML(xml::ElementNode *pElmParent) const
190{
191 /*
192 * Lease
193 */
194 xml::ElementNode *pElmLease = pElmParent->createChild("Lease");
195
196 pElmLease->setAttribute("mac", RTCStringFmt("%RTmac", &m_id.mac()));
197 if (m_id.id().present())
198 {
199 /* I'd prefer RTSTRPRINTHEXBYTES_F_SEP_COLON but there's no decoder */
200 size_t cbStrId = m_id.id().value().size() * 2 + 1;
201 char *pszId = new char[cbStrId];
202 int rc = RTStrPrintHexBytes(pszId, cbStrId,
203 &m_id.id().value().front(), m_id.id().value().size(),
204 0);
205 AssertRC(rc);
206 pElmLease->setAttribute("id", pszId);
207 delete[] pszId;
208 }
209
210 /* unused but we need it to keep the old code happy */
211 pElmLease->setAttribute("network", "0.0.0.0");
212 pElmLease->setAttribute("state", stateName());
213
214 /*
215 * Lease/Address
216 */
217 xml::ElementNode *pElmAddr = pElmLease->createChild("Address");
218 pElmAddr->setAttribute("value", RTCStringFmt("%RTnaipv4", m_addr.u));
219
220 /*
221 * Lease/Time
222 */
223 xml::ElementNode *pElmTime = pElmLease->createChild("Time");
224 pElmTime->setAttribute("issued", m_issued.getAbsSeconds());
225 pElmTime->setAttribute("expiration", m_secLease);
226}
227
228
229/**
230 * Deserializes the binding from the XML lease database.
231 *
232 * @param pElmLease The "Lease" element to serialize into.
233 * @return Pointer to the resulting binding, NULL on failure.
234 * @throw std::bad_alloc
235 * @note DHCPServerImpl.cpp contains a similar reader, keep it in sync.
236 */
237Binding *Binding::fromXML(const xml::ElementNode *pElmLease)
238{
239 /* Note! Lease/@network seems to always have bogus value, ignore it. */
240 /* Note! We parse the mandatory attributes and elements first, then
241 the optional ones. This means things appear a little jumbled. */
242
243 /*
244 * Lease/@mac - mandatory.
245 */
246 const char *pszMacAddress = pElmLease->findAttributeValue("mac");
247 if (!pszMacAddress)
248 DHCP_LOG_RET_NULL(("Binding::fromXML: <Lease> element without 'mac' attribute! Skipping lease.\n"));
249
250 RTMAC mac;
251 int rc = RTNetStrToMacAddr(pszMacAddress, &mac);
252 if (RT_FAILURE(rc))
253 DHCP_LOG_RET_NULL(("Binding::fromXML: Malformed mac address attribute value '%s': %Rrc - Skipping lease.\n",
254 pszMacAddress, rc));
255
256 /*
257 * Lease/Address/@value - mandatory.
258 */
259 const char *pszAddress = pElmLease->findChildElementAttributeValue("Address", "value");
260 if (!pszAddress)
261 DHCP_LOG_RET_NULL(("Binding::fromXML: Could not find <Address> with a 'value' attribute! Skipping lease.\n"));
262
263 RTNETADDRIPV4 addr;
264 rc = RTNetStrToIPv4Addr(pszAddress, &addr);
265 if (RT_FAILURE(rc))
266 DHCP_LOG_RET_NULL(("Binding::fromXML: Malformed IPv4 address value '%s': %Rrc - Skipping lease.\n", pszAddress, rc));
267
268 /*
269 * Lease/Time - mandatory.
270 */
271 const xml::ElementNode *pElmTime = pElmLease->findChildElement("Time");
272 if (pElmTime == NULL)
273 DHCP_LOG_RET_NULL(("Binding::fromXML: No <Time> element under <Lease mac=%RTmac>! Skipping lease.\n", &mac));
274
275 /*
276 * Lease/Time/@issued - mandatory.
277 */
278 int64_t secIssued;
279 if (!pElmTime->getAttributeValue("issued", &secIssued))
280 DHCP_LOG_RET_NULL(("Binding::fromXML: <Time> element for %RTmac has no valid 'issued' attribute! Skipping lease.\n", &mac));
281
282 /*
283 * Lease/Time/@expiration - mandatory.
284 */
285 uint32_t cSecToLive;
286 if (!pElmTime->getAttributeValue("expiration", &cSecToLive))
287 DHCP_LOG_RET_NULL(("Binding::fromXML: <Time> element for %RTmac has no valid 'expiration' attribute! Skipping lease.\n", &mac));
288
289 std::unique_ptr<Binding> b(new Binding(addr));
290
291 /*
292 * Lease/@state - mandatory but not present in old leases file, so pretent
293 * we're loading an expired one if absent.
294 */
295 const char *pszState = pElmLease->findAttributeValue("state");
296 if (pszState)
297 {
298 b->m_issued = Timestamp::absSeconds(secIssued);
299 b->setState(pszState);
300 }
301 else
302 { /** @todo XXX: old code wrote timestamps instead of absolute time. */
303 /* pretend that lease has just ended */
304 LogRel(("Binding::fromXML: No 'state' attribute for <Lease mac=%RTmac> (ts=%RI64 ttl=%RU32)! Assuming EXPIRED.\n",
305 &mac, secIssued, cSecToLive));
306 b->m_issued = Timestamp::now().subSeconds(cSecToLive);
307 b->m_state = Binding::EXPIRED;
308 }
309 b->m_secLease = cSecToLive;
310
311
312 /*
313 * Lease/@id - optional, ignore if bad.
314 * Value format: "deadbeef..." or "de:ad:be:ef...".
315 */
316 const char *pszClientId = pElmLease->findAttributeValue("id");
317 if (pszClientId)
318 {
319 uint8_t abBytes[255];
320 size_t cbActual;
321 rc = RTStrConvertHexBytesEx(pszClientId, abBytes, sizeof(abBytes), RTSTRCONVERTHEXBYTES_F_SEP_COLON, NULL, &cbActual);
322 if (RT_SUCCESS(rc))
323 {
324 b->m_id = ClientId(mac, OptClientId(std::vector<uint8_t>(&abBytes[0], &abBytes[cbActual]))); /* throws bad_alloc */
325 if (rc != VINF_BUFFER_UNDERFLOW && rc != VINF_SUCCESS)
326 LogRel(("Binding::fromXML: imperfect 'id' attribute: rc=%Rrc, cbActual=%u, '%s'\n", rc, cbActual, pszClientId));
327 }
328 else
329 {
330 LogRel(("Binding::fromXML: ignoring malformed 'id' attribute: rc=%Rrc, cbActual=%u, '%s'\n",
331 rc, cbActual, pszClientId));
332 b->m_id = ClientId(mac, OptClientId());
333 }
334 }
335 else
336 b->m_id = ClientId(mac, OptClientId());
337
338 return b.release();
339}
340
341
342
343/*********************************************************************************************************************************
344* Class Db Implementation *
345*********************************************************************************************************************************/
346
347Db::Db()
348 : m_pConfig(NULL)
349{
350}
351
352
353Db::~Db()
354{
355 /** @todo free bindings */
356}
357
358
359int Db::init(const Config *pConfig)
360{
361 Binding::registerFormat();
362
363 m_pConfig = pConfig;
364
365 int rc = m_pool.init(pConfig->getIPv4PoolFirst(), pConfig->getIPv4PoolLast());
366 if (RT_SUCCESS(rc))
367 {
368 /*
369 * If the server IP is in the dynamic range, preallocate it like a fixed assignment.
370 */
371 rc = i_enterFixedAddressAssignment(pConfig->getIPv4Address(), pConfig->getMacAddress());
372 if (RT_SUCCESS(rc))
373 {
374 /*
375 * Preallocate any fixed address assignments:
376 */
377 Config::HostConfigVec vecHostConfigs;
378 rc = pConfig->getFixedAddressConfigs(vecHostConfigs);
379 for (Config::HostConfigVec::const_iterator it = vecHostConfigs.begin();
380 it != vecHostConfigs.end() && RT_SUCCESS(rc); ++it)
381 rc = i_enterFixedAddressAssignment((*it)->getFixedAddress(), (*it)->getMACAddress());
382 }
383 }
384
385 return rc;
386}
387
388
389/**
390 * Used by Db::init() to register a fixed address assignment.
391 *
392 * @returns IPRT status code.
393 * @param a_rAddress The IPv4 address assignment.
394 * @param a_rMACAddress The MAC address.
395 */
396int Db::i_enterFixedAddressAssignment(RTNETADDRIPV4 const &a_rAddress, RTMAC const &a_rMACAddress) RT_NOEXCEPT
397{
398 LogRelFunc(("%RTmac: %RTnaipv4\n", &a_rMACAddress, a_rAddress));
399 Assert(m_pConfig->isInIPv4Network(a_rAddress)); /* should've been checked elsewhere already */
400
401 /*
402 * If the address is part of the pool, we have to allocate it to
403 * prevent it from being used again.
404 */
405 if (m_pool.contains(a_rAddress))
406 {
407 if (!m_pool.allocate(a_rAddress))
408 {
409 LogRelFunc(("%RTnaipv4 already allocated?\n", a_rAddress));
410 return VERR_ADDRESS_CONFLICT;
411 }
412 }
413
414 /*
415 * Create the binding.
416 */
417 Binding *pBinding = NULL;
418 try
419 {
420 pBinding = new Binding(a_rAddress, a_rMACAddress, true /*fFixed*/);
421 m_bindings.push_front(pBinding);
422 }
423 catch (std::bad_alloc &)
424 {
425 if (pBinding)
426 delete pBinding;
427 return VERR_NO_MEMORY;
428 }
429 return VINF_SUCCESS;
430}
431
432
433/**
434 * Expire old binding (leases).
435 */
436void Db::expire() RT_NOEXCEPT
437{
438 const Timestamp now = Timestamp::now();
439 for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
440 {
441 Binding *b = *it;
442 b->expire(now);
443 }
444}
445
446
447/**
448 * Internal worker that creates a binding for the given client, allocating new
449 * IPv4 address for it.
450 *
451 * @returns Pointer to the binding.
452 * @param id The client ID.
453 */
454Binding *Db::i_createBinding(const ClientId &id)
455{
456 Binding *pBinding = NULL;
457 RTNETADDRIPV4 addr = m_pool.allocate();
458 if (addr.u != 0)
459 {
460 try
461 {
462 pBinding = new Binding(addr, id);
463 m_bindings.push_front(pBinding);
464 }
465 catch (std::bad_alloc &)
466 {
467 if (pBinding)
468 delete pBinding;
469 /** @todo free address (no pool method for that) */
470 }
471 }
472 return pBinding;
473}
474
475
476/**
477 * Internal worker that creates a binding to the specified IPv4 address for the
478 * given client.
479 *
480 * @returns Pointer to the binding.
481 * NULL if the address is in use or we ran out of memory.
482 * @param addr The IPv4 address.
483 * @param id The client.
484 */
485Binding *Db::i_createBinding(RTNETADDRIPV4 addr, const ClientId &id)
486{
487 bool fAvailable = m_pool.allocate(addr);
488 if (!fAvailable)
489 {
490 /** @todo
491 * XXX: this should not happen. If the address is from the
492 * pool, which we have verified before, then either it's in
493 * the free pool or there's an binding (possibly free) for it.
494 */
495 return NULL;
496 }
497
498 Binding *b = new Binding(addr, id);
499 m_bindings.push_front(b);
500 return b;
501}
502
503
504/**
505 * Internal worker that allocates an IPv4 address for the given client, taking
506 * the preferred address (@a addr) into account when possible and if non-zero.
507 */
508Binding *Db::i_allocateAddress(const ClientId &id, RTNETADDRIPV4 addr)
509{
510 Assert(addr.u == 0 || addressBelongs(addr));
511
512 if (addr.u != 0)
513 LogRel(("> allocateAddress %RTnaipv4 to client %R[id]\n", addr.u, &id));
514 else
515 LogRel(("> allocateAddress to client %R[id]\n", &id));
516
517 /*
518 * Allocate existing address if client has one. Ignore requested
519 * address in that case. While here, look for free addresses and
520 * addresses that can be reused.
521 */
522 Binding *addrBinding = NULL;
523 Binding *freeBinding = NULL;
524 Binding *reuseBinding = NULL;
525 const Timestamp now = Timestamp::now();
526 for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
527 {
528 Binding *b = *it;
529 b->expire(now);
530
531 /*
532 * We've already seen this client, give it its old binding.
533 *
534 * If the client's MAC address is configured with a fixed
535 * address, give its preconfigured binding. Fixed bindings
536 * are always at the head of the m_bindings list, so we
537 * won't be confused by any old leases of the client.
538 */
539 if (b->m_id == id)
540 {
541 LogRel(("> ... found existing binding %R[binding]\n", b));
542 return b;
543 }
544 if (b->isFixed() && b->id().mac() == id.mac())
545 {
546 b->idUpdate(id);
547 LogRel(("> ... found fixed binding %R[binding]\n", b));
548 return b;
549 }
550
551 if (addr.u != 0 && b->m_addr.u == addr.u)
552 {
553 Assert(addrBinding == NULL);
554 addrBinding = b;
555 LogRel(("> .... noted existing binding %R[binding]\n", addrBinding));
556 }
557
558 /* if we haven't found a free binding yet, keep looking */
559 if (freeBinding == NULL)
560 {
561 if (b->m_state == Binding::FREE)
562 {
563 freeBinding = b;
564 LogRel(("> .... noted free binding %R[binding]\n", freeBinding));
565 continue;
566 }
567
568 /* still no free binding, can this one be reused? */
569 if (b->m_state == Binding::RELEASED)
570 {
571 if ( reuseBinding == NULL
572 /* released binding is better than an expired one */
573 || reuseBinding->m_state == Binding::EXPIRED)
574 {
575 reuseBinding = b;
576 LogRel(("> .... noted released binding %R[binding]\n", reuseBinding));
577 }
578 }
579 else if (b->m_state == Binding::EXPIRED)
580 {
581 if ( reuseBinding == NULL
582 /* long expired binding is bettern than a recent one */
583 /* || (reuseBinding->m_state == Binding::EXPIRED && b->olderThan(reuseBinding)) */)
584 {
585 reuseBinding = b;
586 LogRel(("> .... noted expired binding %R[binding]\n", reuseBinding));
587 }
588 }
589 }
590 }
591
592 /*
593 * Allocate requested address if we can.
594 */
595 if (addr.u != 0)
596 {
597 if (addrBinding == NULL)
598 {
599 addrBinding = i_createBinding(addr, id);
600 Assert(addrBinding != NULL);
601 LogRel(("> .... creating new binding for this address %R[binding]\n", addrBinding));
602 return addrBinding;
603 }
604
605 if (addrBinding->m_state <= Binding::EXPIRED) /* not in use */
606 {
607 LogRel(("> .... reusing %s binding for this address\n", addrBinding->stateName()));
608 addrBinding->giveTo(id);
609 return addrBinding;
610 }
611 LogRel(("> .... cannot reuse %s binding for this address\n", addrBinding->stateName()));
612 }
613
614 /*
615 * Allocate new (or reuse).
616 */
617 Binding *idBinding = NULL;
618 if (freeBinding != NULL)
619 {
620 idBinding = freeBinding;
621 LogRel(("> .... reusing free binding\n"));
622 }
623 else
624 {
625 idBinding = i_createBinding();
626 if (idBinding != NULL)
627 LogRel(("> .... creating new binding\n"));
628 else
629 {
630 idBinding = reuseBinding;
631 if (idBinding != NULL)
632 LogRel(("> .... reusing %s binding %R[binding]\n", reuseBinding->stateName(), reuseBinding));
633 else
634 DHCP_LOG_RET_NULL(("> .... failed to allocate binding\n"));
635 }
636 }
637
638 idBinding->giveTo(id);
639 LogRel(("> .... allocated %R[binding]\n", idBinding));
640
641 return idBinding;
642}
643
644
645
646/**
647 * Called by DHCPD to allocate a binding for the specified request.
648 *
649 * @returns Pointer to the binding, NULL on failure.
650 * @param req The DHCP request being served.
651 * @param rConfigVec The configurations that applies to the client.
652 * Used for lease time calculation.
653 */
654Binding *Db::allocateBinding(const DhcpClientMessage &req, Config::ConfigVec const &rConfigVec)
655{
656 const ClientId &id(req.clientId());
657
658 /*
659 * Get and validate the requested address (if present).
660 *
661 * Fixed assignments are often outside the dynamic range, so we much detect
662 * those to make sure they aren't rejected based on IP range. ASSUMES fixed
663 * assignments are at the head of the binding list.
664 */
665 OptRequestedAddress reqAddr(req);
666 if (reqAddr.present() && !addressBelongs(reqAddr.value()))
667 {
668 bool fIsFixed = false;
669 for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end() && (*it)->isFixed(); ++it)
670 if (reqAddr.value().u == (*it)->addr().u)
671 {
672 if ( (*it)->id() == id
673 || (*it)->id().mac() == id.mac())
674 {
675 fIsFixed = true;
676 break;
677 }
678 }
679 if (fIsFixed)
680 reqAddr = OptRequestedAddress();
681 else if (req.messageType() == RTNET_DHCP_MT_DISCOVER)
682 {
683 LogRel(("DISCOVER: ignoring invalid requested address\n"));
684 reqAddr = OptRequestedAddress();
685 }
686 else
687 DHCP_LOG_RET_NULL(("rejecting invalid requested address\n"));
688 }
689
690 /*
691 * Allocate the address.
692 */
693 Binding *b = i_allocateAddress(id, reqAddr.value());
694 if (b != NULL)
695 {
696 Assert(b->id() == id);
697
698 /*
699 * Figure out the lease time.
700 */
701 uint32_t secMin = 0;
702 uint32_t secDfl = 0;
703 uint32_t secMax = 0;
704 for (Config::ConfigVec::const_iterator it = rConfigVec.begin(); it != rConfigVec.end(); ++it)
705 {
706 ConfigLevelBase const *pConfig = *it;
707 if (secMin == 0)
708 secMin = pConfig->getMinLeaseTime();
709 if (secDfl == 0)
710 secDfl = pConfig->getDefaultLeaseTime();
711 if (secMax == 0)
712 secMax = pConfig->getMaxLeaseTime();
713 }
714 Assert(secMin); Assert(secMax); Assert(secDfl); /* global config always have non-defaults set */
715 if (secMin > secMax)
716 secMin = secMax;
717
718 OptLeaseTime reqLeaseTime(req);
719 if (!reqLeaseTime.present())
720 {
721 b->setLeaseTime(secDfl);
722 LogRel2(("Lease time %u secs (default)\n", b->leaseTime()));
723 }
724 else if (reqLeaseTime.value() < secMin)
725 {
726 b->setLeaseTime(secMin);
727 LogRel2(("Lease time %u secs (min)\n", b->leaseTime()));
728 }
729 else if (reqLeaseTime.value() > secMax)
730 {
731 b->setLeaseTime(secMax);
732 LogRel2(("Lease time %u secs (max)\n", b->leaseTime()));
733 }
734 else
735 {
736 b->setLeaseTime(reqLeaseTime.value());
737 LogRel2(("Lease time %u secs (requested)\n", b->leaseTime()));
738 }
739 }
740 return b;
741}
742
743
744/**
745 * Internal worker used by loadLease().
746 *
747 * @returns IPRT status code.
748 * @param pNewBinding The new binding to add.
749 */
750int Db::i_addBinding(Binding *pNewBinding) RT_NOEXCEPT
751{
752 /*
753 * Validate the binding against the range and existing bindings.
754 */
755 if (!addressBelongs(pNewBinding->m_addr))
756 {
757 LogRel(("Binding for out of range address %RTnaipv4 ignored\n", pNewBinding->m_addr.u));
758 return VERR_OUT_OF_RANGE;
759 }
760
761 for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
762 {
763 Binding *b = *it;
764
765 if (pNewBinding->m_addr.u == b->m_addr.u)
766 {
767 LogRel(("> ADD: %R[binding]\n", pNewBinding));
768 LogRel(("> .... duplicate ip: %R[binding]\n", b));
769 return VERR_DUPLICATE;
770 }
771
772 if (pNewBinding->m_id == b->m_id)
773 {
774 LogRel(("> ADD: %R[binding]\n", pNewBinding));
775 LogRel(("> .... duplicate id: %R[binding]\n", b));
776 return VERR_DUPLICATE;
777 }
778 }
779
780 /*
781 * Allocate the address and add the binding to the list.
782 */
783 AssertLogRelMsgReturn(m_pool.allocate(pNewBinding->m_addr),
784 ("> ADD: failed to claim IP %R[binding]\n", pNewBinding),
785 VERR_INTERNAL_ERROR);
786 try
787 {
788 m_bindings.push_back(pNewBinding);
789 }
790 catch (std::bad_alloc &)
791 {
792 return VERR_NO_MEMORY;
793 }
794 return VINF_SUCCESS;
795}
796
797
798/**
799 * Called by DHCP to cancel an offset.
800 *
801 * @param req The DHCP request.
802 */
803void Db::cancelOffer(const DhcpClientMessage &req) RT_NOEXCEPT
804{
805 const OptRequestedAddress reqAddr(req);
806 if (!reqAddr.present())
807 return;
808
809 const RTNETADDRIPV4 addr = reqAddr.value();
810 const ClientId &id(req.clientId());
811
812 for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
813 {
814 Binding *b = *it;
815
816 if (b->addr().u == addr.u && b->id() == id)
817 {
818 if (b->state() == Binding::OFFERED)
819 {
820 LogRel2(("Db::cancelOffer: cancelling %R[binding]\n", b));
821 if (!b->isFixed())
822 {
823 b->setLeaseTime(0);
824 b->setState(Binding::RELEASED);
825 }
826 else
827 b->setState(Binding::ACKED);
828 }
829 else
830 LogRel2(("Db::cancelOffer: not offered state: %R[binding]\n", b));
831 return;
832 }
833 }
834 LogRel2(("Db::cancelOffer: not found (%RTnaipv4, %R[id])\n", addr.u, &id));
835}
836
837
838/**
839 * Called by DHCP to cancel an offset.
840 *
841 * @param req The DHCP request.
842 * @returns true if found and released, otherwise false.
843 * @throws nothing
844 */
845bool Db::releaseBinding(const DhcpClientMessage &req) RT_NOEXCEPT
846{
847 const RTNETADDRIPV4 addr = req.ciaddr();
848 const ClientId &id(req.clientId());
849
850 for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
851 {
852 Binding *b = *it;
853
854 if (b->addr().u == addr.u && b->id() == id)
855 {
856 LogRel2(("Db::releaseBinding: releasing %R[binding]\n", b));
857 if (!b->isFixed())
858 {
859 b->setState(Binding::RELEASED);
860 return true;
861 }
862 b->setState(Binding::ACKED);
863 return false;
864 }
865 }
866
867 LogRel2(("Db::releaseBinding: not found (%RTnaipv4, %R[id])\n", addr.u, &id));
868 return false;
869}
870
871
872/**
873 * Called by DHCPD to write out the lease database to @a strFilename.
874 *
875 * @returns IPRT status code.
876 * @param strFilename The file to write it to.
877 */
878int Db::writeLeases(const RTCString &strFilename) const RT_NOEXCEPT
879{
880 LogRel(("writing leases to %s\n", strFilename.c_str()));
881
882 /** @todo This could easily be written directly to the file w/o going thru
883 * a xml::Document, xml::XmlFileWriter, hammering the heap and being
884 * required to catch a lot of different exceptions at various points.
885 * (RTStrmOpen, bunch of RTStrmPrintf using \%RMas and \%RMes.,
886 * RTStrmClose closely followed by a couple of renames.)
887 */
888
889 /*
890 * Create the document and root element.
891 */
892 xml::Document doc;
893 try
894 {
895 xml::ElementNode *pElmRoot = doc.createRootElement("Leases");
896 pElmRoot->setAttribute("version", "1.0");
897
898 /*
899 * Add the leases.
900 */
901 for (bindings_t::const_iterator it = m_bindings.begin(); it != m_bindings.end(); ++it)
902 {
903 const Binding *b = *it;
904 if (!b->isFixed())
905 b->toXML(pElmRoot);
906 }
907 }
908 catch (std::bad_alloc &)
909 {
910 return VERR_NO_MEMORY;
911 }
912
913 /*
914 * Write the document to the specified file in a safe manner (written to temporary
915 * file, renamed to destination on success)
916 */
917 try
918 {
919 xml::XmlFileWriter writer(doc);
920 writer.write(strFilename.c_str(), true /*fSafe*/);
921 }
922 catch (const xml::EIPRTFailure &e)
923 {
924 LogRel(("%s\n", e.what()));
925 return e.rc();
926 }
927 catch (const RTCError &e)
928 {
929 LogRel(("%s\n", e.what()));
930 return VERR_GENERAL_FAILURE;
931 }
932 catch (...)
933 {
934 LogRel(("Unknown exception while writing '%s'\n", strFilename.c_str()));
935 return VERR_UNEXPECTED_EXCEPTION;
936 }
937
938 return VINF_SUCCESS;
939}
940
941
942/**
943 * Called by DHCPD to load the lease database to @a strFilename.
944 *
945 * @note Does not clear the database state before doing the load.
946 *
947 * @returns IPRT status code.
948 * @param strFilename The file to load it from.
949 * @throws nothing
950 */
951int Db::loadLeases(const RTCString &strFilename) RT_NOEXCEPT
952{
953 LogRel(("loading leases from %s\n", strFilename.c_str()));
954
955 /*
956 * Load the file into an XML document.
957 */
958 xml::Document doc;
959 try
960 {
961 xml::XmlFileParser parser;
962 parser.read(strFilename.c_str(), doc);
963 }
964 catch (const xml::EIPRTFailure &e)
965 {
966 LogRel(("%s\n", e.what()));
967 return e.rc();
968 }
969 catch (const RTCError &e)
970 {
971 LogRel(("%s\n", e.what()));
972 return VERR_GENERAL_FAILURE;
973 }
974 catch (...)
975 {
976 LogRel(("Unknown exception while reading and parsing '%s'\n", strFilename.c_str()));
977 return VERR_UNEXPECTED_EXCEPTION;
978 }
979
980 /*
981 * Check that the root element is "Leases" and process its children.
982 */
983 xml::ElementNode *pElmRoot = doc.getRootElement();
984 if (!pElmRoot)
985 {
986 LogRel(("No root element in '%s'\n", strFilename.c_str()));
987 return VERR_NOT_FOUND;
988 }
989 if (!pElmRoot->nameEquals("Leases"))
990 {
991 LogRel(("No root element is not 'Leases' in '%s', but '%s'\n", strFilename.c_str(), pElmRoot->getName()));
992 return VERR_NOT_FOUND;
993 }
994
995 int rc = VINF_SUCCESS;
996 xml::NodesLoop it(*pElmRoot);
997 const xml::ElementNode *pElmLease;
998 while ((pElmLease = it.forAllNodes()) != NULL)
999 {
1000 if (pElmLease->nameEquals("Lease"))
1001 {
1002 int rc2 = i_loadLease(pElmLease);
1003 if (RT_SUCCESS(rc2))
1004 { /* likely */ }
1005 else if (rc2 == VERR_NO_MEMORY)
1006 return rc2;
1007 else
1008 rc = -rc2;
1009 }
1010 else
1011 LogRel(("Ignoring unexpected element '%s' under 'Leases'...\n", pElmLease->getName()));
1012 }
1013
1014 return rc;
1015}
1016
1017
1018/**
1019 * Internal worker for loadLeases() that handles one 'Lease' element.
1020 *
1021 * @param pElmLease The 'Lease' element to handle.
1022 * @return IPRT status code.
1023 */
1024int Db::i_loadLease(const xml::ElementNode *pElmLease) RT_NOEXCEPT
1025{
1026 Binding *pBinding = NULL;
1027 try
1028 {
1029 pBinding = Binding::fromXML(pElmLease);
1030 }
1031 catch (std::bad_alloc &)
1032 {
1033 return VERR_NO_MEMORY;
1034 }
1035 if (pBinding)
1036 {
1037 bool fExpired = pBinding->expire();
1038 if (!fExpired)
1039 LogRel(("> LOAD: lease %R[binding]\n", pBinding));
1040 else
1041 LogRel(("> LOAD: EXPIRED lease %R[binding]\n", pBinding));
1042
1043 int rc = i_addBinding(pBinding);
1044 if (RT_FAILURE(rc))
1045 delete pBinding;
1046 return rc;
1047 }
1048 LogRel(("> LOAD: failed to load lease!\n"));
1049 return VERR_PARSE_ERROR;
1050}
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