VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 17 months ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use